@furystack/websocket-api 11.0.0 → 12.0.1
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/esm/actions/whoami.d.ts +2 -2
- package/esm/actions/whoami.d.ts.map +1 -1
- package/esm/actions/whoami.js +2 -9
- package/esm/actions/whoami.js.map +1 -1
- package/esm/actions/whoami.spec.js +1 -1
- package/esm/actions/whoami.spec.js.map +1 -1
- package/esm/models/websocket-action.d.ts +9 -1
- package/esm/models/websocket-action.d.ts.map +1 -1
- package/esm/websocket-api.d.ts +5 -4
- package/esm/websocket-api.d.ts.map +1 -1
- package/esm/websocket-api.js +11 -17
- package/esm/websocket-api.js.map +1 -1
- package/esm/websocket-api.spec.js +7 -1
- package/esm/websocket-api.spec.js.map +1 -1
- package/esm/websocket-integration.spec.js +53 -13
- package/esm/websocket-integration.spec.js.map +1 -1
- package/package.json +7 -7
- package/src/actions/whoami.spec.ts +1 -2
- package/src/actions/whoami.ts +5 -9
- package/src/models/websocket-action.ts +4 -3
- package/src/websocket-api.spec.ts +18 -1
- package/src/websocket-api.ts +16 -15
- package/src/websocket-integration.spec.ts +66 -14
- package/esm/websocket-user-context.d.ts +0 -8
- package/esm/websocket-user-context.d.ts.map +0 -1
- package/esm/websocket-user-context.js +0 -52
- package/esm/websocket-user-context.js.map +0 -1
- package/esm/websocket-user-context.spec.d.ts +0 -2
- package/esm/websocket-user-context.spec.d.ts.map +0 -1
- package/esm/websocket-user-context.spec.js +0 -68
- package/esm/websocket-user-context.spec.js.map +0 -1
- package/src/websocket-user-context.spec.ts +0 -73
- package/src/websocket-user-context.ts +0 -37
package/esm/actions/whoami.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/// <reference path="../../../rest-service/esm/incoming-message-extensions.d.ts" />
|
|
2
2
|
/// <reference path="../../../rest-service/esm/server-response-extensions.d.ts" />
|
|
3
3
|
/// <reference types="node/http.js" />
|
|
4
|
-
import type { Data } from 'ws';
|
|
4
|
+
import type { Data, WebSocket } from 'ws';
|
|
5
5
|
import type { WebSocketAction } from '../models/websocket-action.js';
|
|
6
6
|
import type { IncomingMessage } from 'http';
|
|
7
7
|
/**
|
|
@@ -16,8 +16,8 @@ export declare class WhoAmI implements WebSocketAction {
|
|
|
16
16
|
execute(options: {
|
|
17
17
|
data: Data;
|
|
18
18
|
request: IncomingMessage;
|
|
19
|
+
socket: WebSocket;
|
|
19
20
|
}): Promise<void>;
|
|
20
21
|
private readonly httpUserContext;
|
|
21
|
-
private readonly websocket;
|
|
22
22
|
}
|
|
23
23
|
//# sourceMappingURL=whoami.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"whoami.d.ts","sourceRoot":"","sources":["../../src/actions/whoami.ts"],"names":[],"mappings":";;;AAEA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,IAAI,CAAA;
|
|
1
|
+
{"version":3,"file":"whoami.d.ts","sourceRoot":"","sources":["../../src/actions/whoami.ts"],"names":[],"mappings":";;;AAEA,OAAO,KAAK,EAAE,IAAI,EAAE,SAAS,EAAE,MAAM,IAAI,CAAA;AACzC,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,+BAA+B,CAAA;AACpE,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,MAAM,CAAA;AAE3C;;GAEG;AACH,qBACa,MAAO,YAAW,eAAe;IACrC,OAAO;WAGA,UAAU,CAAC,OAAO,EAAE;QAAE,IAAI,EAAE,IAAI,CAAC;QAAC,OAAO,EAAE,eAAe,CAAA;KAAE,GAAG,OAAO;IAIvE,OAAO,CAAC,OAAO,EAAE;QAAE,IAAI,EAAE,IAAI,CAAC;QAAC,OAAO,EAAE,eAAe,CAAC;QAAC,MAAM,EAAE,SAAS,CAAA;KAAE;IASzF,iBACyB,eAAe,CAAiB;CAC1D"}
|
package/esm/actions/whoami.js
CHANGED
|
@@ -9,7 +9,6 @@ var __metadata = (this && this.__metadata) || function (k, v) {
|
|
|
9
9
|
};
|
|
10
10
|
import { HttpUserContext } from '@furystack/rest-service';
|
|
11
11
|
import { Injectable, Injected } from '@furystack/inject';
|
|
12
|
-
import ws from 'ws';
|
|
13
12
|
/**
|
|
14
13
|
* Example action that returns the current user instance
|
|
15
14
|
*/
|
|
@@ -23,23 +22,17 @@ let WhoAmI = class WhoAmI {
|
|
|
23
22
|
async execute(options) {
|
|
24
23
|
try {
|
|
25
24
|
const currentUser = await this.httpUserContext.getCurrentUser(options.request);
|
|
26
|
-
|
|
25
|
+
options.socket.send(JSON.stringify({ currentUser }));
|
|
27
26
|
}
|
|
28
27
|
catch (error) {
|
|
29
|
-
|
|
28
|
+
options.socket.send(JSON.stringify({ currentUser: null }));
|
|
30
29
|
}
|
|
31
30
|
}
|
|
32
|
-
httpUserContext;
|
|
33
|
-
websocket;
|
|
34
31
|
};
|
|
35
32
|
__decorate([
|
|
36
33
|
Injected(HttpUserContext),
|
|
37
34
|
__metadata("design:type", HttpUserContext)
|
|
38
35
|
], WhoAmI.prototype, "httpUserContext", void 0);
|
|
39
|
-
__decorate([
|
|
40
|
-
Injected(ws),
|
|
41
|
-
__metadata("design:type", ws)
|
|
42
|
-
], WhoAmI.prototype, "websocket", void 0);
|
|
43
36
|
WhoAmI = __decorate([
|
|
44
37
|
Injectable({ lifetime: 'transient' })
|
|
45
38
|
], WhoAmI);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"whoami.js","sourceRoot":"","sources":["../../src/actions/whoami.ts"],"names":[],"mappings":";;;;;;;;;AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,yBAAyB,CAAA;AACzD,OAAO,EAAE,UAAU,EAAE,QAAQ,EAAE,MAAM,mBAAmB,CAAA;
|
|
1
|
+
{"version":3,"file":"whoami.js","sourceRoot":"","sources":["../../src/actions/whoami.ts"],"names":[],"mappings":";;;;;;;;;AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,yBAAyB,CAAA;AACzD,OAAO,EAAE,UAAU,EAAE,QAAQ,EAAE,MAAM,mBAAmB,CAAA;AAKxD;;GAEG;AAEI,IAAM,MAAM,GAAZ,MAAM,MAAM;IACV,OAAO;QACZ,MAAM;IACR,CAAC;IACM,MAAM,CAAC,UAAU,CAAC,OAAiD;QACxE,OAAO,OAAO,CAAC,IAAI,CAAC,QAAQ,EAAE,KAAK,QAAQ,IAAI,OAAO,CAAC,IAAI,CAAC,QAAQ,EAAE,KAAK,gBAAgB,CAAA;IAC7F,CAAC;IAEM,KAAK,CAAC,OAAO,CAAC,OAAoE;QACvF,IAAI,CAAC;YACH,MAAM,WAAW,GAAG,MAAM,IAAI,CAAC,eAAe,CAAC,cAAc,CAAC,OAAO,CAAC,OAAO,CAAC,CAAA;YAC9E,OAAO,CAAC,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,SAAS,CAAC,EAAE,WAAW,EAAE,CAAC,CAAC,CAAA;QACtD,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,OAAO,CAAC,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,SAAS,CAAC,EAAE,WAAW,EAAE,IAAI,EAAE,CAAC,CAAC,CAAA;QAC5D,CAAC;IACH,CAAC;CAIF,CAAA;AAD0B;IADxB,QAAQ,CAAC,eAAe,CAAC;8BACgB,eAAe;+CAAA;AAlB9C,MAAM;IADlB,UAAU,CAAC,EAAE,QAAQ,EAAE,WAAW,EAAE,CAAC;GACzB,MAAM,CAmBlB"}
|
|
@@ -25,7 +25,7 @@ describe('Whoami action', () => {
|
|
|
25
25
|
injector.setExplicitInstance(contextMock, HttpUserContext);
|
|
26
26
|
injector.setExplicitInstance(wsMock, ws);
|
|
27
27
|
const instance = injector.getInstance(WhoAmI);
|
|
28
|
-
await instance.execute({ request, data: '' });
|
|
28
|
+
await instance.execute({ request, data: '', socket: wsMock });
|
|
29
29
|
expect(wsMock.send).toBeCalledWith(JSON.stringify({ currentUser }));
|
|
30
30
|
});
|
|
31
31
|
});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"whoami.spec.js","sourceRoot":"","sources":["../../src/actions/whoami.spec.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAA;AACpC,OAAO,EAAE,eAAe,EAAE,MAAM,yBAAyB,CAAA;AACzD,OAAO,EAAE,MAAM,IAAI,CAAA;AACnB,OAAO,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAA;AAE7C,OAAO,EAAE,QAAQ,EAAE,MAAM,mBAAmB,CAAA;AAC5C,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,QAAQ,CAAA;AAEjD,QAAQ,CAAC,eAAe,EAAE,GAAG,EAAE;IAC7B,MAAM,WAAW,GAAG,EAAE,QAAQ,EAAE,UAAU,EAAE,CAAA;IAC5C,MAAM,WAAW,GAAoB,EAAE,cAAc,EAAE,KAAK,IAAI,EAAE,CAAC,WAAW,EAAgC,CAAA;IAE9G,MAAM,OAAO,GAAG,EAAE,GAAG,EAAE,oBAAoB,EAAqB,CAAA;IAEhE,MAAM,MAAM,GAAO;QACjB,IAAI,EAAE,EAAE,CAAC,EAAE,CAAC,GAAG,EAAE,CAAC,SAAS,CAAC;KACZ,CAAA;IAElB,EAAE,CAAC,2CAA2C,EAAE,GAAG,EAAE;QACnD,MAAM,CAAC,MAAM,CAAC,UAAU,CAAC,EAAE,OAAO,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC,CAAC,SAAS,EAAE,CAAA;IACjE,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,6BAA6B,EAAE,GAAG,EAAE;QACrC,MAAM,CAAC,MAAM,CAAC,UAAU,CAAC,EAAE,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,CAAC,CAAC,CAAC,UAAU,EAAE,CAAA;IACrE,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,qCAAqC,EAAE,GAAG,EAAE;QAC7C,MAAM,CAAC,MAAM,CAAC,UAAU,CAAC,EAAE,OAAO,EAAE,IAAI,EAAE,gBAAgB,EAAE,CAAC,CAAC,CAAC,UAAU,EAAE,CAAA;IAC7E,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,gCAAgC,EAAE,KAAK,IAAI,EAAE;QAC9C,MAAM,UAAU,CAAC,IAAI,QAAQ,EAAE,EAAE,KAAK,EAAE,QAAQ,EAAE,EAAE;YAClD,QAAQ,CAAC,mBAAmB,CAAC,WAAW,EAAE,eAAe,CAAC,CAAA;YAC1D,QAAQ,CAAC,mBAAmB,CAAC,MAAM,EAAE,EAAE,CAAC,CAAA;YACxC,MAAM,QAAQ,GAAG,QAAQ,CAAC,WAAW,CAAC,MAAM,CAAC,CAAA;
|
|
1
|
+
{"version":3,"file":"whoami.spec.js","sourceRoot":"","sources":["../../src/actions/whoami.spec.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAA;AACpC,OAAO,EAAE,eAAe,EAAE,MAAM,yBAAyB,CAAA;AACzD,OAAO,EAAE,MAAM,IAAI,CAAA;AACnB,OAAO,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAA;AAE7C,OAAO,EAAE,QAAQ,EAAE,MAAM,mBAAmB,CAAA;AAC5C,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,QAAQ,CAAA;AAEjD,QAAQ,CAAC,eAAe,EAAE,GAAG,EAAE;IAC7B,MAAM,WAAW,GAAG,EAAE,QAAQ,EAAE,UAAU,EAAE,CAAA;IAC5C,MAAM,WAAW,GAAoB,EAAE,cAAc,EAAE,KAAK,IAAI,EAAE,CAAC,WAAW,EAAgC,CAAA;IAE9G,MAAM,OAAO,GAAG,EAAE,GAAG,EAAE,oBAAoB,EAAqB,CAAA;IAEhE,MAAM,MAAM,GAAO;QACjB,IAAI,EAAE,EAAE,CAAC,EAAE,CAAC,GAAG,EAAE,CAAC,SAAS,CAAC;KACZ,CAAA;IAElB,EAAE,CAAC,2CAA2C,EAAE,GAAG,EAAE;QACnD,MAAM,CAAC,MAAM,CAAC,UAAU,CAAC,EAAE,OAAO,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC,CAAC,SAAS,EAAE,CAAA;IACjE,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,6BAA6B,EAAE,GAAG,EAAE;QACrC,MAAM,CAAC,MAAM,CAAC,UAAU,CAAC,EAAE,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,CAAC,CAAC,CAAC,UAAU,EAAE,CAAA;IACrE,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,qCAAqC,EAAE,GAAG,EAAE;QAC7C,MAAM,CAAC,MAAM,CAAC,UAAU,CAAC,EAAE,OAAO,EAAE,IAAI,EAAE,gBAAgB,EAAE,CAAC,CAAC,CAAC,UAAU,EAAE,CAAA;IAC7E,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,gCAAgC,EAAE,KAAK,IAAI,EAAE;QAC9C,MAAM,UAAU,CAAC,IAAI,QAAQ,EAAE,EAAE,KAAK,EAAE,QAAQ,EAAE,EAAE;YAClD,QAAQ,CAAC,mBAAmB,CAAC,WAAW,EAAE,eAAe,CAAC,CAAA;YAC1D,QAAQ,CAAC,mBAAmB,CAAC,MAAM,EAAE,EAAE,CAAC,CAAA;YACxC,MAAM,QAAQ,GAAG,QAAQ,CAAC,WAAW,CAAC,MAAM,CAAC,CAAA;YAC7C,MAAM,QAAQ,CAAC,OAAO,CAAC,EAAE,OAAO,EAAE,IAAI,EAAE,EAAE,EAAE,MAAM,EAAE,MAAM,EAAE,CAAC,CAAA;YAC7D,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,cAAc,CAAC,IAAI,CAAC,SAAS,CAAC,EAAE,WAAW,EAAE,CAAC,CAAC,CAAA;QACrE,CAAC,CAAC,CAAA;IACJ,CAAC,CAAC,CAAA;AACJ,CAAC,CAAC,CAAA"}
|
|
@@ -1,11 +1,17 @@
|
|
|
1
|
+
/// <reference path="../../../rest-service/esm/incoming-message-extensions.d.ts" />
|
|
2
|
+
/// <reference path="../../../rest-service/esm/server-response-extensions.d.ts" />
|
|
3
|
+
/// <reference types="node/http.js" />
|
|
1
4
|
import type { Disposable } from '@furystack/utils';
|
|
2
|
-
import type { Data } from 'ws';
|
|
5
|
+
import type { Data, WebSocket } from 'ws';
|
|
6
|
+
import type { IncomingMessage } from 'http';
|
|
3
7
|
/**
|
|
4
8
|
* Static methods of a WebSocket Action
|
|
5
9
|
*/
|
|
6
10
|
export interface WebSocketActionStatic {
|
|
7
11
|
canExecute(options: {
|
|
8
12
|
data: Data;
|
|
13
|
+
request: IncomingMessage;
|
|
14
|
+
socket: WebSocket;
|
|
9
15
|
}): boolean;
|
|
10
16
|
}
|
|
11
17
|
/**
|
|
@@ -14,6 +20,8 @@ export interface WebSocketActionStatic {
|
|
|
14
20
|
export interface WebSocketAction extends Disposable {
|
|
15
21
|
execute(options: {
|
|
16
22
|
data: Data;
|
|
23
|
+
request: IncomingMessage;
|
|
24
|
+
socket: WebSocket;
|
|
17
25
|
}): void;
|
|
18
26
|
}
|
|
19
27
|
//# sourceMappingURL=websocket-action.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"websocket-action.d.ts","sourceRoot":"","sources":["../../src/models/websocket-action.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAA;AAClD,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,IAAI,CAAA;
|
|
1
|
+
{"version":3,"file":"websocket-action.d.ts","sourceRoot":"","sources":["../../src/models/websocket-action.ts"],"names":[],"mappings":";;;AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAA;AAClD,OAAO,KAAK,EAAE,IAAI,EAAE,SAAS,EAAE,MAAM,IAAI,CAAA;AACzC,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,MAAM,CAAA;AAE3C;;GAEG;AACH,MAAM,WAAW,qBAAqB;IACpC,UAAU,CAAC,OAAO,EAAE;QAAE,IAAI,EAAE,IAAI,CAAC;QAAC,OAAO,EAAE,eAAe,CAAC;QAAC,MAAM,EAAE,SAAS,CAAA;KAAE,GAAG,OAAO,CAAA;CAC1F;AAED;;GAEG;AACH,MAAM,WAAW,eAAgB,SAAQ,UAAU;IACjD,OAAO,CAAC,OAAO,EAAE;QAAE,IAAI,EAAE,IAAI,CAAC;QAAC,OAAO,EAAE,eAAe,CAAC;QAAC,MAAM,EAAE,SAAS,CAAA;KAAE,GAAG,IAAI,CAAA;CACpF"}
|
package/esm/websocket-api.d.ts
CHANGED
|
@@ -3,15 +3,16 @@
|
|
|
3
3
|
/// <reference types="node/http.js" />
|
|
4
4
|
/// <reference types="node/http.js" />
|
|
5
5
|
import { IncomingMessage } from 'http';
|
|
6
|
-
import { Injector } from '@furystack/inject';
|
|
7
|
-
import type
|
|
6
|
+
import type { Injector } from '@furystack/inject';
|
|
7
|
+
import { type Disposable } from '@furystack/utils';
|
|
8
8
|
import type { Data } from 'ws';
|
|
9
|
+
import type WebSocket from 'ws';
|
|
9
10
|
import ws from 'ws';
|
|
10
11
|
/**
|
|
11
12
|
* A WebSocket API implementation for FuryStack
|
|
12
13
|
*/
|
|
13
14
|
export declare class WebSocketApi implements Disposable {
|
|
14
|
-
readonly socket:
|
|
15
|
+
readonly socket: WebSocket.Server<typeof WebSocket, typeof IncomingMessage>;
|
|
15
16
|
private clients;
|
|
16
17
|
private readonly settings;
|
|
17
18
|
private readonly serverManager;
|
|
@@ -24,6 +25,6 @@ export declare class WebSocketApi implements Disposable {
|
|
|
24
25
|
ws: ws;
|
|
25
26
|
message: IncomingMessage;
|
|
26
27
|
}) => void | Promise<void>): Promise<void>;
|
|
27
|
-
execute(data: Data, injector: Injector): void
|
|
28
|
+
execute(data: Data, request: IncomingMessage, injector: Injector, socket: WebSocket): Promise<void>;
|
|
28
29
|
}
|
|
29
30
|
//# sourceMappingURL=websocket-api.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"websocket-api.d.ts","sourceRoot":"","sources":["../src/websocket-api.ts"],"names":[],"mappings":";;;;AAEA,OAAO,EAAE,eAAe,EAAE,MAAM,MAAM,CAAA;AAEtC,OAAO,
|
|
1
|
+
{"version":3,"file":"websocket-api.d.ts","sourceRoot":"","sources":["../src/websocket-api.ts"],"names":[],"mappings":";;;;AAEA,OAAO,EAAE,eAAe,EAAE,MAAM,MAAM,CAAA;AAEtC,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,mBAAmB,CAAA;AAEjD,OAAO,EAAc,KAAK,UAAU,EAAE,MAAM,kBAAkB,CAAA;AAC9D,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,IAAI,CAAA;AAC9B,OAAO,KAAK,SAAS,MAAM,IAAI,CAAA;AAE/B,OAAO,EAAE,MAAM,IAAI,CAAA;AAKnB;;GAEG;AACH,qBACa,YAAa,YAAW,UAAU;IAC7C,SAAgB,MAAM,6DAA0C;IAEhE,OAAO,CAAC,OAAO,CAA0E;IAEzF,iBACyB,QAAQ,CAAsB;IAEvD,iBACyB,aAAa,CAAe;IAErD,iBAAyB,QAAQ,CAAU;IAE3C,OAAO,CAAC,aAAa,CAAQ;IACtB,IAAI;IA+BE,OAAO;IAMP,SAAS,CACpB,QAAQ,EAAE,CAAC,OAAO,EAAE;QAAE,QAAQ,EAAE,QAAQ,CAAC;QAAC,EAAE,EAAE,EAAE,CAAC;QAAC,OAAO,EAAE,eAAe,CAAA;KAAE,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC;IAmB1F,OAAO,CAAC,IAAI,EAAE,IAAI,EAAE,OAAO,EAAE,eAAe,EAAE,QAAQ,EAAE,QAAQ,EAAE,MAAM,EAAE,SAAS;CAQjG"}
|
package/esm/websocket-api.js
CHANGED
|
@@ -9,22 +9,19 @@ var __metadata = (this && this.__metadata) || function (k, v) {
|
|
|
9
9
|
};
|
|
10
10
|
import { URL } from 'url';
|
|
11
11
|
import { IncomingMessage } from 'http';
|
|
12
|
-
import { ServerManager } from '@furystack/rest-service';
|
|
13
|
-
import { Injectable, Injected
|
|
12
|
+
import { HttpUserContext, ServerManager } from '@furystack/rest-service';
|
|
13
|
+
import { Injectable, Injected } from '@furystack/inject';
|
|
14
|
+
import { usingAsync } from '@furystack/utils';
|
|
14
15
|
import { WebSocketServer } from 'ws';
|
|
15
16
|
import ws from 'ws';
|
|
16
17
|
import { WebSocketApiSettings } from './websocket-api-settings.js';
|
|
17
18
|
import { AggregatedError, IdentityContext } from '@furystack/core';
|
|
18
|
-
import { WebsocketUserContext } from './websocket-user-context.js';
|
|
19
19
|
/**
|
|
20
20
|
* A WebSocket API implementation for FuryStack
|
|
21
21
|
*/
|
|
22
22
|
let WebSocketApi = class WebSocketApi {
|
|
23
23
|
socket = new WebSocketServer({ noServer: true });
|
|
24
24
|
clients = new Map();
|
|
25
|
-
settings;
|
|
26
|
-
serverManager;
|
|
27
|
-
injector;
|
|
28
25
|
isInitialized = false;
|
|
29
26
|
init() {
|
|
30
27
|
if (!this.isInitialized) {
|
|
@@ -32,10 +29,10 @@ let WebSocketApi = class WebSocketApi {
|
|
|
32
29
|
const connectionInjector = this.injector.createChild({ owner: msg });
|
|
33
30
|
connectionInjector.setExplicitInstance(websocket, ws);
|
|
34
31
|
connectionInjector.setExplicitInstance(msg, IncomingMessage);
|
|
35
|
-
connectionInjector.setExplicitInstance(connectionInjector.getInstance(
|
|
32
|
+
connectionInjector.setExplicitInstance(connectionInjector.getInstance(HttpUserContext), IdentityContext);
|
|
36
33
|
this.clients.set(websocket, { injector: connectionInjector, message: msg, ws: websocket });
|
|
37
34
|
websocket.on('message', (message) => {
|
|
38
|
-
this.execute(message, connectionInjector);
|
|
35
|
+
this.execute(message, msg, connectionInjector, websocket);
|
|
39
36
|
});
|
|
40
37
|
websocket.on('close', () => {
|
|
41
38
|
this.clients.delete(websocket);
|
|
@@ -77,11 +74,12 @@ let WebSocketApi = class WebSocketApi {
|
|
|
77
74
|
throw new AggregatedError('The Broadcast operation encountered some errors', errors);
|
|
78
75
|
}
|
|
79
76
|
}
|
|
80
|
-
execute(data, injector) {
|
|
81
|
-
const
|
|
82
|
-
if (
|
|
83
|
-
|
|
84
|
-
|
|
77
|
+
async execute(data, request, injector, socket) {
|
|
78
|
+
const Action = this.settings.actions.find((a) => a.canExecute({ data, request, socket }));
|
|
79
|
+
if (Action) {
|
|
80
|
+
await usingAsync(injector.getInstance(Action), async (action) => {
|
|
81
|
+
await action.execute({ data, request, socket });
|
|
82
|
+
});
|
|
85
83
|
}
|
|
86
84
|
}
|
|
87
85
|
};
|
|
@@ -93,10 +91,6 @@ __decorate([
|
|
|
93
91
|
Injected(ServerManager),
|
|
94
92
|
__metadata("design:type", ServerManager)
|
|
95
93
|
], WebSocketApi.prototype, "serverManager", void 0);
|
|
96
|
-
__decorate([
|
|
97
|
-
Injected(Injector),
|
|
98
|
-
__metadata("design:type", Injector)
|
|
99
|
-
], WebSocketApi.prototype, "injector", void 0);
|
|
100
94
|
WebSocketApi = __decorate([
|
|
101
95
|
Injectable({ lifetime: 'scoped' })
|
|
102
96
|
], WebSocketApi);
|
package/esm/websocket-api.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"websocket-api.js","sourceRoot":"","sources":["../src/websocket-api.ts"],"names":[],"mappings":";;;;;;;;;AAAA,OAAO,EAAE,GAAG,EAAE,MAAM,KAAK,CAAA;AAEzB,OAAO,EAAE,eAAe,EAAE,MAAM,MAAM,CAAA;AACtC,OAAO,EAAE,aAAa,EAAE,MAAM,yBAAyB,CAAA;
|
|
1
|
+
{"version":3,"file":"websocket-api.js","sourceRoot":"","sources":["../src/websocket-api.ts"],"names":[],"mappings":"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"}
|
|
@@ -7,19 +7,23 @@ var __decorate = (this && this.__decorate) || function (decorators, target, key,
|
|
|
7
7
|
import { Injector, Injectable } from '@furystack/inject';
|
|
8
8
|
import { usingAsync } from '@furystack/utils';
|
|
9
9
|
import { WebSocketApi } from './websocket-api.js';
|
|
10
|
-
import WebSocket from 'ws';
|
|
10
|
+
import { WebSocket } from 'ws';
|
|
11
11
|
import { useWebsockets } from './helpers.js';
|
|
12
12
|
import { describe, it, expect } from 'vitest';
|
|
13
13
|
import { getPort } from '@furystack/core/port-generator';
|
|
14
|
+
import { InMemoryStore, User, addStore } from '@furystack/core';
|
|
15
|
+
import { DefaultSession } from '@furystack/rest-service';
|
|
14
16
|
describe('WebSocketApi', () => {
|
|
15
17
|
it('Should be built', async () => {
|
|
16
18
|
await usingAsync(new Injector(), async (i) => {
|
|
19
|
+
addStore(i, new InMemoryStore({ model: User, primaryKey: 'username' })).addStore(new InMemoryStore({ model: DefaultSession, primaryKey: 'sessionId' }));
|
|
17
20
|
useWebsockets(i, { port: getPort() });
|
|
18
21
|
expect(i.getInstance(WebSocketApi)).toBeInstanceOf(WebSocketApi);
|
|
19
22
|
});
|
|
20
23
|
});
|
|
21
24
|
it('Should be built with settings', async () => {
|
|
22
25
|
await usingAsync(new Injector(), async (i) => {
|
|
26
|
+
addStore(i, new InMemoryStore({ model: User, primaryKey: 'username' })).addStore(new InMemoryStore({ model: DefaultSession, primaryKey: 'sessionId' }));
|
|
23
27
|
useWebsockets(i, { path: '/web-socket', port: getPort() });
|
|
24
28
|
expect(i.getInstance(WebSocketApi)).toBeInstanceOf(WebSocketApi);
|
|
25
29
|
});
|
|
@@ -27,6 +31,7 @@ describe('WebSocketApi', () => {
|
|
|
27
31
|
it('Should broadcast messages', async () => {
|
|
28
32
|
const port = getPort();
|
|
29
33
|
await usingAsync(new Injector(), async (i) => {
|
|
34
|
+
addStore(i, new InMemoryStore({ model: User, primaryKey: 'username' })).addStore(new InMemoryStore({ model: DefaultSession, primaryKey: 'sessionId' }));
|
|
30
35
|
expect.assertions(5); // All 5 clients should receive the message
|
|
31
36
|
useWebsockets(i, { path: '/web-socket', port });
|
|
32
37
|
const api = i.getInstance(WebSocketApi);
|
|
@@ -49,6 +54,7 @@ describe('WebSocketApi', () => {
|
|
|
49
54
|
it('Should receive client messages', async () => {
|
|
50
55
|
const port = getPort();
|
|
51
56
|
await usingAsync(new Injector(), async (i) => {
|
|
57
|
+
addStore(i, new InMemoryStore({ model: User, primaryKey: 'username' })).addStore(new InMemoryStore({ model: DefaultSession, primaryKey: 'sessionId' }));
|
|
52
58
|
expect.assertions(1);
|
|
53
59
|
const data = { value: 'alma' };
|
|
54
60
|
let ExampleWsAction = class ExampleWsAction {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"websocket-api.spec.js","sourceRoot":"","sources":["../src/websocket-api.spec.ts"],"names":[],"mappings":";;;;;;AAAA,OAAO,EAAE,QAAQ,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAA;AACxD,OAAO,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAA;AAC7C,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAA;AACjD,OAAO,SAAS,MAAM,IAAI,CAAA;
|
|
1
|
+
{"version":3,"file":"websocket-api.spec.js","sourceRoot":"","sources":["../src/websocket-api.spec.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,20 +1,21 @@
|
|
|
1
1
|
import { Injector } from '@furystack/inject';
|
|
2
2
|
import { WhoAmI } from './actions/whoami.js';
|
|
3
|
-
import
|
|
4
|
-
import { addStore, InMemoryStore, User } from '@furystack/core';
|
|
5
|
-
import { DefaultSession, ServerManager, useHttpAuthentication } from '@furystack/rest-service';
|
|
3
|
+
import { WebSocket } from 'ws';
|
|
4
|
+
import { addStore, InMemoryStore, StoreManager, User } from '@furystack/core';
|
|
5
|
+
import { DefaultSession, HttpUserContext, ServerManager, useHttpAuthentication } from '@furystack/rest-service';
|
|
6
6
|
import { useRestService } from '@furystack/rest-service';
|
|
7
7
|
import { useWebsockets } from './helpers.js';
|
|
8
|
-
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
|
|
8
|
+
import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
|
|
9
9
|
import { getPort } from '@furystack/core/port-generator';
|
|
10
10
|
describe('WebSocket Integration tests', () => {
|
|
11
11
|
const host = 'localhost';
|
|
12
12
|
const path = '/ws';
|
|
13
13
|
let i;
|
|
14
14
|
let client;
|
|
15
|
+
let port;
|
|
15
16
|
beforeEach(async () => {
|
|
16
17
|
i = new Injector();
|
|
17
|
-
|
|
18
|
+
port = getPort();
|
|
18
19
|
useRestService({
|
|
19
20
|
injector: i,
|
|
20
21
|
api: {},
|
|
@@ -29,7 +30,7 @@ describe('WebSocket Integration tests', () => {
|
|
|
29
30
|
i.getInstance(ServerManager)
|
|
30
31
|
.getOrCreate({ port })
|
|
31
32
|
.then(() => {
|
|
32
|
-
client = new
|
|
33
|
+
client = new WebSocket(`ws://${host}:${port}/ws`);
|
|
33
34
|
client
|
|
34
35
|
.on('open', () => {
|
|
35
36
|
done();
|
|
@@ -41,15 +42,54 @@ describe('WebSocket Integration tests', () => {
|
|
|
41
42
|
afterEach(async () => {
|
|
42
43
|
await i.dispose();
|
|
43
44
|
});
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
client.close();
|
|
49
|
-
done();
|
|
45
|
+
const getWhoAmIResult = async (subjectClient) => {
|
|
46
|
+
return new Promise((resolve, reject) => {
|
|
47
|
+
subjectClient.once('message', (data) => {
|
|
48
|
+
resolve(JSON.parse(data.toString()));
|
|
50
49
|
});
|
|
51
|
-
|
|
50
|
+
subjectClient.once('error', reject);
|
|
51
|
+
subjectClient.send('whoami');
|
|
52
52
|
});
|
|
53
|
+
};
|
|
54
|
+
describe('Authentication', () => {
|
|
55
|
+
it('Should be unauthenticated by default', async () => {
|
|
56
|
+
expect((await getWhoAmIResult(client)).currentUser).toBe(null);
|
|
57
|
+
});
|
|
58
|
+
});
|
|
59
|
+
it('Should be authenticated, roles should be updated and should be logged out', async () => {
|
|
60
|
+
const testUser = { username: 'test', password: 'test', roles: [] };
|
|
61
|
+
const userStore = i.getInstance(StoreManager).getStoreFor(User, 'username');
|
|
62
|
+
userStore.add(testUser);
|
|
63
|
+
const userCtx = i.getInstance(HttpUserContext);
|
|
64
|
+
let cookie = '';
|
|
65
|
+
await userCtx.cookieLogin(testUser, {
|
|
66
|
+
setHeader: (_setCookie, cookieValue) => {
|
|
67
|
+
cookie = cookieValue;
|
|
68
|
+
return {};
|
|
69
|
+
},
|
|
70
|
+
});
|
|
71
|
+
const authenticatedClient = await new Promise((done, reject) => {
|
|
72
|
+
const cl = new WebSocket(`ws://${host}:${port}/ws`, {
|
|
73
|
+
headers: { cookie },
|
|
74
|
+
});
|
|
75
|
+
cl.once('open', () => {
|
|
76
|
+
done(cl);
|
|
77
|
+
}).once('error', reject);
|
|
78
|
+
});
|
|
79
|
+
const whoAmIResult = await getWhoAmIResult(authenticatedClient);
|
|
80
|
+
expect(whoAmIResult.currentUser).toEqual(testUser);
|
|
81
|
+
userStore.update(testUser.username, { ...testUser, roles: ['newFancyRole'] });
|
|
82
|
+
const updatedWhoAmIResult = await getWhoAmIResult(authenticatedClient);
|
|
83
|
+
expect(updatedWhoAmIResult.currentUser.roles).toEqual(['newFancyRole']);
|
|
84
|
+
await userCtx.cookieLogout({
|
|
85
|
+
headers: {
|
|
86
|
+
cookie,
|
|
87
|
+
},
|
|
88
|
+
}, {
|
|
89
|
+
setHeader: vi.fn(),
|
|
90
|
+
});
|
|
91
|
+
const loggedOutWhoAmIResult = await getWhoAmIResult(authenticatedClient);
|
|
92
|
+
expect(loggedOutWhoAmIResult.currentUser).toBe(null);
|
|
53
93
|
});
|
|
54
94
|
});
|
|
55
95
|
//# sourceMappingURL=websocket-integration.spec.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"websocket-integration.spec.js","sourceRoot":"","sources":["../src/websocket-integration.spec.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,mBAAmB,CAAA;AAC5C,OAAO,EAAE,MAAM,EAAE,MAAM,qBAAqB,CAAA;AAC5C,OAAO,EAAE,MAAM,IAAI,CAAA;
|
|
1
|
+
{"version":3,"file":"websocket-integration.spec.js","sourceRoot":"","sources":["../src/websocket-integration.spec.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@furystack/websocket-api",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "12.0.1",
|
|
4
4
|
"description": "HTTP Api FuryStack package",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"scripts": {
|
|
@@ -34,16 +34,16 @@
|
|
|
34
34
|
},
|
|
35
35
|
"homepage": "https://github.com/furystack/furystack",
|
|
36
36
|
"dependencies": {
|
|
37
|
-
"@furystack/core": "^14.0.
|
|
38
|
-
"@furystack/inject": "^
|
|
39
|
-
"@furystack/rest-service": "^9.0.
|
|
40
|
-
"@furystack/utils": "^
|
|
37
|
+
"@furystack/core": "^14.0.2",
|
|
38
|
+
"@furystack/inject": "^11.0.1",
|
|
39
|
+
"@furystack/rest-service": "^9.0.2",
|
|
40
|
+
"@furystack/utils": "^7.0.0",
|
|
41
41
|
"ws": "^8.16.0"
|
|
42
42
|
},
|
|
43
43
|
"devDependencies": {
|
|
44
44
|
"@types/ws": "^8.5.10",
|
|
45
|
-
"typescript": "^5.4.
|
|
46
|
-
"vitest": "^1.
|
|
45
|
+
"typescript": "^5.4.5",
|
|
46
|
+
"vitest": "^1.5.0"
|
|
47
47
|
},
|
|
48
48
|
"gitHead": "1045d854bfd8c475b7035471d130d401417a2321"
|
|
49
49
|
}
|
|
@@ -33,8 +33,7 @@ describe('Whoami action', () => {
|
|
|
33
33
|
injector.setExplicitInstance(contextMock, HttpUserContext)
|
|
34
34
|
injector.setExplicitInstance(wsMock, ws)
|
|
35
35
|
const instance = injector.getInstance(WhoAmI)
|
|
36
|
-
|
|
37
|
-
await instance.execute({ request, data: '' })
|
|
36
|
+
await instance.execute({ request, data: '', socket: wsMock })
|
|
38
37
|
expect(wsMock.send).toBeCalledWith(JSON.stringify({ currentUser }))
|
|
39
38
|
})
|
|
40
39
|
})
|
package/src/actions/whoami.ts
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { HttpUserContext } from '@furystack/rest-service'
|
|
2
2
|
import { Injectable, Injected } from '@furystack/inject'
|
|
3
|
-
import type { Data } from 'ws'
|
|
4
|
-
import ws from 'ws'
|
|
3
|
+
import type { Data, WebSocket } from 'ws'
|
|
5
4
|
import type { WebSocketAction } from '../models/websocket-action.js'
|
|
6
5
|
import type { IncomingMessage } from 'http'
|
|
7
6
|
|
|
@@ -17,18 +16,15 @@ export class WhoAmI implements WebSocketAction {
|
|
|
17
16
|
return options.data.toString() === 'whoami' || options.data.toString() === 'whoami /claims'
|
|
18
17
|
}
|
|
19
18
|
|
|
20
|
-
public async execute(options: { data: Data; request: IncomingMessage }) {
|
|
19
|
+
public async execute(options: { data: Data; request: IncomingMessage; socket: WebSocket }) {
|
|
21
20
|
try {
|
|
22
21
|
const currentUser = await this.httpUserContext.getCurrentUser(options.request)
|
|
23
|
-
|
|
22
|
+
options.socket.send(JSON.stringify({ currentUser }))
|
|
24
23
|
} catch (error) {
|
|
25
|
-
|
|
24
|
+
options.socket.send(JSON.stringify({ currentUser: null }))
|
|
26
25
|
}
|
|
27
26
|
}
|
|
28
27
|
|
|
29
28
|
@Injected(HttpUserContext)
|
|
30
|
-
private readonly httpUserContext
|
|
31
|
-
|
|
32
|
-
@Injected(ws)
|
|
33
|
-
private readonly websocket!: ws
|
|
29
|
+
private declare readonly httpUserContext: HttpUserContext
|
|
34
30
|
}
|
|
@@ -1,16 +1,17 @@
|
|
|
1
1
|
import type { Disposable } from '@furystack/utils'
|
|
2
|
-
import type { Data } from 'ws'
|
|
2
|
+
import type { Data, WebSocket } from 'ws'
|
|
3
|
+
import type { IncomingMessage } from 'http'
|
|
3
4
|
|
|
4
5
|
/**
|
|
5
6
|
* Static methods of a WebSocket Action
|
|
6
7
|
*/
|
|
7
8
|
export interface WebSocketActionStatic {
|
|
8
|
-
canExecute(options: { data: Data }): boolean
|
|
9
|
+
canExecute(options: { data: Data; request: IncomingMessage; socket: WebSocket }): boolean
|
|
9
10
|
}
|
|
10
11
|
|
|
11
12
|
/**
|
|
12
13
|
* A WebSocket action implementaion
|
|
13
14
|
*/
|
|
14
15
|
export interface WebSocketAction extends Disposable {
|
|
15
|
-
execute(options: { data: Data }): void
|
|
16
|
+
execute(options: { data: Data; request: IncomingMessage; socket: WebSocket }): void
|
|
16
17
|
}
|
|
@@ -1,21 +1,30 @@
|
|
|
1
1
|
import { Injector, Injectable } from '@furystack/inject'
|
|
2
2
|
import { usingAsync } from '@furystack/utils'
|
|
3
3
|
import { WebSocketApi } from './websocket-api.js'
|
|
4
|
-
import WebSocket from 'ws'
|
|
4
|
+
import { WebSocket } from 'ws'
|
|
5
5
|
import type { WebSocketAction } from './models/websocket-action.js'
|
|
6
6
|
import { useWebsockets } from './helpers.js'
|
|
7
7
|
import { describe, it, expect } from 'vitest'
|
|
8
8
|
import { getPort } from '@furystack/core/port-generator'
|
|
9
|
+
import { InMemoryStore, User, addStore } from '@furystack/core'
|
|
10
|
+
import { DefaultSession } from '@furystack/rest-service'
|
|
9
11
|
|
|
10
12
|
describe('WebSocketApi', () => {
|
|
11
13
|
it('Should be built', async () => {
|
|
12
14
|
await usingAsync(new Injector(), async (i) => {
|
|
15
|
+
addStore(i, new InMemoryStore({ model: User, primaryKey: 'username' })).addStore(
|
|
16
|
+
new InMemoryStore({ model: DefaultSession, primaryKey: 'sessionId' }),
|
|
17
|
+
)
|
|
13
18
|
useWebsockets(i, { port: getPort() })
|
|
14
19
|
expect(i.getInstance(WebSocketApi)).toBeInstanceOf(WebSocketApi)
|
|
15
20
|
})
|
|
16
21
|
})
|
|
17
22
|
it('Should be built with settings', async () => {
|
|
18
23
|
await usingAsync(new Injector(), async (i) => {
|
|
24
|
+
addStore(i, new InMemoryStore({ model: User, primaryKey: 'username' })).addStore(
|
|
25
|
+
new InMemoryStore({ model: DefaultSession, primaryKey: 'sessionId' }),
|
|
26
|
+
)
|
|
27
|
+
|
|
19
28
|
useWebsockets(i, { path: '/web-socket', port: getPort() })
|
|
20
29
|
expect(i.getInstance(WebSocketApi)).toBeInstanceOf(WebSocketApi)
|
|
21
30
|
})
|
|
@@ -24,6 +33,10 @@ describe('WebSocketApi', () => {
|
|
|
24
33
|
it('Should broadcast messages', async () => {
|
|
25
34
|
const port = getPort()
|
|
26
35
|
await usingAsync(new Injector(), async (i) => {
|
|
36
|
+
addStore(i, new InMemoryStore({ model: User, primaryKey: 'username' })).addStore(
|
|
37
|
+
new InMemoryStore({ model: DefaultSession, primaryKey: 'sessionId' }),
|
|
38
|
+
)
|
|
39
|
+
|
|
27
40
|
expect.assertions(5) // All 5 clients should receive the message
|
|
28
41
|
useWebsockets(i, { path: '/web-socket', port })
|
|
29
42
|
const api = i.getInstance(WebSocketApi)
|
|
@@ -51,6 +64,10 @@ describe('WebSocketApi', () => {
|
|
|
51
64
|
it('Should receive client messages', async () => {
|
|
52
65
|
const port = getPort()
|
|
53
66
|
await usingAsync(new Injector(), async (i) => {
|
|
67
|
+
addStore(i, new InMemoryStore({ model: User, primaryKey: 'username' })).addStore(
|
|
68
|
+
new InMemoryStore({ model: DefaultSession, primaryKey: 'sessionId' }),
|
|
69
|
+
)
|
|
70
|
+
|
|
54
71
|
expect.assertions(1)
|
|
55
72
|
const data = { value: 'alma' }
|
|
56
73
|
@Injectable()
|
package/src/websocket-api.ts
CHANGED
|
@@ -1,16 +1,17 @@
|
|
|
1
1
|
import { URL } from 'url'
|
|
2
2
|
import type { Socket } from 'net'
|
|
3
3
|
import { IncomingMessage } from 'http'
|
|
4
|
-
import { ServerManager } from '@furystack/rest-service'
|
|
5
|
-
import {
|
|
6
|
-
import
|
|
4
|
+
import { HttpUserContext, ServerManager } from '@furystack/rest-service'
|
|
5
|
+
import type { Injector } from '@furystack/inject'
|
|
6
|
+
import { Injectable, Injected } from '@furystack/inject'
|
|
7
|
+
import { usingAsync, type Disposable } from '@furystack/utils'
|
|
7
8
|
import type { Data } from 'ws'
|
|
9
|
+
import type WebSocket from 'ws'
|
|
8
10
|
import { WebSocketServer } from 'ws'
|
|
9
11
|
import ws from 'ws'
|
|
10
12
|
import { WebSocketApiSettings } from './websocket-api-settings.js'
|
|
11
13
|
import type { WebSocketAction } from './models/websocket-action.js'
|
|
12
14
|
import { AggregatedError, IdentityContext } from '@furystack/core'
|
|
13
|
-
import { WebsocketUserContext } from './websocket-user-context.js'
|
|
14
15
|
|
|
15
16
|
/**
|
|
16
17
|
* A WebSocket API implementation for FuryStack
|
|
@@ -22,13 +23,12 @@ export class WebSocketApi implements Disposable {
|
|
|
22
23
|
private clients = new Map<ws, { injector: Injector; ws: ws; message: IncomingMessage }>()
|
|
23
24
|
|
|
24
25
|
@Injected(WebSocketApiSettings)
|
|
25
|
-
private readonly settings
|
|
26
|
+
private declare readonly settings: WebSocketApiSettings
|
|
26
27
|
|
|
27
28
|
@Injected(ServerManager)
|
|
28
|
-
private readonly serverManager
|
|
29
|
+
private declare readonly serverManager: ServerManager
|
|
29
30
|
|
|
30
|
-
|
|
31
|
-
private readonly injector!: Injector
|
|
31
|
+
private declare readonly injector: Injector
|
|
32
32
|
|
|
33
33
|
private isInitialized = false
|
|
34
34
|
public init() {
|
|
@@ -37,10 +37,10 @@ export class WebSocketApi implements Disposable {
|
|
|
37
37
|
const connectionInjector = this.injector.createChild({ owner: msg })
|
|
38
38
|
connectionInjector.setExplicitInstance(websocket, ws)
|
|
39
39
|
connectionInjector.setExplicitInstance(msg, IncomingMessage)
|
|
40
|
-
connectionInjector.setExplicitInstance(connectionInjector.getInstance(
|
|
40
|
+
connectionInjector.setExplicitInstance(connectionInjector.getInstance(HttpUserContext), IdentityContext)
|
|
41
41
|
this.clients.set(websocket, { injector: connectionInjector, message: msg, ws: websocket })
|
|
42
42
|
websocket.on('message', (message) => {
|
|
43
|
-
this.execute(message, connectionInjector)
|
|
43
|
+
this.execute(message, msg, connectionInjector, websocket)
|
|
44
44
|
})
|
|
45
45
|
|
|
46
46
|
websocket.on('close', () => {
|
|
@@ -88,11 +88,12 @@ export class WebSocketApi implements Disposable {
|
|
|
88
88
|
}
|
|
89
89
|
}
|
|
90
90
|
|
|
91
|
-
public execute(data: Data, injector: Injector) {
|
|
92
|
-
const
|
|
93
|
-
if (
|
|
94
|
-
|
|
95
|
-
|
|
91
|
+
public async execute(data: Data, request: IncomingMessage, injector: Injector, socket: WebSocket) {
|
|
92
|
+
const Action = this.settings.actions.find((a) => a.canExecute({ data, request, socket }))
|
|
93
|
+
if (Action) {
|
|
94
|
+
await usingAsync(injector.getInstance<WebSocketAction>(Action), async (action) => {
|
|
95
|
+
await action.execute({ data, request, socket })
|
|
96
|
+
})
|
|
96
97
|
}
|
|
97
98
|
}
|
|
98
99
|
}
|
|
@@ -1,22 +1,23 @@
|
|
|
1
1
|
import { Injector } from '@furystack/inject'
|
|
2
2
|
import { WhoAmI } from './actions/whoami.js'
|
|
3
|
-
import
|
|
4
|
-
import { addStore, InMemoryStore, User } from '@furystack/core'
|
|
5
|
-
import { DefaultSession, ServerManager, useHttpAuthentication } from '@furystack/rest-service'
|
|
3
|
+
import { WebSocket } from 'ws'
|
|
4
|
+
import { addStore, InMemoryStore, StoreManager, User } from '@furystack/core'
|
|
5
|
+
import { DefaultSession, HttpUserContext, ServerManager, useHttpAuthentication } from '@furystack/rest-service'
|
|
6
6
|
import { useRestService } from '@furystack/rest-service'
|
|
7
7
|
import { useWebsockets } from './helpers.js'
|
|
8
|
-
import { describe, it, expect, beforeEach, afterEach } from 'vitest'
|
|
8
|
+
import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest'
|
|
9
9
|
import { getPort } from '@furystack/core/port-generator'
|
|
10
10
|
|
|
11
11
|
describe('WebSocket Integration tests', () => {
|
|
12
12
|
const host = 'localhost'
|
|
13
13
|
const path = '/ws'
|
|
14
14
|
let i!: Injector
|
|
15
|
-
let client:
|
|
15
|
+
let client: WebSocket
|
|
16
|
+
let port: number
|
|
16
17
|
|
|
17
18
|
beforeEach(async () => {
|
|
18
19
|
i = new Injector()
|
|
19
|
-
|
|
20
|
+
port = getPort()
|
|
20
21
|
useRestService({
|
|
21
22
|
injector: i,
|
|
22
23
|
api: {},
|
|
@@ -34,7 +35,7 @@ describe('WebSocket Integration tests', () => {
|
|
|
34
35
|
i.getInstance(ServerManager)
|
|
35
36
|
.getOrCreate({ port })
|
|
36
37
|
.then(() => {
|
|
37
|
-
client = new
|
|
38
|
+
client = new WebSocket(`ws://${host}:${port}/ws`)
|
|
38
39
|
client
|
|
39
40
|
.on('open', () => {
|
|
40
41
|
done()
|
|
@@ -47,15 +48,66 @@ describe('WebSocket Integration tests', () => {
|
|
|
47
48
|
afterEach(async () => {
|
|
48
49
|
await i.dispose()
|
|
49
50
|
})
|
|
51
|
+
const getWhoAmIResult = async (subjectClient: WebSocket) => {
|
|
52
|
+
return new Promise<{ currentUser: User }>((resolve, reject) => {
|
|
53
|
+
subjectClient.once('message', (data: any) => {
|
|
54
|
+
resolve(JSON.parse(data.toString()))
|
|
55
|
+
})
|
|
56
|
+
subjectClient.once('error', reject)
|
|
57
|
+
subjectClient.send('whoami')
|
|
58
|
+
})
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
describe('Authentication', () => {
|
|
62
|
+
it('Should be unauthenticated by default', async () => {
|
|
63
|
+
expect((await getWhoAmIResult(client)).currentUser).toBe(null)
|
|
64
|
+
})
|
|
65
|
+
})
|
|
66
|
+
|
|
67
|
+
it('Should be authenticated, roles should be updated and should be logged out', async () => {
|
|
68
|
+
const testUser = { username: 'test', password: 'test', roles: [] } as User
|
|
69
|
+
|
|
70
|
+
const userStore = i.getInstance(StoreManager).getStoreFor(User, 'username')
|
|
71
|
+
userStore.add(testUser)
|
|
72
|
+
|
|
73
|
+
const userCtx = i.getInstance(HttpUserContext)
|
|
50
74
|
|
|
51
|
-
|
|
52
|
-
await
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
75
|
+
let cookie = ''
|
|
76
|
+
await userCtx.cookieLogin(testUser, {
|
|
77
|
+
setHeader: (_setCookie, cookieValue) => {
|
|
78
|
+
cookie = cookieValue as string
|
|
79
|
+
return {} as any
|
|
80
|
+
},
|
|
81
|
+
})
|
|
82
|
+
|
|
83
|
+
const authenticatedClient = await new Promise<WebSocket>((done, reject) => {
|
|
84
|
+
const cl = new WebSocket(`ws://${host}:${port}/ws`, {
|
|
85
|
+
headers: { cookie },
|
|
57
86
|
})
|
|
58
|
-
|
|
87
|
+
cl.once('open', () => {
|
|
88
|
+
done(cl)
|
|
89
|
+
}).once('error', reject)
|
|
59
90
|
})
|
|
91
|
+
const whoAmIResult = await getWhoAmIResult(authenticatedClient)
|
|
92
|
+
expect(whoAmIResult.currentUser).toEqual(testUser)
|
|
93
|
+
|
|
94
|
+
userStore.update(testUser.username, { ...testUser, roles: ['newFancyRole'] })
|
|
95
|
+
|
|
96
|
+
const updatedWhoAmIResult = await getWhoAmIResult(authenticatedClient)
|
|
97
|
+
expect(updatedWhoAmIResult.currentUser.roles).toEqual(['newFancyRole'])
|
|
98
|
+
|
|
99
|
+
await userCtx.cookieLogout(
|
|
100
|
+
{
|
|
101
|
+
headers: {
|
|
102
|
+
cookie,
|
|
103
|
+
},
|
|
104
|
+
},
|
|
105
|
+
{
|
|
106
|
+
setHeader: vi.fn(),
|
|
107
|
+
},
|
|
108
|
+
)
|
|
109
|
+
|
|
110
|
+
const loggedOutWhoAmIResult = await getWhoAmIResult(authenticatedClient)
|
|
111
|
+
expect(loggedOutWhoAmIResult.currentUser).toBe(null)
|
|
60
112
|
})
|
|
61
113
|
})
|
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
import type { IdentityContext, User } from '@furystack/core';
|
|
2
|
-
export declare class WebsocketUserContext implements IdentityContext {
|
|
3
|
-
isAuthenticated(): Promise<boolean>;
|
|
4
|
-
isAuthorized(...roles: string[]): Promise<boolean>;
|
|
5
|
-
getCurrentUser<TUser extends User>(): Promise<TUser>;
|
|
6
|
-
private readonly injector;
|
|
7
|
-
}
|
|
8
|
-
//# sourceMappingURL=websocket-user-context.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"websocket-user-context.d.ts","sourceRoot":"","sources":["../src/websocket-user-context.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,eAAe,EAAE,IAAI,EAAE,MAAM,iBAAiB,CAAA;AAI5D,qBACa,oBAAqB,YAAW,eAAe;IAC7C,eAAe,IAAI,OAAO,CAAC,OAAO,CAAC;IAOnC,YAAY,CAAC,GAAG,KAAK,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,OAAO,CAAC;IAalD,cAAc,CAAC,KAAK,SAAS,IAAI,KAAK,OAAO,CAAC,KAAK,CAAC;IAQjE,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAW;CACrC"}
|
|
@@ -1,52 +0,0 @@
|
|
|
1
|
-
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
2
|
-
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
3
|
-
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
4
|
-
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
5
|
-
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
6
|
-
};
|
|
7
|
-
var __metadata = (this && this.__metadata) || function (k, v) {
|
|
8
|
-
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
9
|
-
};
|
|
10
|
-
import { IncomingMessage } from 'http';
|
|
11
|
-
import { Injectable, Injected, Injector } from '@furystack/inject';
|
|
12
|
-
import { HttpUserContext } from '@furystack/rest-service';
|
|
13
|
-
let WebsocketUserContext = class WebsocketUserContext {
|
|
14
|
-
async isAuthenticated() {
|
|
15
|
-
try {
|
|
16
|
-
return (await this.getCurrentUser()) ? true : false;
|
|
17
|
-
}
|
|
18
|
-
catch (error) {
|
|
19
|
-
return false;
|
|
20
|
-
}
|
|
21
|
-
}
|
|
22
|
-
async isAuthorized(...roles) {
|
|
23
|
-
try {
|
|
24
|
-
const currentUser = await this.getCurrentUser();
|
|
25
|
-
for (const role of roles) {
|
|
26
|
-
if (!currentUser || !currentUser.roles.some((c) => c === role)) {
|
|
27
|
-
return false;
|
|
28
|
-
}
|
|
29
|
-
}
|
|
30
|
-
return true;
|
|
31
|
-
}
|
|
32
|
-
catch (error) {
|
|
33
|
-
return false;
|
|
34
|
-
}
|
|
35
|
-
}
|
|
36
|
-
async getCurrentUser() {
|
|
37
|
-
const user = await this.injector
|
|
38
|
-
.getInstance(HttpUserContext)
|
|
39
|
-
.authenticateRequest(this.injector.getInstance(IncomingMessage));
|
|
40
|
-
return user;
|
|
41
|
-
}
|
|
42
|
-
injector;
|
|
43
|
-
};
|
|
44
|
-
__decorate([
|
|
45
|
-
Injected(Injector),
|
|
46
|
-
__metadata("design:type", Injector)
|
|
47
|
-
], WebsocketUserContext.prototype, "injector", void 0);
|
|
48
|
-
WebsocketUserContext = __decorate([
|
|
49
|
-
Injectable({ lifetime: 'scoped' })
|
|
50
|
-
], WebsocketUserContext);
|
|
51
|
-
export { WebsocketUserContext };
|
|
52
|
-
//# sourceMappingURL=websocket-user-context.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"websocket-user-context.js","sourceRoot":"","sources":["../src/websocket-user-context.ts"],"names":[],"mappings":";;;;;;;;;AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,MAAM,CAAA;AAEtC,OAAO,EAAE,UAAU,EAAE,QAAQ,EAAE,QAAQ,EAAE,MAAM,mBAAmB,CAAA;AAClE,OAAO,EAAE,eAAe,EAAE,MAAM,yBAAyB,CAAA;AAGlD,IAAM,oBAAoB,GAA1B,MAAM,oBAAoB;IACxB,KAAK,CAAC,eAAe;QAC1B,IAAI,CAAC;YACH,OAAO,CAAC,MAAM,IAAI,CAAC,cAAc,EAAE,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,KAAK,CAAA;QACrD,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,OAAO,KAAK,CAAA;QACd,CAAC;IACH,CAAC;IACM,KAAK,CAAC,YAAY,CAAC,GAAG,KAAe;QAC1C,IAAI,CAAC;YACH,MAAM,WAAW,GAAG,MAAM,IAAI,CAAC,cAAc,EAAE,CAAA;YAC/C,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;gBACzB,IAAI,CAAC,WAAW,IAAI,CAAC,WAAW,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,KAAK,IAAI,CAAC,EAAE,CAAC;oBAC/D,OAAO,KAAK,CAAA;gBACd,CAAC;YACH,CAAC;YACD,OAAO,IAAI,CAAA;QACb,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,OAAO,KAAK,CAAA;QACd,CAAC;IACH,CAAC;IACM,KAAK,CAAC,cAAc;QACzB,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC,QAAQ;aAC7B,WAAW,CAAC,eAAe,CAAC;aAC5B,mBAAmB,CAAC,IAAI,CAAC,QAAQ,CAAC,WAAW,CAAC,eAAe,CAAC,CAAC,CAAA;QAClE,OAAO,IAAa,CAAA;IACtB,CAAC;IAGgB,QAAQ,CAAW;CACrC,CAAA;AADkB;IADhB,QAAQ,CAAC,QAAQ,CAAC;8BACS,QAAQ;sDAAA;AA7BzB,oBAAoB;IADhC,UAAU,CAAC,EAAE,QAAQ,EAAE,QAAQ,EAAE,CAAC;GACtB,oBAAoB,CA8BhC"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"websocket-user-context.spec.d.ts","sourceRoot":"","sources":["../src/websocket-user-context.spec.ts"],"names":[],"mappings":"AAEA,OAAO,GAAG,CAAA"}
|
|
@@ -1,68 +0,0 @@
|
|
|
1
|
-
import { Injector } from '@furystack/inject';
|
|
2
|
-
import { IncomingMessage } from 'http';
|
|
3
|
-
import '.';
|
|
4
|
-
import { HttpUserContext } from '@furystack/rest-service';
|
|
5
|
-
import { usingAsync } from '@furystack/utils';
|
|
6
|
-
import { WebsocketUserContext } from './websocket-user-context.js';
|
|
7
|
-
import { describe, it, expect, vi } from 'vitest';
|
|
8
|
-
describe('WebSocket User Context', () => {
|
|
9
|
-
const mockUser = { username: 'mock@gmail.com', roles: [] };
|
|
10
|
-
const mockAdmin = { username: 'mock@gmail.com', roles: ['admin'] };
|
|
11
|
-
describe('isAuthenticated', () => {
|
|
12
|
-
it('should authenticate with HttpUserContext and the IncomingMessage', async () => {
|
|
13
|
-
await usingAsync(new Injector(), async (i) => {
|
|
14
|
-
const authFn = vi.fn(async () => mockUser);
|
|
15
|
-
const incomingMessage = {};
|
|
16
|
-
i.setExplicitInstance(incomingMessage, IncomingMessage);
|
|
17
|
-
i.setExplicitInstance({ authenticateRequest: authFn }, HttpUserContext);
|
|
18
|
-
const authResult = await i.getInstance(WebsocketUserContext).isAuthenticated();
|
|
19
|
-
expect(authResult).toBeTruthy();
|
|
20
|
-
expect(authFn).toBeCalledWith(incomingMessage);
|
|
21
|
-
});
|
|
22
|
-
});
|
|
23
|
-
it('should return false if HttpUserContext throws', async () => {
|
|
24
|
-
await usingAsync(new Injector(), async (i) => {
|
|
25
|
-
const authFn = vi.fn(() => Promise.reject('Hey! No user here!'));
|
|
26
|
-
const incomingMessage = {};
|
|
27
|
-
i.setExplicitInstance(incomingMessage, IncomingMessage);
|
|
28
|
-
i.setExplicitInstance({ authenticateRequest: authFn }, HttpUserContext);
|
|
29
|
-
const authResult = await i.getInstance(WebsocketUserContext).isAuthenticated();
|
|
30
|
-
expect(authResult).toBeFalsy();
|
|
31
|
-
expect(authFn).toBeCalledWith(incomingMessage);
|
|
32
|
-
});
|
|
33
|
-
});
|
|
34
|
-
});
|
|
35
|
-
describe('isAuthorized', () => {
|
|
36
|
-
it('should return true if the user has the role', async () => {
|
|
37
|
-
await usingAsync(new Injector(), async (i) => {
|
|
38
|
-
const authFn = vi.fn(async () => mockAdmin);
|
|
39
|
-
const incomingMessage = {};
|
|
40
|
-
i.setExplicitInstance(incomingMessage, IncomingMessage);
|
|
41
|
-
i.setExplicitInstance({ authenticateRequest: authFn }, HttpUserContext);
|
|
42
|
-
const authResult = await i.getInstance(WebsocketUserContext).isAuthorized('admin');
|
|
43
|
-
expect(authResult).toBe(true);
|
|
44
|
-
});
|
|
45
|
-
});
|
|
46
|
-
it('should return false if the user does not have the role', async () => {
|
|
47
|
-
await usingAsync(new Injector(), async (i) => {
|
|
48
|
-
const authFn = vi.fn(async () => mockUser);
|
|
49
|
-
const incomingMessage = {};
|
|
50
|
-
i.setExplicitInstance(incomingMessage, IncomingMessage);
|
|
51
|
-
i.setExplicitInstance({ authenticateRequest: authFn }, HttpUserContext);
|
|
52
|
-
const authResult = await i.getInstance(WebsocketUserContext).isAuthorized('admin');
|
|
53
|
-
expect(authResult).toBe(false);
|
|
54
|
-
});
|
|
55
|
-
});
|
|
56
|
-
it('should return false if getting the current user throws', async () => {
|
|
57
|
-
await usingAsync(new Injector(), async (i) => {
|
|
58
|
-
const authFn = vi.fn(() => Promise.reject('Hey! No user here!'));
|
|
59
|
-
const incomingMessage = {};
|
|
60
|
-
i.setExplicitInstance(incomingMessage, IncomingMessage);
|
|
61
|
-
i.setExplicitInstance({ authenticateRequest: authFn }, HttpUserContext);
|
|
62
|
-
const authResult = await i.getInstance(WebsocketUserContext).isAuthorized('admin');
|
|
63
|
-
expect(authResult).toBe(false);
|
|
64
|
-
});
|
|
65
|
-
});
|
|
66
|
-
});
|
|
67
|
-
});
|
|
68
|
-
//# sourceMappingURL=websocket-user-context.spec.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"websocket-user-context.spec.js","sourceRoot":"","sources":["../src/websocket-user-context.spec.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,73 +0,0 @@
|
|
|
1
|
-
import { Injector } from '@furystack/inject'
|
|
2
|
-
import { IncomingMessage } from 'http'
|
|
3
|
-
import '.'
|
|
4
|
-
import { HttpUserContext } from '@furystack/rest-service'
|
|
5
|
-
import { usingAsync } from '@furystack/utils'
|
|
6
|
-
import { WebsocketUserContext } from './websocket-user-context.js'
|
|
7
|
-
import { describe, it, expect, vi } from 'vitest'
|
|
8
|
-
|
|
9
|
-
describe('WebSocket User Context', () => {
|
|
10
|
-
const mockUser = { username: 'mock@gmail.com', roles: [] }
|
|
11
|
-
const mockAdmin = { username: 'mock@gmail.com', roles: ['admin'] }
|
|
12
|
-
|
|
13
|
-
describe('isAuthenticated', () => {
|
|
14
|
-
it('should authenticate with HttpUserContext and the IncomingMessage', async () => {
|
|
15
|
-
await usingAsync(new Injector(), async (i) => {
|
|
16
|
-
const authFn = vi.fn(async () => mockUser)
|
|
17
|
-
const incomingMessage = {}
|
|
18
|
-
i.setExplicitInstance(incomingMessage, IncomingMessage)
|
|
19
|
-
i.setExplicitInstance({ authenticateRequest: authFn }, HttpUserContext)
|
|
20
|
-
const authResult = await i.getInstance(WebsocketUserContext).isAuthenticated()
|
|
21
|
-
expect(authResult).toBeTruthy()
|
|
22
|
-
expect(authFn).toBeCalledWith(incomingMessage)
|
|
23
|
-
})
|
|
24
|
-
})
|
|
25
|
-
|
|
26
|
-
it('should return false if HttpUserContext throws', async () => {
|
|
27
|
-
await usingAsync(new Injector(), async (i) => {
|
|
28
|
-
const authFn = vi.fn(() => Promise.reject('Hey! No user here!'))
|
|
29
|
-
const incomingMessage = {}
|
|
30
|
-
i.setExplicitInstance(incomingMessage, IncomingMessage)
|
|
31
|
-
i.setExplicitInstance({ authenticateRequest: authFn }, HttpUserContext)
|
|
32
|
-
const authResult = await i.getInstance(WebsocketUserContext).isAuthenticated()
|
|
33
|
-
expect(authResult).toBeFalsy()
|
|
34
|
-
expect(authFn).toBeCalledWith(incomingMessage)
|
|
35
|
-
})
|
|
36
|
-
})
|
|
37
|
-
})
|
|
38
|
-
|
|
39
|
-
describe('isAuthorized', () => {
|
|
40
|
-
it('should return true if the user has the role', async () => {
|
|
41
|
-
await usingAsync(new Injector(), async (i) => {
|
|
42
|
-
const authFn = vi.fn(async () => mockAdmin)
|
|
43
|
-
const incomingMessage = {}
|
|
44
|
-
i.setExplicitInstance(incomingMessage, IncomingMessage)
|
|
45
|
-
i.setExplicitInstance({ authenticateRequest: authFn }, HttpUserContext)
|
|
46
|
-
const authResult = await i.getInstance(WebsocketUserContext).isAuthorized('admin')
|
|
47
|
-
expect(authResult).toBe(true)
|
|
48
|
-
})
|
|
49
|
-
})
|
|
50
|
-
|
|
51
|
-
it('should return false if the user does not have the role', async () => {
|
|
52
|
-
await usingAsync(new Injector(), async (i) => {
|
|
53
|
-
const authFn = vi.fn(async () => mockUser)
|
|
54
|
-
const incomingMessage = {}
|
|
55
|
-
i.setExplicitInstance(incomingMessage, IncomingMessage)
|
|
56
|
-
i.setExplicitInstance({ authenticateRequest: authFn }, HttpUserContext)
|
|
57
|
-
const authResult = await i.getInstance(WebsocketUserContext).isAuthorized('admin')
|
|
58
|
-
expect(authResult).toBe(false)
|
|
59
|
-
})
|
|
60
|
-
})
|
|
61
|
-
|
|
62
|
-
it('should return false if getting the current user throws', async () => {
|
|
63
|
-
await usingAsync(new Injector(), async (i) => {
|
|
64
|
-
const authFn = vi.fn(() => Promise.reject('Hey! No user here!'))
|
|
65
|
-
const incomingMessage = {}
|
|
66
|
-
i.setExplicitInstance(incomingMessage, IncomingMessage)
|
|
67
|
-
i.setExplicitInstance({ authenticateRequest: authFn }, HttpUserContext)
|
|
68
|
-
const authResult = await i.getInstance(WebsocketUserContext).isAuthorized('admin')
|
|
69
|
-
expect(authResult).toBe(false)
|
|
70
|
-
})
|
|
71
|
-
})
|
|
72
|
-
})
|
|
73
|
-
})
|
|
@@ -1,37 +0,0 @@
|
|
|
1
|
-
import { IncomingMessage } from 'http'
|
|
2
|
-
import type { IdentityContext, User } from '@furystack/core'
|
|
3
|
-
import { Injectable, Injected, Injector } from '@furystack/inject'
|
|
4
|
-
import { HttpUserContext } from '@furystack/rest-service'
|
|
5
|
-
|
|
6
|
-
@Injectable({ lifetime: 'scoped' })
|
|
7
|
-
export class WebsocketUserContext implements IdentityContext {
|
|
8
|
-
public async isAuthenticated(): Promise<boolean> {
|
|
9
|
-
try {
|
|
10
|
-
return (await this.getCurrentUser()) ? true : false
|
|
11
|
-
} catch (error) {
|
|
12
|
-
return false
|
|
13
|
-
}
|
|
14
|
-
}
|
|
15
|
-
public async isAuthorized(...roles: string[]): Promise<boolean> {
|
|
16
|
-
try {
|
|
17
|
-
const currentUser = await this.getCurrentUser()
|
|
18
|
-
for (const role of roles) {
|
|
19
|
-
if (!currentUser || !currentUser.roles.some((c) => c === role)) {
|
|
20
|
-
return false
|
|
21
|
-
}
|
|
22
|
-
}
|
|
23
|
-
return true
|
|
24
|
-
} catch (error) {
|
|
25
|
-
return false
|
|
26
|
-
}
|
|
27
|
-
}
|
|
28
|
-
public async getCurrentUser<TUser extends User>(): Promise<TUser> {
|
|
29
|
-
const user = await this.injector
|
|
30
|
-
.getInstance(HttpUserContext)
|
|
31
|
-
.authenticateRequest(this.injector.getInstance(IncomingMessage))
|
|
32
|
-
return user as TUser
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
@Injected(Injector)
|
|
36
|
-
private readonly injector!: Injector
|
|
37
|
-
}
|