@owlmeans/server-socket 0.1.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.
Files changed (51) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +560 -0
  3. package/build/.gitkeep +0 -0
  4. package/build/consts.d.ts +2 -0
  5. package/build/consts.d.ts.map +1 -0
  6. package/build/consts.js +2 -0
  7. package/build/consts.js.map +1 -0
  8. package/build/helper.d.ts +7 -0
  9. package/build/helper.d.ts.map +1 -0
  10. package/build/helper.js +14 -0
  11. package/build/helper.js.map +1 -0
  12. package/build/index.d.ts +6 -0
  13. package/build/index.d.ts.map +1 -0
  14. package/build/index.js +5 -0
  15. package/build/index.js.map +1 -0
  16. package/build/middleware.d.ts +3 -0
  17. package/build/middleware.d.ts.map +1 -0
  18. package/build/middleware.js +25 -0
  19. package/build/middleware.js.map +1 -0
  20. package/build/service.d.ts +5 -0
  21. package/build/service.d.ts.map +1 -0
  22. package/build/service.js +109 -0
  23. package/build/service.js.map +1 -0
  24. package/build/types.d.ts +11 -0
  25. package/build/types.d.ts.map +1 -0
  26. package/build/types.js +2 -0
  27. package/build/types.js.map +1 -0
  28. package/build/utils/connection.d.ts +6 -0
  29. package/build/utils/connection.d.ts.map +1 -0
  30. package/build/utils/connection.js +104 -0
  31. package/build/utils/connection.js.map +1 -0
  32. package/build/utils/index.d.ts +3 -0
  33. package/build/utils/index.d.ts.map +1 -0
  34. package/build/utils/index.js +3 -0
  35. package/build/utils/index.js.map +1 -0
  36. package/build/utils/server.d.ts +5 -0
  37. package/build/utils/server.d.ts.map +1 -0
  38. package/build/utils/server.js +15 -0
  39. package/build/utils/server.js.map +1 -0
  40. package/package.json +46 -0
  41. package/src/consts.ts +2 -0
  42. package/src/helper.ts +21 -0
  43. package/src/index.ts +6 -0
  44. package/src/middleware.ts +32 -0
  45. package/src/service.ts +133 -0
  46. package/src/types.ts +12 -0
  47. package/src/utils/connection.ts +124 -0
  48. package/src/utils/index.ts +3 -0
  49. package/src/utils/server.ts +21 -0
  50. package/tsconfig.json +16 -0
  51. package/tsconfig.tsbuildinfo +1 -0
