@pikku/core 0.6.13 → 0.6.14

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.
Files changed (79) hide show
  1. package/CHANGELOG.md +7 -0
  2. package/dist/channel/channel-handler.d.ts +2 -2
  3. package/dist/channel/channel-handler.js +4 -4
  4. package/dist/channel/channel-runner.d.ts +5 -4
  5. package/dist/channel/channel-runner.js +8 -14
  6. package/dist/channel/channel.types.d.ts +11 -18
  7. package/dist/channel/local/local-channel-handler.d.ts +1 -3
  8. package/dist/channel/local/local-channel-handler.js +0 -3
  9. package/dist/channel/local/local-channel-runner.js +49 -33
  10. package/dist/channel/pikku-abstract-channel-handler.d.ts +4 -7
  11. package/dist/channel/pikku-abstract-channel-handler.js +1 -5
  12. package/dist/channel/serverless/serverless-channel-runner.js +45 -38
  13. package/dist/handle-error.d.ts +14 -0
  14. package/dist/handle-error.js +53 -0
  15. package/dist/http/http-route-runner.d.ts +34 -11
  16. package/dist/http/http-route-runner.js +177 -106
  17. package/dist/http/http-routes.types.d.ts +15 -9
  18. package/dist/http/index.d.ts +0 -1
  19. package/dist/http/index.js +0 -1
  20. package/dist/http/pikku-http-abstract-request.d.ts +4 -1
  21. package/dist/http/pikku-http-abstract-request.js +7 -0
  22. package/dist/index.d.ts +1 -0
  23. package/dist/index.js +1 -0
  24. package/dist/middleware/auth-api-key.d.ts +9 -0
  25. package/dist/middleware/auth-api-key.js +23 -0
  26. package/dist/middleware/auth-cookie-middleware.d.ts +11 -0
  27. package/dist/middleware/auth-cookie-middleware.js +36 -0
  28. package/dist/middleware/auth-jwt-middleware.d.ts +10 -0
  29. package/dist/middleware/auth-jwt-middleware.js +32 -0
  30. package/dist/middleware/auth-query-middleware.d.ts +10 -0
  31. package/dist/middleware/auth-query-middleware.js +21 -0
  32. package/dist/middleware/index.d.ts +4 -0
  33. package/dist/middleware/index.js +4 -0
  34. package/dist/middleware-runner.d.ts +22 -0
  35. package/dist/middleware-runner.js +28 -0
  36. package/dist/scheduler/scheduler.types.d.ts +2 -0
  37. package/dist/schema.d.ts +1 -1
  38. package/dist/schema.js +3 -6
  39. package/dist/services/user-session-service.d.ts +20 -3
  40. package/dist/services/user-session-service.js +35 -1
  41. package/dist/types/core.types.d.ts +11 -15
  42. package/dist/types/functions.types.d.ts +4 -4
  43. package/lcov.info +1574 -1731
  44. package/package.json +1 -2
  45. package/src/channel/channel-handler.ts +6 -11
  46. package/src/channel/channel-runner.ts +13 -32
  47. package/src/channel/channel.types.ts +13 -39
  48. package/src/channel/local/local-channel-handler.ts +1 -7
  49. package/src/channel/local/local-channel-runner.test.ts +2 -5
  50. package/src/channel/local/local-channel-runner.ts +72 -54
  51. package/src/channel/pikku-abstract-channel-handler.test.ts +3 -28
  52. package/src/channel/pikku-abstract-channel-handler.ts +3 -9
  53. package/src/channel/serverless/serverless-channel-runner.ts +68 -56
  54. package/src/handle-error.ts +67 -0
  55. package/src/http/http-route-runner.test.ts +12 -45
  56. package/src/http/http-route-runner.ts +256 -182
  57. package/src/http/http-routes.types.ts +15 -10
  58. package/src/http/index.ts +0 -2
  59. package/src/http/pikku-http-abstract-request.ts +8 -1
  60. package/src/index.ts +1 -0
  61. package/src/middleware/auth-api-key.ts +32 -0
  62. package/src/middleware/auth-cookie-middleware.ts +54 -0
  63. package/src/middleware/auth-jwt-middleware.ts +47 -0
  64. package/src/middleware/auth-query-middleware.ts +33 -0
  65. package/src/middleware/index.ts +4 -0
  66. package/src/middleware-runner.ts +43 -0
  67. package/src/scheduler/scheduler.types.ts +2 -0
  68. package/src/schema.ts +4 -8
  69. package/src/services/user-session-service.ts +45 -3
  70. package/src/types/core.types.ts +19 -18
  71. package/src/types/functions.types.ts +11 -7
  72. package/tsconfig.tsbuildinfo +1 -1
  73. package/dist/http/http-session-service.d.ts +0 -15
  74. package/dist/http/http-session-service.js +0 -1
  75. package/dist/http/pikku-http-session-service.d.ts +0 -45
  76. package/dist/http/pikku-http-session-service.js +0 -92
  77. package/src/http/http-session-service.ts +0 -19
  78. package/src/http/pikku-http-session-service.test.ts +0 -106
  79. package/src/http/pikku-http-session-service.ts +0 -135
