@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.
- package/LICENSE +21 -0
- package/README.md +560 -0
- package/build/.gitkeep +0 -0
- package/build/consts.d.ts +2 -0
- package/build/consts.d.ts.map +1 -0
- package/build/consts.js +2 -0
- package/build/consts.js.map +1 -0
- package/build/helper.d.ts +7 -0
- package/build/helper.d.ts.map +1 -0
- package/build/helper.js +14 -0
- package/build/helper.js.map +1 -0
- package/build/index.d.ts +6 -0
- package/build/index.d.ts.map +1 -0
- package/build/index.js +5 -0
- package/build/index.js.map +1 -0
- package/build/middleware.d.ts +3 -0
- package/build/middleware.d.ts.map +1 -0
- package/build/middleware.js +25 -0
- package/build/middleware.js.map +1 -0
- package/build/service.d.ts +5 -0
- package/build/service.d.ts.map +1 -0
- package/build/service.js +109 -0
- package/build/service.js.map +1 -0
- package/build/types.d.ts +11 -0
- package/build/types.d.ts.map +1 -0
- package/build/types.js +2 -0
- package/build/types.js.map +1 -0
- package/build/utils/connection.d.ts +6 -0
- package/build/utils/connection.d.ts.map +1 -0
- package/build/utils/connection.js +104 -0
- package/build/utils/connection.js.map +1 -0
- package/build/utils/index.d.ts +3 -0
- package/build/utils/index.d.ts.map +1 -0
- package/build/utils/index.js +3 -0
- package/build/utils/index.js.map +1 -0
- package/build/utils/server.d.ts +5 -0
- package/build/utils/server.d.ts.map +1 -0
- package/build/utils/server.js +15 -0
- package/build/utils/server.js.map +1 -0
- package/package.json +46 -0
- package/src/consts.ts +2 -0
- package/src/helper.ts +21 -0
- package/src/index.ts +6 -0
- package/src/middleware.ts +32 -0
- package/src/service.ts +133 -0
- package/src/types.ts +12 -0
- package/src/utils/connection.ts +124 -0
- package/src/utils/index.ts +3 -0
- package/src/utils/server.ts +21 -0
- package/tsconfig.json +16 -0
- 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"}
|
package/build/service.js
ADDED
|
@@ -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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"}
|
package/build/types.d.ts
ADDED
|
@@ -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 @@
|
|
|
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 @@
|
|
|
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 @@
|
|
|
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
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,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 { }
|