pockethost 1.2.0 → 1.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,23 @@
1
1
  # pockethost
2
2
 
3
+ ## 1.3.0
4
+
5
+ ### Minor Changes
6
+
7
+ - 96f355a: Now auto-creates first admin account by default
8
+ - 2fc2eeb: Added before/after instance start/stop events
9
+ - 3f99ea0: Added subdomain to request action and filter context
10
+ - 1384a86: BREAKING: refactored actions/filters
11
+
12
+ ### Patch Changes
13
+
14
+ - 6812784: Added improved error parsing support
15
+ - da82667: Fix: properly determine hostname when running on nonstandard port
16
+ - cc4aa02: Moved mothership-related code into mothership plugin
17
+ - Updated dependencies [9d626ec]
18
+ - @pockethost/plugin-launcher-spawn@0.1.0
19
+ - @pockethost/plugin-console-logger@0.1.0
20
+
3
21
  ## 1.2.0
4
22
 
5
23
  ### Minor Changes
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pockethost",
3
- "version": "1.2.0",
3
+ "version": "1.3.0",
4
4
  "author": {
5
5
  "name": "Ben Allfree",
6
6
  "url": "https://github.com/benallfree"
@@ -39,8 +39,9 @@
39
39
  "typescript": "^5.4.5",
40
40
  "update-notifier": "^7.0.0",
41
41
  "url-pattern": "^1.0.3",
42
- "@pockethost/plugin-console-logger": "^0.0.1",
43
- "@pockethost/plugin-launcher-spawn": "^0.0.1"
42
+ "@pockethost/plugin-console-logger": "^0.1.0",
43
+ "@pockethost/plugin-launcher-spawn": "^0.1.0",
44
+ "@pockethost/plugin-auto-admin": "^0.0.1"
44
45
  },
45
46
  "devDependencies": {
46
47
  "@types/express": "^4.17.21",
@@ -49,9 +50,7 @@
49
50
  "@types/node": "^20.8.10",
50
51
  "@types/semver": "^7.5.4",
51
52
  "@types/tmp": "^0.2.5",
52
- "@types/update-notifier": "^6.0.8",
53
- "@pockethost/plugin-launcher-spawn": "0.0.1",
54
- "@pockethost/plugin-console-logger": "0.0.1"
53
+ "@types/update-notifier": "^6.0.8"
55
54
  },