package/CHANGELOG.md CHANGED
@@ -1,5 +1,12 @@
1
1
  ## 0.6
2
2
 
3
+ ## 0.6.14
4
+
5
+ ### Patch Changes
6
+
7
+ - c1d8381: feat: adding filtering by tags to minimize produced payload
8
+ - ee5c874: feat: moving towards using middleware for http and channels
9
+
3
10
  ## 0.6.13
4
11
 
5
12
  ### Patch Changes
@@ -1,3 +1,3 @@
1
- import { CoreServices, CoreUserSession } from '../types/core.types.js';
1
+ import { CoreServices } from '../types/core.types.js';
2
2
  import { CoreAPIChannel, PikkuChannelHandler } from './channel.types.js';
3
- export declare const processMessageHandlers: (services: CoreServices, channelConfig: CoreAPIChannel<any, any>, channelHandler: PikkuChannelHandler<CoreUserSession, unknown>) => (rawData: any) => Promise<unknown>;
3
+ export declare const processMessageHandlers: (services: CoreServices, channelConfig: CoreAPIChannel<any, any>, channelHandler: PikkuChannelHandler) => (rawData: any) => Promise<unknown>;
@@ -17,20 +17,20 @@ const validateSchema = (logger, data, channelName, routingProperty, routerValue)
17
17
  }
18
18
  }
19
19
  };
