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)
|
}>(CoreActions.InstanceLog)
|
||||||
|
|
||||||
export const [doAppMountedAction, onAppMountedAction] =
|
export const [doAppMountedAction, onAppMountedAction] =
|
||||||
createCustomActionWithContext<{ app: Express }>(CoreActions.AppMounted)
|
createCustomActionWithContext<{ app: Express; internalApp: Express }>(
|
||||||
|
CoreActions.AppMounted,
|
||||||
|
)
|
||||||
|
|
||||||
export const [doIncomingRequestAction, onIncomingRequestAction] =
|
export const [doIncomingRequestAction, onIncomingRequestAction] =
|
||||||
createCustomActionWithContext<{ req: Request; res: Response; host: string }>(
|
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 { doNewInstanceRecordFilter } from '../plugin'
|
||||||
|
import { IsoDate } from './BaseFields'
|
||||||
|
import { newId, pocketNow } from './util'
|
||||||
|
|
||||||
export type VersionId = string
|
export type VersionId = string
|
||||||
|
|
||||||
@ -21,16 +23,24 @@ export type InstanceSecretCollection = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export type InstanceFields = {
|
export type InstanceFields = {
|
||||||
|
id: string
|
||||||
subdomain: string
|
subdomain: string
|
||||||
version: VersionId
|
version: VersionId
|
||||||
secrets: InstanceSecretCollection
|
secrets: InstanceSecretCollection
|
||||||
dev: boolean // Should instance run in --dev mode
|
dev: boolean // Should instance run in --dev mode
|
||||||
|
created: IsoDate
|
||||||
|
updated: IsoDate
|
||||||
}
|
}
|
||||||
|
|
||||||
export const mkInstance = (subdomain: string) =>
|
export const mkInstance = (subdomain: string, id = newId()) => {
|
||||||
doNewInstanceRecordFilter({
|
const now = pocketNow()
|
||||||
|
return doNewInstanceRecordFilter({
|
||||||
|
id,
|
||||||
subdomain,
|
subdomain,
|
||||||
version: '*',
|
version: '*',
|
||||||
secrets: {},
|
secrets: {},
|
||||||
dev: false,
|
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 { cwd } from 'process'
|
||||||
import { fileURLToPath } from 'url'
|
import { fileURLToPath } from 'url'
|
||||||
import { mkBoolean, mkCsvString, mkNumber, mkPath, mkString } from '../core'
|
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 { DEBUG, IS_DEV } from './common/debug'
|
||||||
import { Settings } from './core/Settings'
|
import { Settings } from './core/Settings'
|
||||||
import { logSettings } from './core/logSettings'
|
import { logSettings } from './core/logSettings'
|
||||||
@ -32,6 +32,7 @@ export const settings = Settings({
|
|||||||
PH_HOME: mkPath(_PH_HOME),
|
PH_HOME: mkPath(_PH_HOME),
|
||||||
PH_PROJECT_DIR: mkPath(_PH_PROJECT_DIR),
|
PH_PROJECT_DIR: mkPath(_PH_PROJECT_DIR),
|
||||||
|
|
||||||
|
PH_INTERNAL_HOST: mkString('localhost'),
|
||||||
PH_APEX_DOMAIN: mkString(_APEX_DOMAIN),
|
PH_APEX_DOMAIN: mkString(_APEX_DOMAIN),
|
||||||
|
|
||||||
PH_PORT: mkNumber(3000),
|
PH_PORT: mkNumber(3000),
|
||||||
@ -39,6 +40,7 @@ export const settings = Settings({
|
|||||||
PH_DATA_DIR: mkPath(join(_PH_HOME, 'data'), { create: true }),
|
PH_DATA_DIR: mkPath(join(_PH_HOME, 'data'), { create: true }),
|
||||||
PH_DEV: mkBoolean(IS_DEV()),
|
PH_DEV: mkBoolean(IS_DEV()),
|
||||||
PH_DEBUG: mkBoolean(DEBUG()),
|
PH_DEBUG: mkBoolean(DEBUG()),
|
||||||
|
PH_INTERNAL_APP_SECRET: mkString(''),
|
||||||
})
|
})
|
||||||
|
|
||||||
/** Accessors */
|
/** Accessors */
|
||||||
@ -57,6 +59,14 @@ export const DATA_DIR = (...paths: string[]) =>
|
|||||||
export const NODE_ENV = () => process.env.NODE_ENV
|
export const NODE_ENV = () => process.env.NODE_ENV
|
||||||
export const INSTANCE_DATA_DIR = (id: InstanceId, ...paths: string[]) =>
|
export const INSTANCE_DATA_DIR = (id: InstanceId, ...paths: string[]) =>
|
||||||
join(DATA_DIR(), id, ...paths)
|
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 HTTP_PROTOCOL = () => (PORT() === 443 ? 'https:' : 'http:')
|
||||||
export const PUBLIC_INSTANCE_URL = ({ subdomain }: Partial<InstanceFields>) => {
|
export const PUBLIC_INSTANCE_URL = ({ subdomain }: Partial<InstanceFields>) => {
|
||||||
const url = new URL(`${HTTP_PROTOCOL()}//${subdomain}.${APEX_DOMAIN()}`)
|
const url = new URL(`${HTTP_PROTOCOL()}//${subdomain}.${APEX_DOMAIN()}`)
|
||||||
|
@ -4,6 +4,7 @@ import {
|
|||||||
PocketHostPlugin,
|
PocketHostPlugin,
|
||||||
doSettingsFilter,
|
doSettingsFilter,
|
||||||
namespace,
|
namespace,
|
||||||
|
newId,
|
||||||
onAfterPluginsLoadedAction,
|
onAfterPluginsLoadedAction,
|
||||||
onInstanceConfigFilter,
|
onInstanceConfigFilter,
|
||||||
onInstanceLogAction,
|
onInstanceLogAction,
|
||||||
@ -12,6 +13,7 @@ import {
|
|||||||
} from '../common'
|
} from '../common'
|
||||||
import { dbg, info } from './cli'
|
import { dbg, info } from './cli'
|
||||||
import { INTERNAL_APP_SECRET, PH_PROJECT_DIR, settings } from './constants'
|
import { INTERNAL_APP_SECRET, PH_PROJECT_DIR, settings } from './constants'
|
||||||
|
import { setConfig } from './core/config'
|
||||||
import { serve } from './server'
|
import { serve } from './server'
|
||||||
|
|
||||||
export const pockethost: PocketHostPlugin = async ({}) => {
|
export const pockethost: PocketHostPlugin = async ({}) => {
|
||||||
@ -44,4 +46,9 @@ export const pockethost: PocketHostPlugin = async ({}) => {
|
|||||||
})
|
})
|
||||||
}, 99)
|
}, 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,
|
doRequestErrorAction,
|
||||||
doRequestErrorMessageFilter,
|
doRequestErrorMessageFilter,
|
||||||
} from '../common'
|
} 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 () => {
|
export const serve = async () => {
|
||||||
const _proxyLogger = LoggerService().create('ProxyService')
|
const _proxyLogger = LoggerService().create('ProxyService')
|
||||||
@ -45,7 +52,25 @@ export const serve = async () => {
|
|||||||
}
|
}
|
||||||
app.use(errorHandler)
|
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) => {
|
app.all(`*`, async (req, res, next) => {
|
||||||
const method = req.method
|
const method = req.method
|
||||||
|
Loading…
x
Reference in New Issue
Block a user