56
55
  "scripts": {
57
56
  "check-types": "tsc --noEmit ",
package/src/cli/index.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env tsx
2
2
 
3
3
  import { uniq } from '@s-libs/micro-dash'
4
- import { Command, program } from 'commander'
4
+ import { program } from 'commander'
5
5
  import { readFileSync } from 'fs'
6
6
  import updateNotifier from 'update-notifier'
7
7
  import {
@@ -10,8 +10,7 @@ import {
10
10
  LoggerService,
11
11
  PH_PLUGINS,
12
12
  PH_PROJECT_DIR,
13
- PocketHostFilter,
14
- filter,
13
+ doCliCommandsFilter,
15
14
  loadPlugins,
16
15
  } from '../../core'
17
16
  import { ConfigCommand } from './commands/ConfigCommand'
@@ -36,11 +35,12 @@ export const main = async () => {
36
35
  .description('Multitenant PocketBase hosting')
37
36
  .version(version)
38
37
 
39
- const commands = await filter<Command[]>(PocketHostFilter.Core_CliCommands, [
38
+ const commands = await doCliCommandsFilter([
40
39
  ServeCommand(),
41
40
  ConfigCommand(),
42
41
  PluginCommand(),
43
42
  ])
43
+
44
44
  for (const command of commands) {
45
45
  program.addCommand(command)
46
46
  }
@@ -2,7 +2,7 @@
2
2
 
3
3
  import chalk from 'chalk'
4
4
  import stringify from 'json-stringify-safe'
5
- import { PocketHostAction, action, mergeConfig, mkSingleton } from '.'
5
+ import { doLogAction, mergeConfig, mkSingleton } from '.'
6
6
 
7
7
  export type LoggerConfig = {
8
8
  level: LogLevelName
@@ -74,7 +74,11 @@ export const createLogger = (config: Partial<LoggerConfig>) => {
74
74
  .join(' ')
75
75
 
76
76
  const _log = (levelIn: LogLevelName, ...args: any[]) => {
77
- action(PocketHostAction.Core_Log, _config.level, levelIn, args)
77
+ doLogAction({
78
+ currentLevel: _config.level,
79
+ levelIn,
80
+ args,
81
+ })
78
82
  }
79
83
 
80
84
  const raw = (...args: any[]) => {
@@ -0,0 +1,16 @@
1
+ import { keys, map } from '@s-libs/micro-dash'
2
+ import { ClientResponseError } from 'pocketbase'
3
+
4
+ export const parseError = (e: any): string[] => {
5
+ if (e instanceof ClientResponseError) {
6
+ if (e.data.message && keys(e.data.data).length === 0)
7
+ return [e.data.message]
8
+ return map(e.data.data, (v, k) => (v ? v.message : undefined)).filter(
9
+ (v) => !!v,
10
+ )
11
+ }
12
+ if (e instanceof Error) {
13
+ return [e.message]
14
+ }
15
+ return [e.toString()]
16
+ }
@@ -3,10 +3,10 @@ export * from './Logger'
3
3
  export * from './TimerManager'
4
4
  export * from './assert'
5
5
  export * from './debug'
6
+ export * from './error'
6
7
  export * from './events'
7
8
  export * from './mergeConfig'
8
9
  export * from './mkSingleton'
9
10
  export * from './plugin'
10
- export * from './pocketbase-client-helpers'
11
11
  export * from './promiseSerializer'
12
12
  export * from './schema'
@@ -1,60 +1,127 @@
1
+ import { Express, Request, Response } from 'express'
2
+ import { LogLevelName } from '../Logger'
1
3
  import { DEBUG } from '../debug'
4
+ import { InstanceFields } from '../schema'
2
5
 
3
- // Global registry of filters and actions
6
+ enum CoreActions {
7
+ Init = 'core_init',
8
+ AppMounted = 'core_on_app_mount',
9
+ AfterServeStart = 'core_after_serve_start',
4
10
 
5
- export enum PocketHostAction {
6
- /** Core actions */
7
- Core_Init = 'init',
8
- Core_OnAppMount = 'on_app_mount',
9
- Core_AfterServeStart = 'after_serve_start',
11
+ Cli_Before_ServeCommand = 'core_before_serve',
12
+ Log = 'core_log',
13
+ InstanceLog = 'core_instance_log',
10
14
 
11
- Core_Cli_Before_ServeCommand = 'before_serve',
12
- Core_Log = 'log',
13
- Core_InstanceLog = 'instance_log',
15
+ StartInstance = 'core_start_instance',
14
16
 
15
- Core_StartInstance = 'start_instance',
17
+ BeforeInstanceStarted = 'core_before_instance_started',
18
+ AfterInstanceStarted = 'core_after_instance_started',
16
19
 
17
- Core_BeforeInstanceStarted = 'before_instance_started',
18
- Core_AfterInstanceStarted = 'after_instance_started',
20
+ BeforeInstanceStopped = 'core_before_instance_stopped',
21
+ AfterInstanceStopped = 'core_after_instance_stopped',
19
22
 
20
- Core_BeforeInstanceStopped = 'before_instance_stopped',
21
- Core_AfterInstanceStopped = 'after_instance_stopped',
23
+ AfterInstanceFound = 'core_after_instance_found',
22
24
 
23
- Core_AfterInstanceFound = 'after_instance_found',
24
-
25
- Core_OnRequestError = 'edge_on_request_error',
26
- Core_Request = 'edge_request',
27
-
28
- /** Firewall actions */
29
- Waf_OnRequestError = 'waf_on_request_error',
30
- Waf_OnAppMiddleware = 'waf_on_app_middleware',
31
-
32
- /** Edge actions */
33
- /** Health actions */
34
- Health_OnMessage = 'health_on_message',
35
- Health_OnError = 'health_on_error',
25
+ RequestError = 'core_edge_on_request_error',
26
+ Request = 'core_edge_request',
36
27
  }
37
28
 
38
29
  const actions: {
39
- [key in PocketHostAction]?: ActionHandler[]
30
+ [key: string]: ActionHandler<any>[]
40
31
  } = {}
41
32
 
42
- export type ActionHandler = (...args: any[]) => Promise<void>
33
+ export type ActionHandler<TContext = {}> = (
34
+ context: TContext extends never ? [] : TContext,
35
+ ) => Promise<void>
43
36
 
44
- export async function registerAction(
45
- action: PocketHostAction,
37
+ async function registerAction(
38
+ action: string,
46
39
  handler: (...args: any[]) => Promise<void>,
47
40
  ) {
48
41
  if (!(action in actions)) actions[action] = []
49
42
  actions[action]!.push(handler)
50
43
  }
51
44
 
52
- export async function action(actionName: PocketHostAction, ...rest: any[]) {
53
- if (DEBUG() && actionName !== PocketHostAction.Core_Log) {
45
+ async function action(actionName: string, context: any) {
46
+ if (DEBUG() && actionName !== CoreActions.Log) {
54
47
  console.log(`a:${actionName}`)
55
48
  }
56
49
  const action = actions[actionName]
57
50
  if (!action) return false
58
- await Promise.all(action.map((handler) => handler(...rest)))
51
+ await Promise.all(action.map((handler) => handler(context)))
59
52
  return true
60
53
  }
54
+
55
+ export function createCustomActionWithContext<TContext extends {}>(
56
+ actionName: CoreActions,
57
+ ) {
58
+ return [
59
+ async (context: TContext) => action(actionName, context),
60
+ async (handler: ActionHandler<TContext>) =>
61
+ registerAction(actionName, handler),
62
+ ] as const
63
+ }
64
+
65
+ export function createCustomAction(actionName: CoreActions) {
66
+ return [
67
+ async () => action(actionName, {}),
68
+ async (handler: ActionHandler) => registerAction(actionName, handler),
69
+ ] as const
70
+ }
71
+
72
+ export const [doRequestErrorAction, onRequestErrorAction] =
73
+ createCustomActionWithContext<{
74
+ err: Error
75
+ }>(CoreActions.RequestError)
76
+
77
+ export const [doAfterServerStartAction, onAfterServerStartAction] =
78
+ createCustomAction(CoreActions.AfterServeStart)
79
+
80
+ export const [doLogAction, onLogAction] = createCustomActionWithContext<{
81
+ currentLevel: LogLevelName
82
+ levelIn: LogLevelName
83
+ args: any[]
84
+ }>(CoreActions.Log)
85
+
86
+ export const [doInstanceLogAction, onInstanceLogAction] =
87
+ createCustomActionWithContext<{
88
+ instance: InstanceFields
89
+ type: 'stdout' | 'stderr'
90
+ data: string
91
+ }>(CoreActions.InstanceLog)
92
+
93
+ export const [doAppMountedAction, onAppMountedAction] =
94
+ createCustomActionWithContext<{ app: Express }>(CoreActions.AppMounted)
95
+
96
+ export const [doIncomingRequestAction, onIncomingRequestAction] =
97
+ createCustomActionWithContext<{ req: Request; res: Response; host: string }>(
98
+ CoreActions.Request,
99
+ )
100
+
101
+ export const [doAfterInstanceFoundAction, onAfterInstanceFoundAction] =
102
+ createCustomActionWithContext<{
103
+ req: Request
104
+ res: Response
105
+ host: string
106
+ instance: InstanceFields
107
+ }>(CoreActions.AfterInstanceFound)
108
+
109
+ export const [doBeforeInstanceStartedAction, onBeforeInstanceStartedAction] =
110
+ createCustomActionWithContext<{ instance: InstanceFields }>(
111
+ CoreActions.BeforeInstanceStarted,
112
+ )
113
+
114
+ export const [doAfterInstanceStartedAction, onAfterInstanceStartedAction] =
115
+ createCustomActionWithContext<{ instance: InstanceFields; url: string }>(
116
+ CoreActions.AfterInstanceStarted,
117
+ )
118
+
119
+ export const [doBeforeInstanceStoppedAction, onBeforeInstanceStoppedAction] =
120
+ createCustomActionWithContext<{ instance: InstanceFields; url: string }>(
121
+ CoreActions.BeforeInstanceStopped,
122
+ )
123
+
124
+ export const [doAfterInstanceStoppedAction, onAfterInstanceStoppedAction] =
125
+ createCustomActionWithContext<{ instance: InstanceFields; url: string }>(
126
+ CoreActions.AfterInstanceStopped,
127
+ )
@@ -1,56 +1,48 @@
1
+ import { Command } from 'commander'
2
+ import { Request, Response } from 'express'
1
3
  import { DEBUG } from '../debug'
4
+ import { InstanceFields, InstanceId } from '../schema'
2
5
 
3
- export enum PocketHostFilter {
4
- /** Core filters */
5
- Core_CliCommands = 'core_cli_commands',
6
- Core_SpawnConfig = 'core_spawn_config',
7
- Core_GetInstanceByRequestInfo = 'core_get_instance_by_request_info',
8
- Core_GetInstanceById = 'core_get_instance_by_id',
9
- Core_ErrorSpawningInstanceMessage = 'core_error_spawning_instance_message',
10
- Core_FailedToLaunchInstanceMessage = 'core_failed_to_launch_instance_message',
11
- Core_AuthenticateRequest = 'core_authenticate_request',
12
- Core_RequestError_Message = 'core_request_error_message',
13
- Core_GetOrProvisionInstanceUrl = 'core_get_or_provision_instance_url',
14
- Core_PocketBaseLaunchHandler = 'core_pocket_base_launch_handler',
15
- Core_NewInstanceRecord = 'core_new_instance_record',
16
-
17
- /** Mothership Filters */
18
- Mothership_MaintenanceMode_Message = 'mothership_maintenance_mode_message',
19
- Mothership_UnverifiedAccountError_Message = 'mothership_unverified_account_error_message',
20
- Mothership_PublicUrl = 'mothership_public_url',
21
-
22
- /** Firewall Filters */
23
- Waf_VHosts = 'waf_vhosts',
24
- Waf_OnError_Message = 'waf_on_error_message',
25
- Waf_AppMount = 'waf_app_mount',
26
-
27
- /** Edge self checks */
28
- EdgeSelfCheck_Checks = 'edge_self_check_checks',
6
+ export enum CoreFilters {
7
+ CliCommands = 'core_cli_commands',
8
+ SpawnConfig = 'core_spawn_config',
9
+ GetInstanceByRequestInfo = 'core_get_instance_by_request_info',
10
+ GetInstanceById = 'core_get_instance_by_id',
11
+ ErrorSpawningInstanceMessage = 'core_error_spawning_instance_message',
12
+ FailedToLaunchInstanceMessage = 'core_failed_to_launch_instance_message',
13
+ AuthenticateRequest = 'core_authenticate_request',
14
+ RequestError_Message = 'core_request_error_message',
15
+ GetOrProvisionInstanceUrl = 'core_get_or_provision_instance_url',
16
+ PocketBaseLaunchHandler = 'core_pocket_base_launch_handler',
17
+ NewInstanceRecord = 'core_new_instance_record',
29
18
  }
30
19
 
31
- export type FilterHandler<T> = (carry: T, ...rest: any[]) => T | Promise<T>
20
+ export type FilterHandler<TCarry, TContext> = (
21
+ carry: TCarry,
22
+ context: TContext,
23
+ ) => TCarry | Promise<TCarry>
32
24
  type FilterEntry<T> = {
33
25
  priority: number
34
- handler: FilterHandler<T>
26
+ handler: FilterHandler<T, any>
35
27
  }
36
28
  const filters: {
37
- [key in PocketHostFilter]?: FilterEntry<any>[]
29
+ [key: string]: FilterEntry<any>[]
38
30
  } = {}
39
31
 
40
- export async function registerFilter<T>(
41
- filter: PocketHostFilter,
42
- handler: FilterHandler<T>,
43
- priority: number = 10,
32
+ async function registerFilter<TCarry, TContext extends {} = {}>(
33
+ filter: string,
34
+ handler: FilterHandler<TCarry, TContext>,
35
+ priority = 10,
44
36
  ) {
45
37
  if (!(filter in filters)) filters[filter] = []
46
38
  filters[filter]!.push({ priority, handler })
47
39
  filters[filter]!.sort((a, b) => a.priority - b.priority)
48
40
  }
49
41
 
50
- export async function filter<TRet, TIn = TRet>(
51
- filterName: PocketHostFilter,
52
- initialValue: TIn,
53
- ...rest: any[]
42
+ async function filter<TCarry, TContext extends {} = {}>(
43
+ filterName: string,
44
+ initialValue: TCarry,
45
+ context: TContext,
54
46
  ) {
55
47
  if (DEBUG()) {
56
48
  console.log(`f:${filterName}`)
@@ -59,7 +51,65 @@ export async function filter<TRet, TIn = TRet>(
59
51
  if (!filter) return initialValue
60
52
  return filter.reduce(
61
53
  (carry, entry) =>
62
- carry.then((carryRes) => entry.handler(carryRes, ...rest)),
54
+ carry.then((carryRes) => entry.handler(carryRes, context)),
63
55
  Promise.resolve(initialValue),
64
- ) as TRet
56
+ ) as TCarry
57
+ }
58
+
59
+ function createCustomFilterWithContext<TCarry, TContext extends {} = {}>(
60
+ filterName: CoreFilters,
61
+ ) {
62
+ return [
63
+ async (initialValue: TCarry, context: TContext) =>
64
+ filter<TCarry, TContext>(filterName, initialValue, context),
65
+ async (handler: FilterHandler<TCarry, TContext>, priority = 10) =>
66
+ registerFilter<TCarry, TContext>(filterName, handler, priority),
67
+ ] as const
68
+ }
69
+
70
+ function createCustomFilter<TCarry>(filterName: string) {
71
+ return [
72
+ async (initialValue: TCarry) =>
73
+ filter<TCarry>(filterName, initialValue, {}),
74
+ async (handler: FilterHandler<TCarry, {}>, priority = 10) =>
75
+ registerFilter<TCarry, {}>(filterName, handler, priority),
76
+ ] as const
65
77
  }
78
+
79
+ export const [
80
+ doGetInstanceByRequestInfoFilter,
81
+ onGetInstanceByRequestInfoFilter,
82
+ ] = createCustomFilterWithContext<
83
+ InstanceFields | undefined,
84
+ { req: Request; res: Response; host: string; subdomain: string }
85
+ >(CoreFilters.GetInstanceByRequestInfo)
86
+
87
+ export const [doGetInstanceByIdFilter, onGetInstanceByIdFilter] =
88
+ createCustomFilterWithContext<
89
+ InstanceFields | undefined,
90
+ { instanceId: InstanceId }
91
+ >(CoreFilters.GetInstanceById)
92
+
93
+ export const [doCliCommandsFilter, onCliCommandsFilter] = createCustomFilter<
94
+ Command[]
95
+ >(CoreFilters.CliCommands)
96
+
97
+ export const [doNewInstanceRecordFilter, onNewInstanceRecordFilter] =
98
+ createCustomFilter<InstanceFields>(CoreFilters.NewInstanceRecord)
99
+
100
+ export const [doRequestErrorMessageFilter, onRequestErrorMessageFilter] =
101
+ createCustomFilter<string>(CoreFilters.RequestError_Message)
102
+
103
+ export const [
104
+ doGetOrProvisionInstanceUrlFilter,
105
+ onGetOrProvisionInstanceUrlFilter,
106
+ ] = createCustomFilterWithContext<
107
+ string,
108
+ {
109
+ req: Request
110
+ res: Response
111
+ host: string
112
+ subdomain: string
113
+ instance: InstanceFields
114
+ }
115
+ >(CoreFilters.GetOrProvisionInstanceUrl)
@@ -1,16 +1,10 @@
1
1
  import { isString } from '@s-libs/micro-dash'
2
- import { registerAction } from './action'
3
- import { registerFilter } from './filter'
4
2
  export * from './action'
5
3
  export * from './filter'
6
- export * from './helpers'
7
4
 
8
5
  export type PocketHostPlugin = (api: PocketHostPluginApi) => Promise<void>
9
6
 
10
- export type PocketHostPluginApi = {
11
- registerAction: typeof registerAction
12
- registerFilter: typeof registerFilter
13
- }
7
+ export type PocketHostPluginApi = {}
14
8
 
15
9
  export const loadPlugins = async (plugins: (string | PocketHostPlugin)[]) => {
16
10
  await Promise.all(
@@ -20,7 +14,7 @@ export const loadPlugins = async (plugins: (string | PocketHostPlugin)[]) => {
20
14
  const plugin = await import(pluginPathOrModule)
21
15
  return plugin.default
22
16
  })()
23
- await plugin({ registerAction, registerFilter })
17
+ await plugin({})
24
18
  }),
25
19
  )
26
20
  return plugins
@@ -1,4 +1,4 @@
1
- import { BaseFields, RecordId, Subdomain, UserFields, UserId } from '.'
1
+ import { doNewInstanceRecordFilter } from '../plugin'
2
2
 
3
3
  export type VersionId = string
4
4
 
@@ -13,33 +13,24 @@ export enum InstanceStatus {
13
13
  Failed = 'failed',
14
14
  }
15
15
 
16
+ export type InstanceId = string
16
17
  export type InstanceSecretKey = string
17
18
  export type InstanceSecretValue = string
18
19
  export type InstanceSecretCollection = {
19
20
  [name: InstanceSecretKey]: InstanceSecretValue
20
21
  }
21
22
 
22
- export type InstanceFields = BaseFields & {
23
- subdomain: Subdomain
24
- uid: UserId
25
- status: InstanceStatus
23
+ export type InstanceFields = {
24
+ subdomain: string
26
25
  version: VersionId
27
- secrets: InstanceSecretCollection | null
28
- maintenance: boolean
29
- suspension: string
30
- syncAdmin: boolean
31
- cname: string
32
- dev: boolean
33
- cname_active: boolean
34
- notifyMaintenanceMode: boolean
26
+ secrets: InstanceSecretCollection
27
+ dev: boolean // Should instance run in --dev mode
35
28
  }
36
29
 
37
- export type WithUser = {
38
- expand: { uid: UserFields }
39
- }
40
-
41
- export type InstanceFields_WithUser = InstanceFields & WithUser
42
-
43
- export type InstanceFields_Create = Omit<InstanceFields, keyof BaseFields>
44
-
45
- export type InstanceRecordsById = { [_: RecordId]: InstanceFields }
30
+ export const mkInstance = (subdomain: string) =>
31
+ doNewInstanceRecordFilter({
32
+ subdomain,
33
+ version: '*',
34
+ secrets: {},
35
+ dev: false,
36
+ })
@@ -1,6 +1,3 @@
1
1
  export * from './Instance'
2
2
  export * from './InstanceLog'
3
- export * from './Rest'
4
- export * from './User'
5
- export * from './types'
6
3
  export * from './util'
package/src/constants.ts CHANGED
@@ -32,6 +32,7 @@ export const settings = Settings({
32
32
  PH_PLUGINS: mkCsvString([
33
33
  `@pockethost/plugin-console-logger`,
34
34
  `@pockethost/plugin-launcher-spawn`,
35
+ `@pockethost/plugin-auto-admin`,
35
36
  ]),
36
37
 
37
38
  PH_HOME: mkPath(_PH_HOME),
package/src/server.ts CHANGED
@@ -2,36 +2,36 @@ import express, { ErrorRequestHandler } from 'express'
2
2
  import 'express-async-errors'
3
3
  import { default as httpProxy } from 'http-proxy'
4
4
  import {
5
- InstanceFields,
5
+ APEX_DOMAIN,
6
6
  LoggerService,
7
7
  PORT,
8
- PocketHostAction,
9
- PocketHostFilter,
10
- action,
11
8
  asyncExitHook,
12
- filter,
13
- registerAction,
14
- registerFilter,
9
+ doAfterInstanceFoundAction,
10
+ doAfterServerStartAction,
11
+ doAppMountedAction,
12
+ doGetInstanceByRequestInfoFilter,
13
+ doGetOrProvisionInstanceUrlFilter,
14
+ doIncomingRequestAction,
15
+ doRequestErrorAction,
16
+ doRequestErrorMessageFilter,
17
+ onInstanceLogAction,
15
18
  } from '../core'
16
19
 
17
20
  export const serve = async () => {
18
21
  const _proxyLogger = LoggerService().create('ProxyService')
19
22
  const { dbg, error, info, trace, warn } = _proxyLogger
20
23
 
21
- registerAction(
22
- PocketHostAction.Core_InstanceLog,
23
- async ({ instance, type, data }) => {
24
- dbg(data.toString())
25
- },
26
- )
27
-
28
- registerFilter(
29
- PocketHostFilter.Waf_VHosts,
30
- async (routes: { [_: string]: string }) => {
31
- return { ...routes, '*': `http://localhost:${PORT()}` }
32
- },
33
- 99,
34
- )
24
+ onInstanceLogAction(async ({ instance, type, data }) => {
25
+ dbg(data.toString())
26
+ })
27
+
28
+ // registerFilter(
29
+ // CoreFilters.Waf_VHosts,
30
+ // async (routes: { [_: string]: string }) => {
31
+ // return { ...routes, '*': `http://localhost:${PORT()}` }
32
+ // },
33
+ // 99,
34
+ // )
35
35
 
36
36
  const proxy = httpProxy.createProxyServer({})
37
37
  proxy.on('error', (err, req, res, target) => {
@@ -47,59 +47,55 @@ export const serve = async () => {
47
47
  })
48
48
 
49
49
  const errorHandler: ErrorRequestHandler = async (err, req, res, next) => {
50
- action(PocketHostAction.Core_OnRequestError, { err })
51
- res
52
- .status(500)
53
- .send(
54
- await filter(
55
- PocketHostFilter.Core_RequestError_Message,
56
- err.toString(),
57
- ),
58
- )
50
+ doRequestErrorAction({ err })
51
+ res.status(500).send(await doRequestErrorMessageFilter(err.toString()))
59
52
  }
60
53
  app.use(errorHandler)
61
54
 
62
- action(PocketHostAction.Core_OnAppMount, app)
55
+ doAppMountedAction({ app })
63
56
 
64
57
  app.all(`*`, async (req, res, next) => {
65
58
  const method = req.method
66
- const fullUrl = req.protocol + '://' + req.get('host') + req.originalUrl
67
- const host = req.headers.host
59
+ const fullUrl = new URL(
60
+ req.protocol + '://' + req.get('host') + req.originalUrl,
61
+ )
62
+ const host = fullUrl.hostname
68
63
  if (!host) {
69
64
  throw new Error(`Host not found`)
70
65
  }
66
+ const subdomain = host.slice(0, -`.${APEX_DOMAIN()}`.length)
71
67
 
72
- dbg(`${method} ${fullUrl} -> ${`http://localhost:${PORT()}`}`)
68
+ dbg(
69
+ `${method} ${fullUrl} (${subdomain}) -> ${`http://localhost:${PORT()}`}`,
70
+ )
73
71
 
74
- await action(PocketHostAction.Core_Request, { req, res, host })
72
+ await doIncomingRequestAction({ req, res, host })
75
73
 
76
- const instance = await filter<undefined | InstanceFields>(
77
- PocketHostFilter.Core_GetInstanceByRequestInfo,
78
- undefined,
79
- { req, res, host },
80
- )
74
+ const instance = await doGetInstanceByRequestInfoFilter(undefined, {
75
+ req,
76
+ res,
77
+ host,
78
+ subdomain,
79
+ })
81
80
  if (!instance) {
82
81
  res.status(404).end(`${host} not found`)
83
82
  return
84
83
  }
85
84
 
86
- await action(PocketHostAction.Core_AfterInstanceFound, {
85
+ await doAfterInstanceFoundAction({
87
86
  instance,
88
87
  req,
89
88
  res,
90
89
  host,
91
90
  })
92
91
 
93
- const url = await filter<string>(
94
- PocketHostFilter.Core_GetOrProvisionInstanceUrl,
95
- '',
96
- {
97
- req,
98
- res,
99
- instance,
100
- host,
101
- },
102
- )
92
+ const url = await doGetOrProvisionInstanceUrlFilter('', {
93
+ req,
94
+ res,
95
+ instance,
96
+ host,
97
+ subdomain,
98
+ })
103
99
 
104
100
  dbg(`Forwarding request for ${req.url} to instance ${url}`)
105
101
 
@@ -110,7 +106,7 @@ export const serve = async () => {
110
106
 
111
107
  app.listen(PORT(), () => {
112
108
  info(`pockethost listening on port ${PORT()}`)
113
- action(PocketHostAction.Core_AfterServeStart)
109
+ doAfterServerStartAction()
114
110
  })
115
111
 
116
112
  asyncExitHook(async () => {
@@ -1,17 +0,0 @@
1
- import { InstanceFields } from '../schema'
2
- import { ActionHandler, PocketHostAction, registerAction } from './action'
3
- import { PocketHostFilter, filter } from './filter'
4
-
5
- export async function getInstanceById(instanceId: string) {
6
- return await filter<InstanceFields | undefined>(
7
- PocketHostFilter.Core_GetInstanceById,
8
- undefined,
9
- { instanceId },
10
- )
11
- }
12
-
13
- export const onAppMount = (cb: ActionHandler) =>
14
- registerAction(PocketHostAction.Core_OnAppMount, cb)
15
-
16
- export const onInit = (cb: ActionHandler) =>
17
- registerAction(PocketHostAction.Core_Init, cb)
@@ -1,69 +0,0 @@
1
- import Ajv, { JSONSchemaType } from 'ajv'
2
- import PocketBase, { ClientResponseError } from 'pocketbase'
3
- import type { JsonObject } from 'type-fest'
4
- import { LoggerService, RestCommands, RestMethods } from '..'
5
-
6
- export type RestHelperConfig = {
7
- client: PocketBase
8
- }
9
-
10
- export type RestHelper = ReturnType<typeof createRestHelper>
11
-
12
- export const createRestHelper = (config: RestHelperConfig) => {
13
- const _logger = LoggerService().create(`RestHelper`)
14
- const { client } = config
15
-
16
- const mkRest = <TPayload extends JsonObject, TResult extends JsonObject>(
17
- cmd: RestCommands,
18
- method: RestMethods,
19
- schema: JSONSchemaType<TPayload>,
20
- ) => {
21
- const validator = new Ajv().compile(schema)
22
- return async (payload: TPayload): Promise<TResult> => {
23
- const _restCallLogger = _logger.create(cmd)
24
- const { dbg, error } = _restCallLogger
25
-
26
- dbg(`Executing REST call`)
27
- if (!validator(payload)) {
28
- throw new Error(`Invalid REST payload: ${validator.errors}`)
29
- }
30
- const _payload = { ...payload }
31
-
32
- const url = `/api/${cmd}${
33
- method === RestMethods.Post ? '' : '/:id'
34
- }`.replace(/:([a-zA-Z]+)/g, (_, key) => {
35
- if (!(key in _payload)) {
36
- throw new Error(`Payload must include '${key}`)
37
- }
38
- const value = _payload[key]!
39
- delete _payload[key]
40
- return encodeURIComponent(value.toString())
41
- })
42
-
43
- const options: any = {
44
- method: method,
45
- }
46
-
47
- if (method !== RestMethods.Get) {
48
- options.body = _payload
49
- }
50
-
51
- dbg({ url, options })
52
-
53
- try {
54
- const res = await client.send(url, options)
55
- dbg(res)
56
- return res
57
- } catch (e) {
58
- if (e instanceof ClientResponseError) {
59
- error(`REST error: ${e.originalError}`)
60
- throw e.originalError
61
- }
62
- error(`REST error: ${e}`)
63
- throw e
64
- }
65
- }
66
- }
67
-
68
- return { mkRest }
69
- }
@@ -1 +0,0 @@
1
- export * from './RestHelper'
@@ -1,20 +0,0 @@
1
- import { JSONSchemaType } from 'ajv'
2
- import { InstanceFields, Subdomain } from '..'
3
-
4
- export type CreateInstancePayload = {
5
- subdomain: Subdomain
6
- }
7
-
8
- export type CreateInstanceResult = {
9
- instance: InstanceFields
10
- }
11
-
12
- export const CreateInstancePayloadSchema: JSONSchemaType<CreateInstancePayload> =
13
- {
14
- type: 'object',
15
- properties: {
16
- subdomain: { type: 'string' },
17
- },
18
- required: ['subdomain'],
19
- additionalProperties: false,
20
- }
@@ -1,21 +0,0 @@
1
- import { JSONSchemaType } from 'ajv'
2
- import { InstanceId } from '..'
3
-
4
- export type DeleteInstancePayload = {
5
- id: InstanceId
6
- }
7
-
8
- export type DeleteInstanceResult = {
9
- status: 'ok' | 'error'
10
- message?: string
11
- }
12
-
13
- export const DeleteInstancePayloadSchema: JSONSchemaType<DeleteInstancePayload> =
14
- {
15
- type: 'object',
16
- properties: {
17
- id: { type: 'string' },
18
- },
19
- required: ['id'],
20
- additionalProperties: false,
21
- }
@@ -1,22 +0,0 @@
1
- import { JSONSchemaType } from 'ajv'
2
- import { UserId } from '..'
3
-
4
- export type GetUserTokenPayload = {
5
- id: UserId
6
- }
7
-
8
- export type GetUserTokenResult = {
9
- id: string
10
- email: string
11
- tokenKey: string
12
- passwordHash: string
13
- }
14
-
15
- export const GetUserTokenPayloadSchema: JSONSchemaType<GetUserTokenPayload> = {
16
- type: 'object',
17
- properties: {
18
- id: { type: 'string' },
19
- },
20
- required: ['id'],
21
- additionalProperties: false,
22
- }
@@ -1,61 +0,0 @@
1
- import { JSONSchemaType } from 'ajv'
2
- import { InstanceFields, InstanceId } from '..'
3
-
4
- export type UpdateInstancePayload = {
5
- id: InstanceId
6
- fields: Partial<
7
- Pick<
8
- InstanceFields,
9
- | 'maintenance'
10
- | 'secrets'
11
- | 'subdomain'
12
- | 'syncAdmin'
13
- | 'version'
14
- | 'dev'
15
- | 'cname'
16
- | 'notifyMaintenanceMode'
17
- >
18
- >
19
- }
20
-
21
- export const SECRET_KEY_REGEX = /^[A-Z][A-Z0-9_]*$/
22
-
23
- export type UpdateInstanceResult = {
24
- status: 'ok' | 'error'
25
- message?: string
26
- }
27
-
28
- export const UpdateInstancePayloadSchema: JSONSchemaType<UpdateInstancePayload> =
29
- {
30
- type: 'object',
31
- properties: {
32
- id: { type: 'string' },
33
- fields: {
34
- type: 'object',
35
- properties: {
36
- syncAdmin: { type: 'boolean', nullable: true },
37
- subdomain: { type: 'string', nullable: true },
38
- maintenance: { type: 'boolean', nullable: true },
39
- version: {
40
- type: 'string',
41
- nullable: true,
42
- },
43
- secrets: {
44
- type: 'object',
45
- nullable: true,
46
- patternProperties: {
47
- [SECRET_KEY_REGEX.source]: {
48
- anyOf: [{ type: 'string' }],
49
- },
50
- },
51
- required: [],
52
- },
53
- dev: { type: 'boolean', nullable: true },
54
- cname: { type: 'string', nullable: true },
55
- notifyMaintenanceMode: { type: 'boolean', nullable: true },
56
- },
57
- },
58
- },
59
- required: ['id', 'fields'],
60
- additionalProperties: false,
61
- }
@@ -1,23 +0,0 @@
1
- import Ajv from 'ajv'
2
- import { JsonObject } from 'type-fest'
3
-
4
- export enum RestMethods {
5
- Get = 'GET',
6
- Post = 'POST',
7
- Put = 'PUT',
8
- Delete = 'DELETE',
9
- }
10
-
11
- export enum RestCommands {
12
- Instance = 'instance',
13
- UserToken = 'userToken',
14
- }
15
-
16
- export type RestPayloadBase = JsonObject
17
-
18
- export const ajv = new Ajv()
19
-
20
- export * from './CreateInstance'
21
- export * from './DeleteInstance'
22
- export * from './GetUserTokenInfo'
23
- export * from './UpdateInstance'
@@ -1,24 +0,0 @@
1
- import { BaseFields } from './types'
2
-
3
- export enum SubscriptionType {
4
- Legacy = 'legacy',
5
- Free = 'free',
6
- Premium = 'premium',
7
- Lifetime = 'lifetime',
8
- }
9
-
10
- export const PLAN_NAMES = {
11
- [SubscriptionType.Legacy]: 'Legacy',
12
- [SubscriptionType.Free]: 'Hacker',
13
- [SubscriptionType.Premium]: `Pro`,
14
- [SubscriptionType.Lifetime]: `Founder's Edition`,
15
- }
16
-
17
- export type UserFields = BaseFields & {
18
- email: string
19
- verified: boolean
20
- isLegacy: boolean
21
- isFounder: boolean
22
- subscription: SubscriptionType
23
- notifyMaintenanceMode: boolean
24
- }
@@ -1,18 +0,0 @@
1
- export type RecordId = string
2
- export type UserId = RecordId
3
- export type InstanceId = RecordId
4
- export type Semver = string
5
- export type InternalInstanceId = RecordId
6
- export type Subdomain = string
7
- export type Port = number
8
- export type IsoDate = string
9
- export type ProcessId = number
10
- export type Username = string
11
- export type Password = string
12
- export type CollectionName = string
13
- export type Seconds = number
14
- export type BaseFields = {
15
- id: RecordId
16
- created: IsoDate
17
- updated: IsoDate
18
- }