20
- const validateAuth = (requiresSession, channelHandler, onMessage) => {
20
+ const validateAuth = (requiresSession, userSession, onMessage) => {
21
21
  const auth = typeof onMessage === 'function'
22
22
  ? requiresSession
23
23
  : onMessage.auth === undefined
24
24
  ? requiresSession
25
25
  : onMessage.auth;
26
- if (auth && !channelHandler.getChannel().userSession) {
26
+ if (auth && !userSession) {
27
27
  return false;
28
28
  }
29
29
  return true;
30
30
  };
31
- const validatePermissions = async (services, channelHandler, onMessage, data) => {
31
+ const validatePermissions = async (services, userSession, onMessage, data) => {
32
32
  const permissions = typeof onMessage === 'function' ? {} : onMessage.permissions;
33
- return await verifyPermissions(permissions, services, data, channelHandler.getChannel().userSession);
33
+ return await verifyPermissions(permissions, services, data, userSession);
34
34
  };
35
35
  const runFunction = async (services, channelHandler, onMessage, data) => {
36
36
  const func = typeof onMessage === 'function' ? onMessage : onMessage.func;
@@ -1,4 +1,4 @@
1
- import { CoreUserSession } from '../types/core.types.js';
1
+ import { UserSessionService } from '../services/user-session-service.js';
2
2
  import { CoreAPIChannel, ChannelsMeta, CoreAPIChannels, RunChannelOptions, RunChannelParams } from './channel.types.js';
3
3
  /**
4
4
  * Returns all the registered routes and associated metadata.
@@ -16,11 +16,12 @@ export declare const setChannelsMeta: (_channelsMeta: ChannelsMeta) => void;
16
16
  export declare const getMatchingChannelConfig: (request: string) => {
17
17
  matchedPath: import("path-to-regexp").MatchResult<Partial<Record<string, string | string[]>>>;
18
18
  params: Partial<Record<string, string | string[]>>;
19
- channelConfig: CoreAPIChannel<any, string, import("./channel.types.js").CoreChannelConnection<any, unknown, import("../types/core.types.js").CoreServices, CoreUserSession>, import("./channel.types.js").CoreChannelDisconnection<any, import("../types/core.types.js").CoreServices, CoreUserSession>, import("./channel.types.js").CoreChannelMessage<unknown, unknown, unknown, import("../types/core.types.js").CoreServices, CoreUserSession>, import("./channel.types.js").CoreChannelMessage<unknown, unknown, unknown, import("../types/core.types.js").CoreServices, CoreUserSession>, import("../index.js").CoreAPIPermission<any>>;
19
+ channelConfig: CoreAPIChannel<any, string, import("./channel.types.js").CoreChannelConnection<any, unknown, import("../index.js").CoreServices>, import("./channel.types.js").CoreChannelDisconnection<any, import("../index.js").CoreServices>, import("./channel.types.js").CoreChannelMessage<unknown, unknown, unknown, import("../index.js").CoreServices>, import("./channel.types.js").CoreChannelMessage<unknown, unknown, unknown, import("../index.js").CoreServices>, import("../index.js").CoreAPIPermission<any>>;
20
20
  schemaName: string | null | undefined;
21
21
  } | null;
22
- export declare const openChannel: ({ route, singletonServices, skipUserSession, coerceToArray, http, }: Pick<CoreAPIChannel<unknown, string>, "route"> & Omit<RunChannelParams<unknown>, "response" | "request"> & RunChannelOptions) => Promise<{
23
- userSession?: CoreUserSession;
22
+ export declare const openChannel: ({ route, singletonServices, coerceToArray, http, userSessionService, }: Pick<CoreAPIChannel<unknown, string>, "route"> & Omit<RunChannelParams<unknown>, "response" | "request"> & {
23
+ userSessionService?: UserSessionService<any>;
24
+ } & RunChannelOptions) => Promise<{
24
25
  openingData: unknown;
25
26
  channelConfig: CoreAPIChannel<unknown, any>;
26
27
  }>;
@@ -1,6 +1,5 @@
1
1
  import { NotFoundError } from '../errors/errors.js';
2
- import { loadUserSession } from '../http/http-route-runner.js';
3
- import { validateAndCoerce } from '../schema.js';
2
+ import { coerceQueryStringToArray, validateSchema } from '../schema.js';
4
3
  import { match } from 'path-to-regexp';
5
4
  if (!globalThis.pikku?.channels) {
6
5
  globalThis.pikku = globalThis.pikku || {};
@@ -59,28 +58,23 @@ export const getMatchingChannelConfig = (request) => {
59
58
  }
60
59
  return null;
61
60
  };
62
- export const openChannel = async ({ route, singletonServices, skipUserSession = false, coerceToArray = false, http, }) => {
61
+ export const openChannel = async ({ route, singletonServices, coerceToArray = false, http, userSessionService, }) => {
63
62
  const matchingChannel = getMatchingChannelConfig(route);
64
63
  if (!matchingChannel) {
65
64
  singletonServices.logger.info(`Channel not found: ${route}`);
66
65
  throw new NotFoundError(`Channel not found: ${route}`);
67
66
  }
68
- const { matchedPath, params, channelConfig, schemaName } = matchingChannel;
67
+ const { params, channelConfig, schemaName } = matchingChannel;
69
68
  const requiresSession = channelConfig.auth !== false;
70
69
  http?.request?.setParams(params);
71
70
  singletonServices.logger.info(`Matched channel: ${channelConfig.name} | route: ${channelConfig.route} | auth: ${requiresSession.toString()}`);
72
- const userSession = await loadUserSession(skipUserSession,
73
- // We may require a session, but we don't actually need it
74
- // on connect since channels can authenticate later given
75
- // how websocket sessions work (cookie or queryParam based)
76
- false, http, matchedPath, channelConfig, singletonServices.logger, singletonServices.httpSessionService);
77
- if (singletonServices.enforceChannelAccess) {
78
- await singletonServices.enforceChannelAccess(matchingChannel.channelConfig, userSession);
79
- }
80
71
  let openingData;
81
72
  if (http?.request) {
82
73
  openingData = await http.request.getData();
83
- validateAndCoerce(singletonServices.logger, singletonServices.schemaService, schemaName, openingData, coerceToArray);
74
+ if (coerceToArray && schemaName) {
75
+ coerceQueryStringToArray(schemaName, openingData);
76
+ }
77
+ validateSchema(singletonServices.logger, singletonServices.schemaService, schemaName, openingData);
84
78
  }
85
- return { userSession, openingData, channelConfig };
79
+ return { openingData, channelConfig };
86
80
  };
@@ -2,7 +2,7 @@ import { PikkuError } from '../errors/error-handler.js';
2
2
  import { HTTPFunctionMetaInputTypes, PikkuHTTP } from '../http/http-routes.types.js';
3
3
  import { PikkuHTTPAbstractRequest } from '../http/pikku-http-abstract-request.js';
4
4
  import { PikkuHTTPAbstractResponse } from '../http/pikku-http-abstract-response.js';
5
- import { APIDocs, CoreServices, CoreSingletonServices, CoreUserSession, CreateSessionServices } from '../types/core.types.js';
5
+ import { APIDocs, CoreServices, CoreSingletonServices, CreateSessionServices, PikkuMiddleware } from '../types/core.types.js';
6
6
  import { CoreAPIPermission } from '../types/functions.types.js';
7
7
  import { PikkuRequest } from '../pikku-request.js';
8
8
  import { PikkuResponse } from '../pikku-response.js';
@@ -41,10 +41,11 @@ export interface ChannelMeta {
41
41
  outputs: string[] | null;
42
42
  }>>;
43
43
  docs?: APIDocs;
44
+ tags?: string[];
44
45
  }
45
46
  export type ChannelsMeta = ChannelMeta[];
46
- export type CoreChannelConnection<ChannelData, Out = unknown, Services extends CoreServices = CoreServices, Session extends CoreUserSession = CoreUserSession> = (services: Services, channel: PikkuChannel<Session, ChannelData, Out>) => Promise<void>;
47
- export type CoreChannelDisconnection<ChannelData, Services extends CoreServices = CoreServices, Session extends CoreUserSession = CoreUserSession> = (services: Services, channel: PikkuChannel<Session, ChannelData, never>) => Promise<void>;
47
+ export type CoreChannelConnection<ChannelData, Out = unknown, Services extends CoreServices = CoreServices> = (services: Services, channel: PikkuChannel<ChannelData, Out>) => Promise<void>;
48
+ export type CoreChannelDisconnection<ChannelData, Services extends CoreServices = CoreServices> = (services: Services, channel: PikkuChannel<ChannelData, never>) => Promise<void>;
48
49
  /**
49
50
  * Represents a core channel function that performs an operation using core services and a user session.
50
51
  *
@@ -52,7 +53,7 @@ export type CoreChannelDisconnection<ChannelData, Services extends CoreServices
52
53
  * @template Services - The services type, defaults to `CoreServices`.
53
54
  * @template Session - The session type, defaults to `CoreUserSession`.
54
55
  */
55
- export type CoreChannelMessage<In, Out, ChannelData, Services extends CoreServices = CoreServices, Session extends CoreUserSession = CoreUserSession> = (services: Services, channel: PikkuChannel<Session, ChannelData, Out>, data: In) => Promise<void | Out>;
56
+ export type CoreChannelMessage<In, Out, ChannelData, Services extends CoreServices = CoreServices> = (services: Services, channel: PikkuChannel<ChannelData, Out>, data: In) => Promise<void | Out>;
56
57
  export type CoreAPIChannelMessage<ChannelFunctionMessage = CoreChannelMessage<unknown, unknown, unknown>> = {
57
58
  func: ChannelFunctionMessage;
58
59
  route: string;
@@ -72,6 +73,7 @@ export type CoreAPIChannel<ChannelData, Channel extends string, ChannelFunctionC
72
73
  permissions?: Record<string, APIPermission[] | APIPermission>;
73
74
  auth?: boolean;
74
75
  }>>;
76
+ middleware?: PikkuMiddleware[];
75
77
  permissions?: Record<string, APIPermission[] | APIPermission>;
76
78
  auth?: boolean;
77
79
  docs?: Partial<{
@@ -80,27 +82,18 @@ export type CoreAPIChannel<ChannelData, Channel extends string, ChannelFunctionC
80
82
  errors: Array<typeof PikkuError>;
81
83
  tags: string[];
82
84
  }>;
85
+ tags?: string[];
83
86
  };
84
87
  export type CoreAPIChannels = CoreAPIChannel<any, string>[];
85
- export interface PikkuChannel<UserSession, OpeningData, Out> {
88
+ export interface PikkuChannel<OpeningData, Out> {
86
89
  channelId: string;
87
- userSession?: UserSession;
88
- setUserSession: (userSession: UserSession) => Promise<void> | void;
89
90
  openingData: OpeningData;
90
91
  send: (data: Out, isBinary?: boolean) => Promise<void> | void;
91
92
  close: () => Promise<void> | void;
92
93
  state: 'initial' | 'open' | 'closed';
93
94
  }
94
- export interface PikkuChannelHandler<UserSession extends CoreUserSession = CoreUserSession, OpeningData = unknown, Out = unknown> {
95
- setUserSession(session: UserSession): Promise<void> | void;
95
+ export interface PikkuChannelHandler<OpeningData = unknown, Out = unknown> {
96
96
  send(message: Out, isBinary?: boolean): Promise<void> | void;
97
- getChannel(): PikkuChannel<UserSession, OpeningData, Out>;
97
+ getChannel(): PikkuChannel<OpeningData, Out>;
98
98
  }
99
- export type PikkuChannelHandlerFactory<OpeningData = unknown, UserSession extends CoreUserSession = CoreUserSession, Out = unknown> = (channelId: string, channelName: string, openingData: OpeningData, userSession: UserSession | undefined) => PikkuChannelHandler<UserSession, OpeningData, Out>;
100
- /**
101
- * Enfore access to a channel.
102
- * @param route - The channel to verify access for.
103
- * @param session - The user session.
104
- * @returns A promise that resolves if access is granted.
105
- */
106
- export type enforceChannelAccess = (channel: CoreAPIChannel<unknown, any>, session?: CoreUserSession) => Promise<void> | void;
99
+ export type PikkuChannelHandlerFactory<OpeningData = unknown, Out = unknown> = (channelId: string, channelName: string, openingData: OpeningData) => PikkuChannelHandler<OpeningData, Out>;
@@ -1,6 +1,5 @@
1
- import { CoreUserSession } from '../../types/core.types.js';
2
1
  import { PikkuAbstractChannelHandler } from '../pikku-abstract-channel-handler.js';
3
- export declare class PikkuLocalChannelHandler<UserSession extends CoreUserSession = CoreUserSession, OpeningData = unknown, Out = unknown> extends PikkuAbstractChannelHandler<UserSession, OpeningData, Out> {
2
+ export declare class PikkuLocalChannelHandler<OpeningData = unknown, Out = unknown> extends PikkuAbstractChannelHandler<OpeningData, Out> {
4
3
  private onMessageCallback?;
5
4
  private openCallBack?;
6
5
  private closeCallback?;
@@ -13,5 +12,4 @@ export declare class PikkuLocalChannelHandler<UserSession extends CoreUserSessio
13
12
  close(): void;
14
13
  registerOnSend(send: (message: Out) => void): void;
15
14
  send(message: Out, isBinary?: boolean): void;
16
- setUserSession(userSession: UserSession): void;
17
15
  }
@@ -35,7 +35,4 @@ export class PikkuLocalChannelHandler extends PikkuAbstractChannelHandler {
35
35
  }
36
36
  return this.sendCallback?.(message, isBinary);
37
37
  }
38
- setUserSession(userSession) {
39
- this.getChannel().userSession = userSession;
40
- }
41
38
  }
@@ -1,46 +1,62 @@
1
1
  import { openChannel } from '../channel-runner.js';
2
- import { createHTTPInteraction, handleError, } from '../../http/http-route-runner.js';
2
+ import { createHTTPInteraction } from '../../http/http-route-runner.js';
3
3
  import { closeSessionServices } from '../../utils.js';
4
4
  import { processMessageHandlers } from '../channel-handler.js';
5
5
  import { PikkuLocalChannelHandler } from './local-channel-handler.js';
6
+ import { handleError } from '../../handle-error.js';
7
+ import { runMiddleware } from '../../middleware-runner.js';
8
+ import { LocalUserSessionService } from '../../services/user-session-service.js';
6
9
  export const runLocalChannel = async ({ singletonServices, channelId, request, response, route, createSessionServices, skipUserSession = false, respondWith404 = true, coerceToArray = false, logWarningsForStatusCodes = [], bubbleErrors = false, }) => {
7
10
  let sessionServices;
11
+ let channelHandler;
12
+ const userSessionService = new LocalUserSessionService();
8
13
  const http = createHTTPInteraction(request, response);
9
- try {
10
- const { userSession, openingData, channelConfig } = await openChannel({
11
- channelId,
12
- createSessionServices,
13
- respondWith404,
14
- http,
15
- route,
16
- singletonServices,
17
- skipUserSession,
18
- coerceToArray,
19
- });
20
- const channelHandler = new PikkuLocalChannelHandler(channelId, channelConfig.name, userSession, openingData);
21
- const channel = channelHandler.getChannel();
22
- if (createSessionServices) {
23
- sessionServices = await createSessionServices(singletonServices, { http }, userSession);
14
+ const main = async () => {
15
+ try {
16
+ const { openingData, channelConfig } = await openChannel({
17
+ channelId,
18
+ createSessionServices,
19
+ respondWith404,
20
+ http,
21
+ route,
22
+ singletonServices,
23
+ skipUserSession,
24
+ coerceToArray,
25
+ });
26
+ channelHandler = new PikkuLocalChannelHandler(channelId, channelConfig.name, openingData);
27
+ const channel = channelHandler.getChannel();
28
+ const userSession = await userSessionService.get();
29
+ if (createSessionServices) {
30
+ sessionServices = await createSessionServices(singletonServices, { http }, userSession);
31
+ }
32
+ const allServices = {
33
+ ...singletonServices,
34
+ ...sessionServices,
35
+ userSessionService,
36
+ };
37
+ channelHandler.registerOnOpen(() => {
38
+ channelConfig.onConnect?.(allServices, channel);
39
+ });
40
+ channelHandler.registerOnClose(async () => {
41
+ channelConfig.onDisconnect?.(allServices, channel);
42
+ if (sessionServices) {
43
+ await closeSessionServices(singletonServices.logger, sessionServices);
44
+ }
45
+ });
46
+ channelHandler.registerOnMessage(processMessageHandlers(allServices, channelConfig, channelHandler));
47
+ }
48
+ catch (e) {
49
+ handleError(e, http, channelId, singletonServices.logger, logWarningsForStatusCodes, respondWith404, bubbleErrors);
24
50
  }
25
- const allServices = { ...singletonServices, ...sessionServices };
26
- channelHandler.registerOnOpen(() => {
27
- channelConfig.onConnect?.(allServices, channel);
28
- });
29
- channelHandler.registerOnClose(async () => {
30
- channelConfig.onDisconnect?.(allServices, channel);
51
+ finally {
31
52
  if (sessionServices) {
32
53
  await closeSessionServices(singletonServices.logger, sessionServices);
33
54
  }
34
- });
35
- channelHandler.registerOnMessage(processMessageHandlers(allServices, channelConfig, channelHandler));
36
- return channelHandler;
37
- }
38
- catch (e) {
39
- handleError(e, http, channelId, singletonServices.logger, logWarningsForStatusCodes, respondWith404, bubbleErrors);
40
- }
41
- finally {
42
- if (sessionServices) {
43
- await closeSessionServices(singletonServices.logger, sessionServices);
44
55
  }
45
- }
56
+ };
57
+ await runMiddleware({
58
+ ...singletonServices,
59
+ userSessionService,
60
+ }, { http }, route.middleware || [], main);
61
+ return channelHandler;
46
62
  };
@@ -1,15 +1,12 @@
1
- import { CoreUserSession } from '../types/core.types.js';
2
1
  import { PikkuChannel, PikkuChannelHandler } from './channel.types.js';
3
- export declare abstract class PikkuAbstractChannelHandler<UserSession extends CoreUserSession = CoreUserSession, OpeningData = unknown, Out = unknown> implements PikkuChannelHandler<UserSession, OpeningData, Out> {
2
+ export declare abstract class PikkuAbstractChannelHandler<OpeningData = unknown, Out = unknown> implements PikkuChannelHandler<OpeningData, Out> {
4
3
  channelId: string;
5
4
  channelName: string;
6
- protected userSession: UserSession | undefined;
7
5
  protected openingData: OpeningData;
8
- protected channel?: PikkuChannel<UserSession, OpeningData, Out>;
9
- constructor(channelId: string, channelName: string, userSession: UserSession | undefined, openingData: OpeningData);
10
- abstract setUserSession(userSession: UserSession): Promise<void> | void;
6
+ protected channel?: PikkuChannel<OpeningData, Out>;
7
+ constructor(channelId: string, channelName: string, openingData: OpeningData);
11
8
  abstract send(message: Out, isBinary?: boolean): Promise<void> | void;
12
- getChannel(): PikkuChannel<UserSession, OpeningData, Out>;
9
+ getChannel(): PikkuChannel<OpeningData, Out>;
13
10
  open(): void;
14
11
  close(): Promise<void> | void;
15
12
  }
@@ -1,22 +1,18 @@
1
1
  export class PikkuAbstractChannelHandler {
2
2
  channelId;
3
3
  channelName;
4
- userSession;
5
4
  openingData;
6
5
  channel;
7
- constructor(channelId, channelName, userSession, openingData) {
6
+ constructor(channelId, channelName, openingData) {
8
7
  this.channelId = channelId;
9
8
  this.channelName = channelName;
10
- this.userSession = userSession;
11
9
  this.openingData = openingData;
12
10
  }
13
11
  getChannel() {
14
12
  if (!this.channel) {
15
13
  this.channel = {
16
14
  channelId: this.channelId,
17
- userSession: this.userSession,
18
15
  openingData: this.openingData,
19
- setUserSession: this.setUserSession.bind(this),
20
16
  send: this.send.bind(this),
21
17
  close: this.close.bind(this),
22
18
  state: 'initial',
@@ -1,14 +1,17 @@
1
1
  import { closeSessionServices } from '../../utils.js';
2
2
  import { processMessageHandlers } from '../channel-handler.js';
3
3
  import { getChannels, openChannel } from '../channel-runner.js';
4
- import { createHTTPInteraction, handleError, } from '../../http/http-route-runner.js';
5
- const getVariablesForChannel = ({ channelId, userSession, channelName, channelHandlerFactory, openingData, }) => {
4
+ import { createHTTPInteraction } from '../../http/http-route-runner.js';
5
+ import { handleError } from '../../handle-error.js';
6
+ import { RemoteUserSessionService } from '../../services/user-session-service.js';
7
+ import { runMiddleware } from '../../middleware-runner.js';
8
+ const getVariablesForChannel = ({ channelId, channelName, channelHandlerFactory, openingData, }) => {
6
9
  const { channels } = getChannels();
7
10
  const channelConfig = channels.find((channelConfig) => channelConfig.name === channelName);
8
11
  if (!channelConfig) {
9
12
  throw new Error(`Channel not found: ${channelName}`);
10
13
  }
11
- const channelHandler = channelHandlerFactory(channelId, channelConfig.name, openingData, userSession);
14
+ const channelHandler = channelHandlerFactory(channelId, channelConfig.name, openingData);
12
15
  return {
13
16
  channelConfig,
14
17
  channelHandler,
@@ -18,48 +21,53 @@ const getVariablesForChannel = ({ channelId, userSession, channelName, channelHa
18
21
  export const runChannelConnect = async ({ singletonServices, channelId, channelObject, request, response, route, createSessionServices, channelStore, channelHandlerFactory, coerceToArray = false, logWarningsForStatusCodes = [], respondWith404 = true, bubbleErrors = false, }) => {
19
22
  let sessionServices;
20
23
  const http = createHTTPInteraction(request, response);
21
- try {
22
- const { userSession, channelConfig, openingData } = await openChannel({
23
- channelId,
24
- createSessionServices,
25
- http,
26
- route,
27
- singletonServices,
28
- coerceToArray,
29
- });
30
- await channelStore.addChannel({
31
- channelId,
32
- channelName: channelConfig.name,
33
- openingData,
34
- channelObject,
35
- });
36
- const { channel } = getVariablesForChannel({
37
- channelId,
38
- userSession,
39
- channelHandlerFactory,
40
- channelName: channelConfig.name,
41
- });
42
- if (createSessionServices) {
43
- sessionServices = await createSessionServices(singletonServices, { http }, userSession);
24
+ const userSessionService = new RemoteUserSessionService(channelStore, channelId);
25
+ const { channelConfig, openingData } = await openChannel({
26
+ channelId,
27
+ createSessionServices,
28
+ http,
29
+ route,
30
+ singletonServices,
31
+ coerceToArray,
32
+ });
33
+ const main = async () => {
34
+ try {
35
+ await channelStore.addChannel({
36
+ channelId,
37
+ channelName: channelConfig.name,
38
+ openingData,
39
+ channelObject,
40
+ });
41
+ const { channel } = getVariablesForChannel({
42
+ channelId,
43
+ channelHandlerFactory,
44
+ channelName: channelConfig.name,
45
+ });
46
+ if (createSessionServices) {
47
+ sessionServices = await createSessionServices(singletonServices, { http }, await userSessionService.get());
48
+ }
49
+ await channelConfig.onConnect?.({ ...singletonServices, ...sessionServices }, channel);
50
+ http?.response?.setStatus(101);
44
51
  }
45
- await channelConfig.onConnect?.({ ...singletonServices, ...sessionServices }, channel);
46
- http?.response?.setStatus(101);
47
- }
48
- catch (e) {
49
- handleError(e, http, channelId, singletonServices.logger, logWarningsForStatusCodes, respondWith404, bubbleErrors);
50
- }
51
- finally {
52
- if (sessionServices) {
53
- await closeSessionServices(singletonServices.logger, sessionServices);
52
+ catch (e) {
53
+ handleError(e, http, channelId, singletonServices.logger, logWarningsForStatusCodes, respondWith404, bubbleErrors);
54
54
  }
55
- }
55
+ finally {
56
+ if (sessionServices) {
57
+ await closeSessionServices(singletonServices.logger, sessionServices);
58
+ }
59
+ }
60
+ };
61
+ await runMiddleware({
62
+ ...singletonServices,
63
+ userSessionService,
64
+ }, { http }, channelConfig.middleware || [], main);
56
65
  };
57
66
  export const runChannelDisconnect = async ({ singletonServices, ...params }) => {
58
67
  let sessionServices;
59
68
  const { userSession, openingData, channelName } = await params.channelStore.getChannel(params.channelId);
60
69
  const { channel, channelConfig } = getVariablesForChannel({
61
70
  ...params,
62
- userSession,
63
71
  openingData,
64
72
  channelName,
65
73
  });
@@ -77,7 +85,6 @@ export const runChannelMessage = async ({ singletonServices, ...params }, data)
77
85
  const { userSession, openingData, channelName } = await params.channelStore.getChannel(params.channelId);
78
86
  const { channelHandler, channelConfig } = getVariablesForChannel({
79
87
  ...params,
80
- userSession,
81
88
  openingData,
82
89
  channelName,
83
90
  });
@@ -0,0 +1,14 @@
1
+ import { Logger } from './services/logger.js';
2
+ import { PikkuHTTP } from './http/http-routes.types.js';
3
+ /**
4
+ * Handle errors that occur during route processing
5
+ *
6
+ * @param {any} e - The error that occurred
7
+ * @param {PikkuHTTP | undefined} http - HTTP interaction object
8
+ * @param {string} trackerId - Unique ID for tracking this error
9
+ * @param {Logger} logger - Logger service
10
+ * @param {number[]} logWarningsForStatusCodes - HTTP status codes to log as warnings
11
+ * @param {boolean} respondWith404 - Whether to respond with 404 for NotFoundError
12
+ * @param {boolean} bubbleError - Whether to throw the error after handling
13
+ */
14
+ export declare const handleError: (e: any, http: PikkuHTTP | undefined, trackerId: string, logger: Logger, logWarningsForStatusCodes: number[], respondWith404: boolean, bubbleError: boolean) => void;
@@ -0,0 +1,53 @@
1
+ import { getErrorResponse } from './errors/error-handler.js';
2
+ import { NotFoundError } from './errors/errors.js';
3
+ /**
4
+ * Handle errors that occur during route processing
5
+ *
6
+ * @param {any} e - The error that occurred
7
+ * @param {PikkuHTTP | undefined} http - HTTP interaction object
8
+ * @param {string} trackerId - Unique ID for tracking this error
9
+ * @param {Logger} logger - Logger service
10
+ * @param {number[]} logWarningsForStatusCodes - HTTP status codes to log as warnings
11
+ * @param {boolean} respondWith404 - Whether to respond with 404 for NotFoundError
12
+ * @param {boolean} bubbleError - Whether to throw the error after handling
13
+ */
14
+ export const handleError = (e, http, trackerId, logger, logWarningsForStatusCodes, respondWith404, bubbleError) => {
15
+ // Skip 404 handling if configured to do so
16
+ if (e instanceof NotFoundError && !respondWith404) {
17
+ return;
18
+ }
19
+ // Get appropriate error response
20
+ const errorResponse = getErrorResponse(e);
21
+ if (errorResponse != null) {
22
+ // Set status and response body
23
+ http?.response?.setStatus(errorResponse.status);
24
+ http?.response?.setJson({
25
+ message: errorResponse.message,
26
+ payload: e.payload,
27
+ traceId: trackerId,
28
+ });
29
+ // Log certain status codes as warnings
30
+ if (logWarningsForStatusCodes.includes(errorResponse.status)) {
31
+ logger.warn(`Warning id: ${trackerId}`);
32
+ logger.warn(e);
33
+ }
34
+ }
35
+ else {
36
+ // Handle unexpected errors
37
+ logger.warn(`Error id: ${trackerId}`);
38
+ logger.error(e);
39
+ http?.response?.setStatus(500);
40
+ http?.response?.setJson({ errorId: trackerId });
41
+ }
42
+ // Handle 404 errors specifically
43
+ if (e instanceof NotFoundError) {
44
+ http?.response?.end();
45
+ }
46
+ // Either bubble up or end the response
47
+ if (bubbleError) {
48
+ throw e;
49
+ }
50
+ else {
51
+ http?.response?.end();
52
+ }
53
+ };
@@ -1,30 +1,53 @@
1
1
  import { CoreHTTPFunctionRoute, HTTPRoutesMeta, RunRouteOptions, RunRouteParams, PikkuHTTP } from './http-routes.types.js';
2
- import { CoreUserSession } from '../types/core.types.js';
3
- import { PikkuHTTPAbstractRequest } from './pikku-http-abstract-request.js';
4
- import { Logger } from '../services/index.js';
5
- import { CoreAPIChannel } from '../channel/channel.types.js';
6
2
  import { PikkuRequest } from '../pikku-request.js';
7
3
  import { PikkuResponse } from '../pikku-response.js';
8
- import { HTTPSessionService } from './http-session-service.js';
9
- export declare const addRoute: <In, Out, Route extends string, APIFunction, APIFunctionSessionless, APIPermission>(route: CoreHTTPFunctionRoute<In, Out, Route, APIFunction, APIFunctionSessionless, APIPermission>) => void;
4
+ /**
5
+ * Add middleware to a specific route or globally
6
+ *
7
+ * @param {APIMiddleware[] | string} routeOrMiddleware - Route pattern to match or middleware array
8
+ * @param {APIMiddleware} [middleware] - Middleware to add (required if first param is a string)
9
+ */
10
+ export declare const addMiddleware: <APIMiddleware>(routeOrMiddleware: APIMiddleware[] | string, middleware?: APIMiddleware) => void;
11
+ /**
12
+ * Add a route to the global routes registry
13
+ *
14
+ * @param {CoreHTTPFunctionRoute} route - Route configuration to add
15
+ */
16
+ export declare const addRoute: <In, Out, Route extends string, APIFunction, APIFunctionSessionless, APIPermission, APIMiddleware>(route: CoreHTTPFunctionRoute<In, Out, Route, APIFunction, APIFunctionSessionless, APIPermission, APIMiddleware>) => void;
17
+ /**
18
+ * Remove all routes from the global registry
19
+ */
10
20
  export declare const clearRoutes: () => void;
11
21
  /**
22
+ * Set the HTTP routes metadata
23
+ *
24
+ * @param {HTTPRoutesMeta} routeMeta - Metadata for routes
12
25
  * @ignore
13
26
  */
14
- export declare const setHTTPRoutesMeta: (_routeMeta: HTTPRoutesMeta) => void;
27
+ export declare const setHTTPRoutesMeta: (routeMeta: HTTPRoutesMeta) => void;
15
28
  /**
16
- * Returns all the registered routes and associated metadata.
29
+ * Returns all the registered routes and associated metadata
30
+ *
31
+ * @returns {Object} Object containing routes and routesMeta
17
32
  * @internal
18
33
  */
19
34
  export declare const getRoutes: () => {
20
35
  routes: CoreHTTPFunctionRoute<any, any, any>[];
21
36
  routesMeta: HTTPRoutesMeta;
22
37
  };
23
- export declare const getUserSession: <UserSession extends CoreUserSession>(httpSessionService: HTTPSessionService<UserSession> | undefined, auth: boolean, request: PikkuHTTPAbstractRequest) => Promise<CoreUserSession | undefined>;
24
- export declare const loadUserSession: (skipUserSession: boolean, requiresSession: boolean, http: PikkuHTTP | undefined, matchedPath: any, route: CoreHTTPFunctionRoute<unknown, unknown, any> | CoreAPIChannel<unknown, any>, logger: Logger, httpSessionService: HTTPSessionService | undefined) => Promise<CoreUserSession | undefined>;
38
+ /**
39
+ * Create an HTTP interaction object from request and response
40
+ *
41
+ * @param {PikkuRequest | undefined} request - The HTTP request object
42
+ * @param {PikkuResponse | undefined} response - The HTTP response object
43
+ * @returns {PikkuHTTP | undefined} HTTP interaction object or undefined
44
+ */
25
45
  export declare const createHTTPInteraction: (request: PikkuRequest | undefined, response: PikkuResponse | undefined) => PikkuHTTP | undefined;
26
- export declare const handleError: (e: any, http: PikkuHTTP | undefined, trackerId: string, logger: Logger, logWarningsForStatusCodes: number[], respondWith404: boolean, bubbleError: boolean) => void;
27
46
  /**
47
+ * Run an HTTP route with the given parameters
48
+ *
49
+ * @param {Object} options - Options for running the route
50
+ * @returns {Promise<Out | void>} Result of the route handler
28
51
  * @ignore
29
52
  */
30
53
  export declare const runHTTPRoute: <In, Out>({ singletonServices, request, response, createSessionServices, route: apiRoute, method: apiType, skipUserSession, respondWith404, logWarningsForStatusCodes, coerceToArray, bubbleErrors, }: Pick<CoreHTTPFunctionRoute<unknown, unknown, any>, "route" | "method"> & RunRouteOptions & RunRouteParams<In>) => Promise<Out | void>;