@@ -0,0 +1,5 @@
1
+ import type { SocketService } from './types.js';
2
+ import type { Config, Context } from '@owlmeans/server-api';
3
+ export declare const createSocketService: (alias?: string) => SocketService;
4
+ export declare const appendSocketService: <C extends Config, T extends Context<C>>(ctx: T, alias?: string) => T;
5
+ //# sourceMappingURL=service.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"service.d.ts","sourceRoot":"","sources":["../src/service.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,YAAY,CAAA;AAC/C,OAAO,KAAK,EAAE,MAAM,EAAE,OAAO,EAAW,MAAM,sBAAsB,CAAA;AAWpE,eAAO,MAAM,mBAAmB,WAAW,MAAM,KAAmB,aA4GnE,CAAA;AAED,eAAO,MAAM,mBAAmB,GAAI,CAAC,SAAS,MAAM,EAAE,CAAC,SAAS,OAAO,CAAC,CAAC,CAAC,OACnE,CAAC,UAAS,MAAM,KACpB,CAOF,CAAA"}
@@ -0,0 +1,109 @@
1
+ import { assertContext, createService } from '@owlmeans/context';
2
+ import { DEFAULT_ALIAS } from './consts.js';
3
+ import { canServerModule } from './utils/server.js';
4
+ import { fastifyWebsocket } from '@fastify/websocket';
5
+ import { authorize, executeResponse, extractContext, handleError, populateContext, provideRequest } from '@owlmeans/server-api/utils';
6
+ import { ModuleOutcome, provideResponse } from '@owlmeans/module';
7
+ import { ResilientError } from '@owlmeans/error';
8
+ export const createSocketService = (alias = DEFAULT_ALIAS) => {
9
+ const service = createService(alias, {
10
+ update: async (api) => {
11
+ const closeListeners = [];
12
+ const ctx = assertContext(service.ctx, alias);
13
+ await api.server.register(fastifyWebsocket, {
14
+ preClose: () => closeListeners.forEach(listener => listener())
15
+ });
16
+ await api.server.register(async (server) => {
17
+ server.addHook('preHandler', async (req, reply) => {
18
+ const context = extractContext(req, service.ctx, alias);
19
+ await context?.modules()
20
+ .filter(module => canServerModule(context, module) && !module.route.isIntermediate())
21
+ .reduce(async (ctx, module) => {
22
+ let context = await ctx;
23
+ await module.resolve();
24
+ if (!module.route.match(req)) {
25
+ return context;
26
+ }
27
+ if (reply.sent) {
28
+ return context;
29
+ }
30
+ try {
31
+ const response = provideResponse(reply);
32
+ const request = provideRequest(module.alias, req, true);
33
+ const authorized = await authorize(context, module, req, reply);
34
+ context = authorized[0];
35
+ module = authorized[1];
36
+ populateContext(req, context);
37
+ // @TODO there code duplication with server-api
38
+ const gates = module.getGates();
39
+ for (const [srv, params] of gates) {
40
+ const gate = context.service(srv);
41
+ await gate.assert(request, response, params);
42
+ executeResponse(response, reply, true);
43
+ }
44
+ }
45
+ catch (error) {
46
+ console.error(error);
47
+ if (module.fixer != null) {
48
+ const fixer = context.service(module.fixer);
49
+ fixer.handle(reply, ResilientError.ensure(error));
50
+ }
51
+ else {
52
+ handleError(error, reply);
53
+ }
54
+ }
55
+ return context;
56
+ }, Promise.resolve(context));
57
+ });
58
+ await Promise.all(ctx.modules().filter(module => canServerModule(ctx, module) && !module.route.isIntermediate()).map(async (module) => {
59
+ await module.resolve();
60
+ if (module.handle == null) {
61
+ return;
62
+ }
63
+ server.get(module.getPath(), {
64
+ schema: {
65
+ querystring: module.filter?.query ?? {},
66
+ params: module.filter?.params ?? {},
67
+ response: module.filter?.response,
68
+ headers: module.filter?.headers ?? {}
69
+ }, websocket: true
70
+ }, (conn, req) => {
71
+ const request = provideRequest(module.alias, req, true);
72
+ request.body = conn;
73
+ conn.on('error', error => console.error('WebSocket error: ', error));
74
+ void module.handle(request, {
75
+ resolve: (value, outcome) => {
76
+ conn.send(typeof value === 'string' ? value : JSON.stringify(value));
77
+ if (outcome === ModuleOutcome.Ok) {
78
+ conn.close();
79
+ }
80
+ },
81
+ reject: error => {
82
+ console.error('Connection rejected: ', error);
83
+ conn.close(1011, ResilientError.ensure(error).marshal().message);
84
+ }
85
+ });
86
+ const close = () => conn.close(1001);
87
+ closeListeners.push(close);
88
+ conn.on('close', () => {
89
+ const index = closeListeners.indexOf(close);
90
+ if (index !== -1) {
91
+ closeListeners.splice(index, 1);
92
+ }
93
+ });
94
+ });
95
+ }));
96
+ });
97
+ }
98
+ }, service => async () => {
99
+ service.initialized = true;
100
+ });
101
+ return service;
102
+ };
103
+ export const appendSocketService = (ctx, alias = DEFAULT_ALIAS) => {
104
+ const service = createSocketService(alias);
105
+ const context = ctx;
106
+ context.registerService(service);
107
+ return context;
108
+ };
109
+ //# sourceMappingURL=service.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"service.js","sourceRoot":"","sources":["../src/service.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,11 @@
1
+ import type { InitializedService } from '@owlmeans/context';
2
+ import type { ApiServer, ApiServerAppend } from '@owlmeans/server-api';
3
+ import type { ServerConfig, ServerContext } from '@owlmeans/server-context';
4
+ export interface SocketService extends InitializedService {
5
+ update: (api: ApiServer) => Promise<void>;
6
+ }
7
+ export interface Config extends ServerConfig {
8
+ }
9
+ export interface Context<C extends Config = Config> extends ServerContext<C>, ApiServerAppend {
10
+ }
11
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,mBAAmB,CAAA;AAC3D,OAAO,KAAK,EAAE,SAAS,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAA;AACtE,OAAO,KAAK,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,0BAA0B,CAAA;AAE3E,MAAM,WAAW,aAAc,SAAQ,kBAAkB;IACvD,MAAM,EAAE,CAAC,GAAG,EAAE,SAAS,KAAK,OAAO,CAAC,IAAI,CAAC,CAAA;CAC1C;AAED,MAAM,WAAW,MAAO,SAAQ,YAAY;CAAI;AAEhD,MAAM,WAAW,OAAO,CAAC,CAAC,SAAS,MAAM,GAAG,MAAM,CAAE,SAAQ,aAAa,CAAC,CAAC,CAAC,EACxE,eAAe;CAAI"}
package/build/types.js ADDED
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":""}
@@ -0,0 +1,6 @@
1
+ import type { WebSocket } from '@fastify/websocket';
2
+ import type { Connection } from '@owlmeans/socket';
3
+ import type { Config, Context } from '@owlmeans/server-api';
4
+ import { AbstractRequest } from '@owlmeans/module';
5
+ export declare const makeConnection: <C extends Config, T extends Context<C> = Context<C>>(request: AbstractRequest<WebSocket>, context: T) => Connection;
6
+ //# sourceMappingURL=connection.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"connection.d.ts","sourceRoot":"","sources":["../../src/utils/connection.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAA;AACnD,OAAO,KAAK,EAAE,UAAU,EAAgB,MAAM,kBAAkB,CAAA;AAChE,OAAO,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,sBAAsB,CAAA;AAI3D,OAAO,EAAE,eAAe,EAAE,MAAM,kBAAkB,CAAA;AAOlD,eAAO,MAAM,cAAc,GAAI,CAAC,SAAS,MAAM,EAAE,CAAC,SAAS,OAAO,CAAC,CAAC,CAAC,wBAC1D,eAAe,CAAC,SAAS,CAAC,WAAW,CAAC,KAC9C,UA4GF,CAAA"}
@@ -0,0 +1,104 @@
1
+ import { SocketUnsupported, createBasicConnection, MessageType, SocketInitializationError, SocketUnauthorized } from '@owlmeans/socket';
2
+ import { AuthenticationStage, AUTH_QUERY } from '@owlmeans/auth';
3
+ import { isAuth, isAuthCredentials, isAuthToken } from '@owlmeans/auth';
4
+ import { EnvelopeKind, makeEnvelopeModel } from '@owlmeans/basic-envelope';
5
+ export const makeConnection = (request, context) => {
6
+ if (request.body == null) {
7
+ throw new SocketInitializationError('request');
8
+ }
9
+ let auth = undefined;
10
+ const conn = request.body;
11
+ const model = createBasicConnection();
12
+ model.close = async () => {
13
+ await conn.close();
14
+ // @TODO check if on close event is triggered
15
+ };
16
+ model.send = async (message) => {
17
+ if (typeof message !== 'string') {
18
+ model.prepare?.(message);
19
+ }
20
+ await conn.send(typeof message === 'string' ? message : JSON.stringify(message));
21
+ };
22
+ // @TODO This method is fully supported only by authentication services
23
+ model.authenticate = async (stage, payload) => {
24
+ if (AuthenticationStage.Authenticate === stage) {
25
+ if (isAuthToken(payload)) {
26
+ const ctx = context;
27
+ const _auth = await ctx.auth().authenticate(payload);
28
+ if (_auth == null) {
29
+ throw new SocketUnauthorized(stage);
30
+ }
31
+ const model = makeEnvelopeModel(_auth.token, EnvelopeKind.Token);
32
+ auth = model.message();
33
+ return [AuthenticationStage.Authenticated, _auth];
34
+ }
35
+ if (isAuth(payload)) {
36
+ auth = payload;
37
+ return [stage, auth];
38
+ }
39
+ }
40
+ else if (AuthenticationStage.Authenticated === stage) {
41
+ if (auth == null) {
42
+ throw new SocketUnauthorized(stage);
43
+ }
44
+ return [stage, auth];
45
+ }
46
+ throw new SocketUnsupported('auth-service');
47
+ };
48
+ model.prepare = (message, isRequest) => {
49
+ if (auth === undefined) {
50
+ auth = null;
51
+ try {
52
+ let authorization = request.query?.[AUTH_QUERY];
53
+ if (authorization != null) {
54
+ const envelope = makeEnvelopeModel(authorization, EnvelopeKind.Token);
55
+ const _auth = envelope.message();
56
+ if (isAuth(_auth) || isAuthCredentials(_auth)) {
57
+ auth = _auth;
58
+ }
59
+ }
60
+ }
61
+ catch {
62
+ auth = null;
63
+ }
64
+ }
65
+ if (isRequest) {
66
+ message.dt = message.dt ?? Date.now();
67
+ if (auth != null && message.recipient == null) {
68
+ message.recipient = auth.profileId ?? auth.userId;
69
+ }
70
+ if (message.sender == null) {
71
+ // @TODO think about using dids here
72
+ message.sender = context.cfg.service;
73
+ }
74
+ }
75
+ else {
76
+ if (auth != null && message.sender == null) {
77
+ message.sender = auth.profileId ?? auth.userId;
78
+ }
79
+ }
80
+ return message;
81
+ };
82
+ const messageHandler = async (_message) => {
83
+ _message = Array.isArray(_message) ? _message : [_message];
84
+ const message = _message.map(msg => msg.toString('utf8')).join('');
85
+ await model.receive(message);
86
+ };
87
+ const closeHandler = async (code) => {
88
+ const msg = {
89
+ type: MessageType.System,
90
+ event: 'close',
91
+ payload: { code }
92
+ };
93
+ if (model.prepare != null) {
94
+ model.prepare(msg);
95
+ }
96
+ await Promise.all(model.getListeners().map(async (listener) => listener(msg)));
97
+ conn.off('message', messageHandler);
98
+ conn.off('close', closeHandler);
99
+ };
100
+ conn.on('message', messageHandler);
101
+ conn.on('close', closeHandler);
102
+ return model;
103
+ };
104
+ //# sourceMappingURL=connection.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"connection.js","sourceRoot":"","sources":["../../src/utils/connection.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,3 @@
1
+ export * from './server.js';
2
+ export * from './connection.js';
3
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/utils/index.ts"],"names":[],"mappings":"AACA,cAAc,aAAa,CAAA;AAC3B,cAAc,iBAAiB,CAAA"}
@@ -0,0 +1,3 @@
1
+ export * from './server.js';
2
+ export * from './connection.js';
3
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/utils/index.ts"],"names":[],"mappings":"AACA,cAAc,aAAa,CAAA;AAC3B,cAAc,iBAAiB,CAAA"}
@@ -0,0 +1,5 @@
1
+ import type { CommonModule } from '@owlmeans/module';
2
+ import type { ServerModule } from '@owlmeans/server-module';
3
+ import type { Context } from '@owlmeans/server-api';
4
+ export declare const canServerModule: (context: Context, module: CommonModule) => module is ServerModule<unknown>;
5
+ //# sourceMappingURL=server.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../../src/utils/server.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAA;AACpD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,yBAAyB,CAAA;AAG3D,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,sBAAsB,CAAA;AAEnD,eAAO,MAAM,eAAe,YAAa,OAAO,UAAU,YAAY,KAAG,MAAM,IAAI,YAAY,CAAC,OAAO,CActG,CAAA"}
@@ -0,0 +1,15 @@
1
+ import { RouteProtocols } from '@owlmeans/route';
2
+ import { AppType } from '@owlmeans/context';
3
+ export const canServerModule = (context, module) => {
4
+ if (module.route.route.type !== AppType.Backend) {
5
+ return false;
6
+ }
7
+ if (module.route.route.service != null && module.route.route.service !== context.cfg.service) {
8
+ return false;
9
+ }
10
+ if (module.route.route.protocol !== RouteProtocols.SOCKET) {
11
+ return false;
12
+ }
13
+ return 'isIntermediate' in module.route;
14
+ };
15
+ //# sourceMappingURL=server.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"server.js","sourceRoot":"","sources":["../../src/utils/server.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAA;AAChD,OAAO,EAAE,OAAO,EAAE,MAAM,mBAAmB,CAAA;AAG3C,MAAM,CAAC,MAAM,eAAe,GAAG,CAAC,OAAgB,EAAE,MAAoB,EAAmC,EAAE;IACzG,IAAI,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,IAAI,KAAK,OAAO,CAAC,OAAO,EAAE,CAAC;QAChD,OAAO,KAAK,CAAA;IACd,CAAC;IAED,IAAI,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,OAAO,IAAI,IAAI,IAAI,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,OAAO,KAAK,OAAO,CAAC,GAAG,CAAC,OAAO,EAAE,CAAC;QAC7F,OAAO,KAAK,CAAA;IACd,CAAC;IAED,IAAI,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,QAAQ,KAAK,cAAc,CAAC,MAAM,EAAE,CAAC;QAC1D,OAAO,KAAK,CAAA;IACd,CAAC;IAED,OAAO,gBAAgB,IAAI,MAAM,CAAC,KAAK,CAAA;AACzC,CAAC,CAAA"}
package/package.json ADDED
@@ -0,0 +1,46 @@
1
+ {
2
+ "name": "@owlmeans/server-socket",
3
+ "version": "0.1.0",
4
+ "type": "module",
5
+ "scripts": {
6
+ "build": "tsc -b",
7
+ "dev": "sleep 324 && nodemon -e ts,tsx,json --watch src --exec \"tsc -p ./tsconfig.json\"",
8
+ "watch": "tsc -b -w --preserveWatchOutput --pretty"
9
+ },
10
+ "main": "build/index.js",
11
+ "module": "build/index.js",
12
+ "types": "build/index.d.ts",
13
+ "exports": {
14
+ ".": {
15
+ "import": "./build/index.js",
16
+ "require": "./build/index.js",
17
+ "default": "./build/index.js",
18
+ "module": "./build/index.js",
19
+ "types": "./build/index.d.ts"
20
+ }
21
+ },
22
+ "devDependencies": {
23
+ "@types/connect": "^3.4.38",
24
+ "nodemon": "^3.1.7",
25
+ "typescript": "^5.6.3"
26
+ },
27
+ "peerDependencies": {
28
+ "fastify": "*"
29
+ },
30
+ "dependencies": {
31
+ "@fastify/websocket": "^11.0.1",
32
+ "@owlmeans/auth": "^0.1.0",
33
+ "@owlmeans/basic-envelope": "^0.1.0",
34
+ "@owlmeans/context": "^0.1.0",
35
+ "@owlmeans/module": "^0.1.0",
36
+ "@owlmeans/server-api": "^0.1.0",
37
+ "@owlmeans/server-auth": "^0.1.0",
38
+ "@owlmeans/server-context": "^0.1.0",
39
+ "@owlmeans/server-module": "^0.1.0",
40
+ "@owlmeans/socket": "^0.1.0"
41
+ },
42
+ "private": false,
43
+ "publishConfig": {
44
+ "access": "public"
45
+ }
46
+ }
package/src/consts.ts ADDED
@@ -0,0 +1,2 @@
1
+
2
+ export const DEFAULT_ALIAS = 'socket-server'
package/src/helper.ts ADDED
@@ -0,0 +1,21 @@
1
+ import { assertContext } from '@owlmeans/context'
2
+ import type { AbstractRequest, AbstractResponse } from '@owlmeans/module'
3
+ import type { Context } from '@owlmeans/server-api'
4
+ import type { RefedModuleHandler } from '@owlmeans/server-module'
5
+ import type { Connection } from '@owlmeans/socket'
6
+ import type { WebSocket } from '@fastify/websocket'
7
+ import { makeConnection } from './utils/connection.js'
8
+
9
+ export const handleConnection: <T extends Connection = Connection>(
10
+ handler: (conn: T, ctx: Context, req: AbstractRequest<WebSocket>, res: AbstractResponse<any>) => Promise<void>
11
+ ) => RefedModuleHandler<AbstractResponse<any>> = handler => ref => async (req, res) => {
12
+ const ctx = assertContext(ref.ref?.ctx) as Context
13
+ try {
14
+ await handler(makeConnection(req, ctx) as any, ctx, req, res)
15
+ } catch (e) {
16
+ res.reject(e as Error)
17
+ }
18
+
19
+ // Actually it does nothing - just for compatibility here
20
+ return res.value
21
+ }
package/src/index.ts ADDED
@@ -0,0 +1,6 @@
1
+
2
+ export type * from './types.js'
3
+ export * from './middleware.js'
4
+ export * from './consts.js'
5
+ export * from './service.js'
6
+ export * from './helper.js'
@@ -0,0 +1,32 @@
1
+
2
+ import type { Middleware } from '@owlmeans/context'
3
+ import { MiddlewareType, MiddlewareStage, assertContext } from '@owlmeans/context'
4
+ import type { SocketService } from './types.js'
5
+ import { DEFAULT_ALIAS as WEB_ALIAS } from '@owlmeans/server-api'
6
+ import type { ApiServer, Config, Context, } from '@owlmeans/server-api'
7
+ import { DEFAULT_ALIAS } from './consts.js'
8
+
9
+
10
+ export const createSocketMiddleware = (web: string = WEB_ALIAS, socket = DEFAULT_ALIAS): Middleware => {
11
+ const middleware: Middleware = {
12
+ type: MiddlewareType.Context,
13
+ stage: MiddlewareStage.Loading,
14
+ apply: async ctx => {
15
+ const context = assertContext<Config, Context>(ctx as unknown as Context, 'socket-middleware')
16
+ const webService = context.service<ApiServer>(web)
17
+ const socketService = context.service<SocketService>(socket)
18
+ // @TODO There is some middlewares that are ok to work only once during context intialization
19
+
20
+ const marker = `__socketServiceAdded-${web}-${socket}`
21
+ if (!ctx.cfg.records?.find(record => record.id === marker)) {
22
+ await socketService.update(webService)
23
+ if (ctx.cfg.records == null) {
24
+ ctx.cfg.records = []
25
+ }
26
+ ctx.cfg.records.push({ id: marker })
27
+ }
28
+ }
29
+ }
30
+
31
+ return middleware
32
+ }
package/src/service.ts ADDED
@@ -0,0 +1,133 @@
1
+ import { assertContext, createService } from '@owlmeans/context'
2
+ import type { SocketService } from './types.js'
3
+ import type { Config, Context, Request } from '@owlmeans/server-api'
4
+ import { DEFAULT_ALIAS } from './consts.js'
5
+ import type { FixerService, ServerModule } from '@owlmeans/server-module'
6
+ import { canServerModule } from './utils/server.js'
7
+ import { fastifyWebsocket } from '@fastify/websocket'
8
+ import type { WebSocket } from '@fastify/websocket'
9
+ import { authorize, executeResponse, extractContext, handleError, populateContext, provideRequest } from '@owlmeans/server-api/utils'
10
+ import { ModuleOutcome, provideResponse } from '@owlmeans/module'
11
+ import type { AbstractRequest, GateService } from '@owlmeans/module'
12
+ import { ResilientError } from '@owlmeans/error'
13
+
14
+ export const createSocketService = (alias: string = DEFAULT_ALIAS): SocketService => {
15
+ const service: SocketService = createService<SocketService>(alias, {
16
+ update: async api => {
17
+ const closeListeners: CallableFunction[] = []
18
+ const ctx = assertContext<Config, Context>(service.ctx as Context, alias)
19
+ await api.server.register(fastifyWebsocket, {
20
+ preClose: () => closeListeners.forEach(listener => listener())
21
+ })
22
+
23
+ await api.server.register(async server => {
24
+ server.addHook('preHandler', async (req, reply) => {
25
+ const context = extractContext(req, service.ctx as Context, alias)
26
+ await context?.modules<ServerModule<Request>>()
27
+ .filter(module => canServerModule(context, module) && !module.route.isIntermediate())
28
+ .reduce<Promise<Context>>(async (ctx, module) => {
29
+ let context = await ctx
30
+ await module.resolve()
31
+
32
+ if (!module.route.match(req)) {
33
+ return context
34
+ }
35
+
36
+ if (reply.sent) {
37
+ return context
38
+ }
39
+
40
+ try {
41
+ const response = provideResponse(reply)
42
+ const request = provideRequest(module.alias, req, true)
43
+
44
+ const authorized = await authorize(context, module, req, reply)
45
+ context = authorized[0]
46
+ module = authorized[1]
47
+
48
+ populateContext(req, context)
49
+
50
+ // @TODO there code duplication with server-api
51
+ const gates = module.getGates()
52
+ for (const [srv, params] of gates) {
53
+ const gate: GateService = context.service(srv)
54
+ await gate.assert(request, response, params)
55
+ executeResponse(response, reply, true)
56
+ }
57
+ } catch (error) {
58
+ console.error(error)
59
+ if (module.fixer != null) {
60
+ const fixer: FixerService = context.service(module.fixer)
61
+ fixer.handle(reply, ResilientError.ensure(error as Error))
62
+ } else {
63
+ handleError(error as Error, reply)
64
+ }
65
+ }
66
+
67
+ return context
68
+ }, Promise.resolve(context))
69
+ })
70
+
71
+ await Promise.all(ctx.modules<ServerModule<Request>>().filter(
72
+ module => canServerModule(ctx, module) && !module.route.isIntermediate()
73
+ ).map(async module => {
74
+ await module.resolve()
75
+ if (module.handle == null) {
76
+ return
77
+ }
78
+
79
+ server.get(module.getPath(), {
80
+ schema: {
81
+ querystring: module.filter?.query ?? {},
82
+ params: module.filter?.params ?? {},
83
+ response: module.filter?.response,
84
+ headers: module.filter?.headers ?? {}
85
+ }, websocket: true
86
+ }, (conn, req) => {
87
+ const request = provideRequest(module.alias, req, true)
88
+ request.body = conn
89
+
90
+ conn.on('error', error => console.error('WebSocket error: ', error))
91
+
92
+ void module.handle<AbstractRequest<WebSocket>>(request, {
93
+ resolve: (value, outcome) => {
94
+ conn.send(typeof value === 'string' ? value : JSON.stringify(value))
95
+ if (outcome === ModuleOutcome.Ok) {
96
+ conn.close()
97
+ }
98
+ },
99
+ reject: error => {
100
+ console.error('Connection rejected: ', error)
101
+ conn.close(1011, ResilientError.ensure(error).marshal().message)
102
+ }
103
+ })
104
+
105
+ const close = () => conn.close(1001)
106
+ closeListeners.push(close)
107
+ conn.on('close', () => {
108
+ const index = closeListeners.indexOf(close)
109
+ if (index !== -1) {
110
+ closeListeners.splice(index, 1)
111
+ }
112
+ })
113
+ })
114
+ }))
115
+ })
116
+ }
117
+ }, service => async () => {
118
+ service.initialized = true
119
+ })
120
+
121
+ return service
122
+ }
123
+
124
+ export const appendSocketService = <C extends Config, T extends Context<C>>(
125
+ ctx: T, alias: string = DEFAULT_ALIAS
126
+ ): T => {
127
+ const service = createSocketService(alias)
128
+ const context = ctx as T
129
+
130
+ context.registerService(service)
131
+
132
+ return context
133
+ }
package/src/types.ts ADDED
@@ -0,0 +1,12 @@
1
+ import type { InitializedService } from '@owlmeans/context'
2
+ import type { ApiServer, ApiServerAppend } from '@owlmeans/server-api'
3
+ import type { ServerConfig, ServerContext } from '@owlmeans/server-context'
4
+
5
+ export interface SocketService extends InitializedService {
6
+ update: (api: ApiServer) => Promise<void>
7
+ }
8
+
9
+ export interface Config extends ServerConfig { }
10
+
11
+ export interface Context<C extends Config = Config> extends ServerContext<C>
12
+ , ApiServerAppend { }