mirror of
https://github.com/pockethost/pockethost.git
synced 2025-09-15 21:20:11 +00:00
feat(pockethost): Add internal app (remote control) support
This commit is contained in:
parent
b7277cf885
commit
45a8f457da
5
.changeset/1719706482967.md
Normal file
5
.changeset/1719706482967.md
Normal file
@ -0,0 +1,5 @@
|
||||
---
|
||||
'pockethost': minor
|
||||
---
|
||||
|
||||
Add internal app (remote control) support
|
@ -117,7 +117,9 @@ export const [doInstanceLogAction, onInstanceLogAction] =
|
||||
}>(CoreActions.InstanceLog)
|
||||
|
||||
export const [doAppMountedAction, onAppMountedAction] =
|
||||
createCustomActionWithContext<{ app: Express }>(CoreActions.AppMounted)
|
||||
createCustomActionWithContext<{ app: Express; internalApp: Express }>(
|
||||
CoreActions.AppMounted,
|
||||
)
|
||||
|
||||
export const [doIncomingRequestAction, onIncomingRequestAction] =
|
||||
createCustomActionWithContext<{ req: Request; res: Response; host: string }>(
|
||||
|
10
packages/pockethost/src/common/schema/BaseFields.ts
Normal file
10
packages/pockethost/src/common/schema/BaseFields.ts
Normal file
@ -0,0 +1,10 @@
|
||||
export type RecordId = string
|
||||
export type UserId = RecordId
|
||||
export type IsoDate = string
|
||||
export type BaseFields = {
|
||||
id: RecordId
|
||||
created: IsoDate
|
||||
updated: IsoDate
|
||||
}
|
||||
|
||||
export type AnyField = { [_: string]: string | number }
|
@ -1,4 +1,6 @@
|
||||
import { doNewInstanceRecordFilter } from '../plugin'
|
||||
import { IsoDate } from './BaseFields'
|
||||
import { newId, pocketNow } from './util'
|
||||
|
||||
export type VersionId = string
|
||||
|
||||
@ -21,16 +23,24 @@ export type InstanceSecretCollection = {
|
||||
}
|
||||
|
||||
export type InstanceFields = {
|
||||
id: string
|
||||
subdomain: string
|
||||
version: VersionId
|
||||
secrets: InstanceSecretCollection
|
||||
dev: boolean // Should instance run in --dev mode
|
||||
created: IsoDate
|
||||
updated: IsoDate
|
||||
}
|
||||
|
||||
export const mkInstance = (subdomain: string) =>
|
||||
doNewInstanceRecordFilter({
|
||||
export const mkInstance = (subdomain: string, id = newId()) => {
|
||||
const now = pocketNow()
|
||||
return doNewInstanceRecordFilter({
|
||||
id,
|
||||
subdomain,
|
||||
version: '*',
|
||||
secrets: {},
|
||||
dev: false,
|
||||
created: now,
|
||||
updated: now,
|
||||
})
|
||||
}
|
||||
|
@ -1 +1,7 @@
|
||||
export const pocketNow = () => new Date().toISOString()
|
||||
import { customAlphabet } from 'nanoid'
|
||||
import { IsoDate } from './BaseFields'
|
||||
|
||||
export const newId = customAlphabet('1234567890abcdefghijklmnopqrstuvwxyz', 10)
|
||||
export const pocketNow = () => toPocketDate(new Date())
|
||||
export const toPocketDate = (date: Date) => date.toISOString()
|
||||
export const fromPocketDate = (iso: IsoDate) => new Date(iso)
|
||||
|
@ -4,7 +4,7 @@ import { dirname, join } from 'path'
|
||||
import { cwd } from 'process'
|
||||
import { fileURLToPath } from 'url'
|
||||
import { mkBoolean, mkCsvString, mkNumber, mkPath, mkString } from '../core'
|
||||
import { InstanceId } from './common'
|
||||
import { InstanceFields, InstanceId } from './common'
|
||||
import { DEBUG, IS_DEV } from './common/debug'
|
||||
import { Settings } from './core/Settings'
|
||||
import { logSettings } from './core/logSettings'
|
||||
@ -32,6 +32,7 @@ export const settings = Settings({
|
||||
PH_HOME: mkPath(_PH_HOME),
|
||||
PH_PROJECT_DIR: mkPath(_PH_PROJECT_DIR),
|
||||
|
||||
PH_INTERNAL_HOST: mkString('localhost'),
|
||||
PH_APEX_DOMAIN: mkString(_APEX_DOMAIN),
|
||||
|
||||
PH_PORT: mkNumber(3000),
|
||||
@ -39,6 +40,7 @@ export const settings = Settings({
|
||||
PH_DATA_DIR: mkPath(join(_PH_HOME, 'data'), { create: true }),
|
||||
PH_DEV: mkBoolean(IS_DEV()),
|
||||
PH_DEBUG: mkBoolean(DEBUG()),
|
||||
PH_INTERNAL_APP_SECRET: mkString(''),
|
||||
})
|
||||
|
||||
/** Accessors */
|
||||
@ -57,6 +59,14 @@ export const DATA_DIR = (...paths: string[]) =>
|
||||
export const NODE_ENV = () => process.env.NODE_ENV
|
||||
export const INSTANCE_DATA_DIR = (id: InstanceId, ...paths: string[]) =>
|
||||
join(DATA_DIR(), id, ...paths)
|
||||
export const INTERNAL_HOST = () => settings.PH_INTERNAL_HOST
|
||||
export const INTERNAL_APP_SECRET = () => settings.PH_INTERNAL_APP_SECRET
|
||||
export const INTERNAL_APP_URL = (...paths: string[]) =>
|
||||
`${HTTP_PROTOCOL()}//${INTERNAL_HOST()}:${PORT()}/${INTERNAL_APP_PREFIX}/${join(
|
||||
...paths,
|
||||
)}`
|
||||
export const INTERNAL_APP_PREFIX = `_internal`
|
||||
export const INTERNAL_APP_AUTH_HEADER = `x-pockethost-authorization`
|
||||
export const HTTP_PROTOCOL = () => (PORT() === 443 ? 'https:' : 'http:')
|
||||
export const PUBLIC_INSTANCE_URL = ({ subdomain }: Partial<InstanceFields>) => {
|
||||
const url = new URL(`${HTTP_PROTOCOL()}//${subdomain}.${APEX_DOMAIN()}`)
|
||||
|
@ -4,6 +4,7 @@ import {
|
||||
PocketHostPlugin,
|
||||
doSettingsFilter,
|
||||
namespace,
|
||||
newId,
|
||||
onAfterPluginsLoadedAction,
|
||||
onInstanceConfigFilter,
|
||||
onInstanceLogAction,
|
||||
@ -12,6 +13,7 @@ import {
|
||||
} from '../common'
|
||||
import { dbg, info } from './cli'
|
||||
import { INTERNAL_APP_SECRET, PH_PROJECT_DIR, settings } from './constants'
|
||||
import { setConfig } from './core/config'
|
||||
import { serve } from './server'
|
||||
|
||||
export const pockethost: PocketHostPlugin = async ({}) => {
|
||||
@ -44,4 +46,9 @@ export const pockethost: PocketHostPlugin = async ({}) => {
|
||||
})
|
||||
}, 99)
|
||||
|
||||
onAfterPluginsLoadedAction(async () => {
|
||||
if (INTERNAL_APP_SECRET()) return
|
||||
info(`Generating internal app secret...`)
|
||||
setConfig(`PH_INTERNAL_APP_SECRET`, newId(30))
|
||||
})
|
||||
}
|
||||
|
@ -12,7 +12,14 @@ import {
|
||||
doRequestErrorAction,
|
||||
doRequestErrorMessageFilter,
|
||||
} from '../common'
|
||||
import { APEX_DOMAIN, PORT, asyncExitHook } from '../core'
|
||||
import {
|
||||
APEX_DOMAIN,
|
||||
INTERNAL_APP_AUTH_HEADER,
|
||||
INTERNAL_APP_PREFIX,
|
||||
INTERNAL_APP_SECRET,
|
||||
PORT,
|
||||
asyncExitHook,
|
||||
} from '../core'
|
||||
|
||||
export const serve = async () => {
|
||||
const _proxyLogger = LoggerService().create('ProxyService')
|
||||
@ -45,7 +52,25 @@ export const serve = async () => {
|
||||
}
|
||||
app.use(errorHandler)
|
||||
|
||||
doAppMountedAction({ app })
|
||||
const internalApp = express()
|
||||
internalApp.use((req, res, next) => {
|
||||
dbg(`Got a request to internal app ${req.url}`)
|
||||
next()
|
||||
})
|
||||
internalApp.use((req, res, next) => {
|
||||
const authHeader = req.headers[INTERNAL_APP_AUTH_HEADER]
|
||||
const internalAppSecret = INTERNAL_APP_SECRET()
|
||||
|
||||
if (authHeader !== internalAppSecret) {
|
||||
res.status(403).send('Forbidden')
|
||||
return
|
||||
}
|
||||
|
||||
next()
|
||||
})
|
||||
app.use(`/${INTERNAL_APP_PREFIX}`, internalApp)
|
||||
|
||||
doAppMountedAction({ app, internalApp })
|
||||
|
||||
app.all(`*`, async (req, res, next) => {
|
||||
const method = req.method
|
||||
|
Loading…
x
Reference in New Issue
Block a user