@ngxs/websocket-plugin 3.8.2 → 18.0.0-dev.master-56d8014
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/README.md +1 -1
- package/esm2022/index.mjs +9 -0
- package/esm2022/src/private_api.mjs +2 -0
- package/esm2022/src/providers.mjs +34 -0
- package/esm2022/src/public_api.mjs +3 -0
- package/esm2022/src/symbols.mjs +65 -0
- package/esm2022/src/websocket-handler.mjs +149 -0
- package/esm2022/src/websocket.module.mjs +21 -0
- package/fesm2022/ngxs-websocket-plugin.mjs +272 -0
- package/fesm2022/ngxs-websocket-plugin.mjs.map +1 -0
- package/index.d.ts +4 -0
- package/package.json +19 -13
- package/src/private_api.d.ts +1 -0
- package/src/providers.d.ts +31 -0
- package/src/public_api.d.ts +2 -2
- package/src/symbols.d.ts +6 -6
- package/src/websocket-handler.d.ts +14 -28
- package/src/websocket.module.d.ts +8 -18
- package/bundles/ngxs-websocket-plugin.umd.js +0 -786
- package/bundles/ngxs-websocket-plugin.umd.js.map +0 -1
- package/esm2015/index.js +0 -5
- package/esm2015/src/public_api.js +0 -3
- package/esm2015/src/symbols.js +0 -66
- package/esm2015/src/websocket-handler.js +0 -142
- package/esm2015/src/websocket.module.js +0 -45
- package/fesm2015/ngxs-websocket-plugin.js +0 -258
- package/fesm2015/ngxs-websocket-plugin.js.map +0 -1
- package/ngxs-websocket-plugin.d.ts +0 -5
- /package/{esm2015/ngxs-websocket-plugin.js → esm2022/ngxs-websocket-plugin.mjs} +0 -0
package/README.md
CHANGED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The public api for consumers of @ngxs/websocket-plugin
|
|
3
|
+
*/
|
|
4
|
+
export * from './src/public_api';
|
|
5
|
+
/**
|
|
6
|
+
* The private api for consumers of @ngxs/websocket-plugin
|
|
7
|
+
*/
|
|
8
|
+
export * from './src/private_api';
|
|
9
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW5kZXguanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi8uLi9wYWNrYWdlcy93ZWJzb2NrZXQtcGx1Z2luL2luZGV4LnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBOztHQUVHO0FBQ0gsY0FBYyxrQkFBa0IsQ0FBQztBQUVqQzs7R0FFRztBQUNILGNBQWMsbUJBQW1CLENBQUMiLCJzb3VyY2VzQ29udGVudCI6WyIvKipcbiAqIFRoZSBwdWJsaWMgYXBpIGZvciBjb25zdW1lcnMgb2YgQG5neHMvd2Vic29ja2V0LXBsdWdpblxuICovXG5leHBvcnQgKiBmcm9tICcuL3NyYy9wdWJsaWNfYXBpJztcblxuLyoqXG4gKiBUaGUgcHJpdmF0ZSBhcGkgZm9yIGNvbnN1bWVycyBvZiBAbmd4cy93ZWJzb2NrZXQtcGx1Z2luXG4gKi9cbmV4cG9ydCAqIGZyb20gJy4vc3JjL3ByaXZhdGVfYXBpJztcbiJdfQ==
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
export { NGXS_WEBSOCKET_OPTIONS as ɵNGXS_WEBSOCKET_OPTIONS } from './symbols';
|
|
2
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoicHJpdmF0ZV9hcGkuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi8uLi8uLi9wYWNrYWdlcy93ZWJzb2NrZXQtcGx1Z2luL3NyYy9wcml2YXRlX2FwaS50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSxPQUFPLEVBQUUsc0JBQXNCLElBQUksdUJBQXVCLEVBQUUsTUFBTSxXQUFXLENBQUMiLCJzb3VyY2VzQ29udGVudCI6WyJleHBvcnQgeyBOR1hTX1dFQlNPQ0tFVF9PUFRJT05TIGFzIMm1TkdYU19XRUJTT0NLRVRfT1BUSU9OUyB9IGZyb20gJy4vc3ltYm9scyc7XG4iXX0=
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { APP_INITIALIZER } from '@angular/core';
|
|
2
|
+
import { WebSocketHandler } from './websocket-handler';
|
|
3
|
+
import { USER_OPTIONS, NGXS_WEBSOCKET_OPTIONS } from './symbols';
|
|
4
|
+
export function ɵwebsocketOptionsFactory(options) {
|
|
5
|
+
return {
|
|
6
|
+
reconnectInterval: 5000,
|
|
7
|
+
reconnectAttempts: 10,
|
|
8
|
+
typeKey: 'type',
|
|
9
|
+
deserializer(e) {
|
|
10
|
+
return JSON.parse(e.data);
|
|
11
|
+
},
|
|
12
|
+
serializer(value) {
|
|
13
|
+
return JSON.stringify(value);
|
|
14
|
+
},
|
|
15
|
+
...options
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
export function ɵgetProviders(options) {
|
|
19
|
+
return [
|
|
20
|
+
{ provide: USER_OPTIONS, useValue: options },
|
|
21
|
+
{
|
|
22
|
+
provide: NGXS_WEBSOCKET_OPTIONS,
|
|
23
|
+
useFactory: ɵwebsocketOptionsFactory,
|
|
24
|
+
deps: [USER_OPTIONS]
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
provide: APP_INITIALIZER,
|
|
28
|
+
useFactory: () => () => { },
|
|
29
|
+
deps: [WebSocketHandler],
|
|
30
|
+
multi: true
|
|
31
|
+
}
|
|
32
|
+
];
|
|
33
|
+
}
|
|
34
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoicHJvdmlkZXJzLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vLi4vLi4vcGFja2FnZXMvd2Vic29ja2V0LXBsdWdpbi9zcmMvcHJvdmlkZXJzLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBLE9BQU8sRUFBRSxlQUFlLEVBQUUsTUFBTSxlQUFlLENBQUM7QUFFaEQsT0FBTyxFQUFFLGdCQUFnQixFQUFFLE1BQU0scUJBQXFCLENBQUM7QUFDdkQsT0FBTyxFQUFFLFlBQVksRUFBRSxzQkFBc0IsRUFBOEIsTUFBTSxXQUFXLENBQUM7QUFFN0YsTUFBTSxVQUFVLHdCQUF3QixDQUFDLE9BQW1DO0lBQzFFLE9BQU87UUFDTCxpQkFBaUIsRUFBRSxJQUFJO1FBQ3ZCLGlCQUFpQixFQUFFLEVBQUU7UUFDckIsT0FBTyxFQUFFLE1BQU07UUFDZixZQUFZLENBQUMsQ0FBZTtZQUMxQixPQUFPLElBQUksQ0FBQyxLQUFLLENBQUMsQ0FBQyxDQUFDLElBQUksQ0FBQyxDQUFDO1FBQzVCLENBQUM7UUFDRCxVQUFVLENBQUMsS0FBVTtZQUNuQixPQUFPLElBQUksQ0FBQyxTQUFTLENBQUMsS0FBSyxDQUFDLENBQUM7UUFDL0IsQ0FBQztRQUNELEdBQUcsT0FBTztLQUNYLENBQUM7QUFDSixDQUFDO0FBRUQsTUFBTSxVQUFVLGFBQWEsQ0FBQyxPQUFvQztJQUNoRSxPQUFPO1FBQ0wsRUFBRSxPQUFPLEVBQUUsWUFBWSxFQUFFLFFBQVEsRUFBRSxPQUFPLEVBQUU7UUFDNUM7WUFDRSxPQUFPLEVBQUUsc0JBQXNCO1lBQy9CLFVBQVUsRUFBRSx3QkFBd0I7WUFDcEMsSUFBSSxFQUFFLENBQUMsWUFBWSxDQUFDO1NBQ3JCO1FBQ0Q7WUFDRSxPQUFPLEVBQUUsZUFBZTtZQUN4QixVQUFVLEVBQUUsR0FBRyxFQUFFLENBQUMsR0FBRyxFQUFFLEdBQUUsQ0FBQztZQUMxQixJQUFJLEVBQUUsQ0FBQyxnQkFBZ0IsQ0FBQztZQUN4QixLQUFLLEVBQUUsSUFBSTtTQUNaO0tBQ0YsQ0FBQztBQUNKLENBQUMiLCJzb3VyY2VzQ29udGVudCI6WyJpbXBvcnQgeyBBUFBfSU5JVElBTElaRVIgfSBmcm9tICdAYW5ndWxhci9jb3JlJztcblxuaW1wb3J0IHsgV2ViU29ja2V0SGFuZGxlciB9IGZyb20gJy4vd2Vic29ja2V0LWhhbmRsZXInO1xuaW1wb3J0IHsgVVNFUl9PUFRJT05TLCBOR1hTX1dFQlNPQ0tFVF9PUFRJT05TLCBOZ3hzV2ViU29ja2V0UGx1Z2luT3B0aW9ucyB9IGZyb20gJy4vc3ltYm9scyc7XG5cbmV4cG9ydCBmdW5jdGlvbiDJtXdlYnNvY2tldE9wdGlvbnNGYWN0b3J5KG9wdGlvbnM6IE5neHNXZWJTb2NrZXRQbHVnaW5PcHRpb25zKSB7XG4gIHJldHVybiB7XG4gICAgcmVjb25uZWN0SW50ZXJ2YWw6IDUwMDAsXG4gICAgcmVjb25uZWN0QXR0ZW1wdHM6IDEwLFxuICAgIHR5cGVLZXk6ICd0eXBlJyxcbiAgICBkZXNlcmlhbGl6ZXIoZTogTWVzc2FnZUV2ZW50KSB7XG4gICAgICByZXR1cm4gSlNPTi5wYXJzZShlLmRhdGEpO1xuICAgIH0sXG4gICAgc2VyaWFsaXplcih2YWx1ZTogYW55KSB7XG4gICAgICByZXR1cm4gSlNPTi5zdHJpbmdpZnkodmFsdWUpO1xuICAgIH0sXG4gICAgLi4ub3B0aW9uc1xuICB9O1xufVxuXG5leHBvcnQgZnVuY3Rpb24gybVnZXRQcm92aWRlcnMob3B0aW9ucz86IE5neHNXZWJTb2NrZXRQbHVnaW5PcHRpb25zKSB7XG4gIHJldHVybiBbXG4gICAgeyBwcm92aWRlOiBVU0VSX09QVElPTlMsIHVzZVZhbHVlOiBvcHRpb25zIH0sXG4gICAge1xuICAgICAgcHJvdmlkZTogTkdYU19XRUJTT0NLRVRfT1BUSU9OUyxcbiAgICAgIHVzZUZhY3Rvcnk6IMm1d2Vic29ja2V0T3B0aW9uc0ZhY3RvcnksXG4gICAgICBkZXBzOiBbVVNFUl9PUFRJT05TXVxuICAgIH0sXG4gICAge1xuICAgICAgcHJvdmlkZTogQVBQX0lOSVRJQUxJWkVSLFxuICAgICAgdXNlRmFjdG9yeTogKCkgPT4gKCkgPT4ge30sXG4gICAgICBkZXBzOiBbV2ViU29ja2V0SGFuZGxlcl0sXG4gICAgICBtdWx0aTogdHJ1ZVxuICAgIH1cbiAgXTtcbn1cbiJdfQ==
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
export { NgxsWebSocketPluginModule, withNgxsWebSocketPlugin } from './websocket.module';
|
|
2
|
+
export { ConnectWebSocket, WebSocketMessageError, DisconnectWebSocket, WebSocketDisconnected, SendWebSocketMessage, WebSocketConnectionUpdated, WebSocketConnected } from './symbols';
|
|
3
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoicHVibGljX2FwaS5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uLy4uLy4uL3BhY2thZ2VzL3dlYnNvY2tldC1wbHVnaW4vc3JjL3B1YmxpY19hcGkudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsT0FBTyxFQUFFLHlCQUF5QixFQUFFLHVCQUF1QixFQUFFLE1BQU0sb0JBQW9CLENBQUM7QUFDeEYsT0FBTyxFQUNMLGdCQUFnQixFQUNoQixxQkFBcUIsRUFDckIsbUJBQW1CLEVBQ25CLHFCQUFxQixFQUNyQixvQkFBb0IsRUFDcEIsMEJBQTBCLEVBQzFCLGtCQUFrQixFQUNuQixNQUFNLFdBQVcsQ0FBQyIsInNvdXJjZXNDb250ZW50IjpbImV4cG9ydCB7IE5neHNXZWJTb2NrZXRQbHVnaW5Nb2R1bGUsIHdpdGhOZ3hzV2ViU29ja2V0UGx1Z2luIH0gZnJvbSAnLi93ZWJzb2NrZXQubW9kdWxlJztcbmV4cG9ydCB7XG4gIENvbm5lY3RXZWJTb2NrZXQsXG4gIFdlYlNvY2tldE1lc3NhZ2VFcnJvcixcbiAgRGlzY29ubmVjdFdlYlNvY2tldCxcbiAgV2ViU29ja2V0RGlzY29ubmVjdGVkLFxuICBTZW5kV2ViU29ja2V0TWVzc2FnZSxcbiAgV2ViU29ja2V0Q29ubmVjdGlvblVwZGF0ZWQsXG4gIFdlYlNvY2tldENvbm5lY3RlZFxufSBmcm9tICcuL3N5bWJvbHMnO1xuIl19
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { InjectionToken } from '@angular/core';
|
|
2
|
+
const NG_DEV_MODE = typeof ngDevMode !== 'undefined' && ngDevMode;
|
|
3
|
+
export const NGXS_WEBSOCKET_OPTIONS = new InjectionToken(NG_DEV_MODE ? 'NGXS_WEBSOCKET_OPTIONS' : '');
|
|
4
|
+
export const USER_OPTIONS = new InjectionToken(NG_DEV_MODE ? 'USER_OPTIONS' : '');
|
|
5
|
+
/**
|
|
6
|
+
* Action to connect to the websocket. Optionally pass a URL.
|
|
7
|
+
*/
|
|
8
|
+
export class ConnectWebSocket {
|
|
9
|
+
static { this.type = '[WebSocket] Connect'; }
|
|
10
|
+
constructor(payload) {
|
|
11
|
+
this.payload = payload;
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Action triggered when a error ocurrs
|
|
16
|
+
*/
|
|
17
|
+
export class WebSocketMessageError {
|
|
18
|
+
static { this.type = '[WebSocket] Message Error'; }
|
|
19
|
+
constructor(payload) {
|
|
20
|
+
this.payload = payload;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Action to disconnect the websocket.
|
|
25
|
+
*/
|
|
26
|
+
export class DisconnectWebSocket {
|
|
27
|
+
static { this.type = '[WebSocket] Disconnect'; }
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Action triggered when websocket is connected
|
|
31
|
+
*/
|
|
32
|
+
export class WebSocketConnected {
|
|
33
|
+
static { this.type = '[WebSocket] Connected'; }
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Action triggered when websocket is disconnected
|
|
37
|
+
*/
|
|
38
|
+
export class WebSocketDisconnected {
|
|
39
|
+
static { this.type = '[WebSocket] Disconnected'; }
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Action to send to the server.
|
|
43
|
+
*/
|
|
44
|
+
export class SendWebSocketMessage {
|
|
45
|
+
static { this.type = '[WebSocket] Send Message'; }
|
|
46
|
+
constructor(payload) {
|
|
47
|
+
this.payload = payload;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Action dispatched when the user tries to connect if the connection already exists.
|
|
52
|
+
*/
|
|
53
|
+
export class WebSocketConnectionUpdated {
|
|
54
|
+
static { this.type = '[WebSocket] Connection Updated'; }
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* This error is thrown where there is no `type` (or custom `typeKey`) property
|
|
58
|
+
* on the message that came from the server side socket
|
|
59
|
+
*/
|
|
60
|
+
export class TypeKeyPropertyMissingError extends Error {
|
|
61
|
+
constructor(typeKey) {
|
|
62
|
+
super(`Property ${typeKey} is missing on the socket message`);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoic3ltYm9scy5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uLy4uLy4uL3BhY2thZ2VzL3dlYnNvY2tldC1wbHVnaW4vc3JjL3N5bWJvbHMudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsT0FBTyxFQUFFLGNBQWMsRUFBRSxNQUFNLGVBQWUsQ0FBQztBQUkvQyxNQUFNLFdBQVcsR0FBRyxPQUFPLFNBQVMsS0FBSyxXQUFXLElBQUksU0FBUyxDQUFDO0FBRWxFLE1BQU0sQ0FBQyxNQUFNLHNCQUFzQixHQUFHLElBQUksY0FBYyxDQUN0RCxXQUFXLENBQUMsQ0FBQyxDQUFDLHdCQUF3QixDQUFDLENBQUMsQ0FBQyxFQUFFLENBQzVDLENBQUM7QUFFRixNQUFNLENBQUMsTUFBTSxZQUFZLEdBQUcsSUFBSSxjQUFjLENBQUMsV0FBVyxDQUFDLENBQUMsQ0FBQyxjQUFjLENBQUMsQ0FBQyxDQUFDLEVBQUUsQ0FBQyxDQUFDO0FBb0RsRjs7R0FFRztBQUNILE1BQU0sT0FBTyxnQkFBZ0I7YUFDWCxTQUFJLEdBQUcscUJBQXFCLENBQUM7SUFFN0MsWUFBbUIsT0FBb0M7UUFBcEMsWUFBTyxHQUFQLE9BQU8sQ0FBNkI7SUFBRyxDQUFDOztBQUc3RDs7R0FFRztBQUNILE1BQU0sT0FBTyxxQkFBcUI7YUFDaEIsU0FBSSxHQUFHLDJCQUEyQixDQUFDO0lBRW5ELFlBQW1CLE9BQVk7UUFBWixZQUFPLEdBQVAsT0FBTyxDQUFLO0lBQUcsQ0FBQzs7QUFHckM7O0dBRUc7QUFDSCxNQUFNLE9BQU8sbUJBQW1CO2FBQ2QsU0FBSSxHQUFHLHdCQUF3QixDQUFDOztBQUdsRDs7R0FFRztBQUNILE1BQU0sT0FBTyxrQkFBa0I7YUFDYixTQUFJLEdBQUcsdUJBQXVCLENBQUM7O0FBR2pEOztHQUVHO0FBQ0gsTUFBTSxPQUFPLHFCQUFxQjthQUNoQixTQUFJLEdBQUcsMEJBQTBCLENBQUM7O0FBR3BEOztHQUVHO0FBQ0gsTUFBTSxPQUFPLG9CQUFvQjthQUNmLFNBQUksR0FBRywwQkFBMEIsQ0FBQztJQUVsRCxZQUFtQixPQUFZO1FBQVosWUFBTyxHQUFQLE9BQU8sQ0FBSztJQUFHLENBQUM7O0FBR3JDOztHQUVHO0FBQ0gsTUFBTSxPQUFPLDBCQUEwQjthQUNyQixTQUFJLEdBQUcsZ0NBQWdDLENBQUM7O0FBRzFEOzs7R0FHRztBQUNILE1BQU0sT0FBTywyQkFBNEIsU0FBUSxLQUFLO0lBQ3BELFlBQVksT0FBZTtRQUN6QixLQUFLLENBQUMsWUFBWSxPQUFPLG1DQUFtQyxDQUFDLENBQUM7SUFDaEUsQ0FBQztDQUNGIiwic291cmNlc0NvbnRlbnQiOlsiaW1wb3J0IHsgSW5qZWN0aW9uVG9rZW4gfSBmcm9tICdAYW5ndWxhci9jb3JlJztcblxuZGVjbGFyZSBjb25zdCBuZ0Rldk1vZGU6IGJvb2xlYW47XG5cbmNvbnN0IE5HX0RFVl9NT0RFID0gdHlwZW9mIG5nRGV2TW9kZSAhPT0gJ3VuZGVmaW5lZCcgJiYgbmdEZXZNb2RlO1xuXG5leHBvcnQgY29uc3QgTkdYU19XRUJTT0NLRVRfT1BUSU9OUyA9IG5ldyBJbmplY3Rpb25Ub2tlbjxOZ3hzV2ViU29ja2V0UGx1Z2luT3B0aW9ucz4oXG4gIE5HX0RFVl9NT0RFID8gJ05HWFNfV0VCU09DS0VUX09QVElPTlMnIDogJydcbik7XG5cbmV4cG9ydCBjb25zdCBVU0VSX09QVElPTlMgPSBuZXcgSW5qZWN0aW9uVG9rZW4oTkdfREVWX01PREUgPyAnVVNFUl9PUFRJT05TJyA6ICcnKTtcblxuZXhwb3J0IGludGVyZmFjZSBOZ3hzV2ViU29ja2V0UGx1Z2luT3B0aW9ucyB7XG4gIC8qKlxuICAgKiBVUkwgb2YgdGhlIHdlYnNvY2tldC5cbiAgICovXG4gIHVybD86IHN0cmluZztcblxuICAvKipcbiAgICogRWl0aGVyIGEgc2luZ2xlIHByb3RvY29sIHN0cmluZyBvciBhbiBhcnJheSBvZiBwcm90b2NvbCBzdHJpbmdzLlxuICAgKiBUaGVzZSBzdHJpbmdzIGFyZSB1c2VkIHRvIGluZGljYXRlIHN1Yi1wcm90b2NvbHMsIHNvIHRoYXQgYSBzaW5nbGUgc2VydmVyXG4gICAqIGNhbiBpbXBsZW1lbnQgbXVsdGlwbGUgV2ViU29ja2V0IHN1Yi1wcm90b2NvbHMgKGZvciBleGFtcGxlLCB5b3UgbWlnaHQgd2FudCBvbmUgc2VydmVyIHRvIGJlIGFibGVcbiAgICogdG8gaGFuZGxlIGRpZmZlcmVudCB0eXBlcyBvZiBpbnRlcmFjdGlvbnMgZGVwZW5kaW5nIG9uIHRoZSBzcGVjaWZpZWQgcHJvdG9jb2wpLlxuICAgKiBJZiB5b3UgZG9uJ3Qgc3BlY2lmeSBhIHByb3RvY29sIHN0cmluZywgYW4gZW1wdHkgc3RyaW5nIGlzIGFzc3VtZWQuXG4gICAqL1xuICBwcm90b2NvbD86IHN0cmluZyB8IHN0cmluZ1tdO1xuXG4gIC8qKlxuICAgKiBTZXRzIHRoZSBgYmluYXJ5VHlwZWAgcHJvcGVydHkgb2YgdGhlIHVuZGVybHlpbmcgV2ViU29ja2V0LlxuICAgKi9cbiAgYmluYXJ5VHlwZT86ICdibG9iJyB8ICdhcnJheWJ1ZmZlcic7XG5cbiAgLyoqXG4gICAqIFRoZSBwcm9wZXJ0eSBuYW1lIHRvIGRpc3RpZ3VuaXNoIHRoaXMgdHlwZSBmb3IgdGhlIHN0b3JlLlxuICAgKiBEZWZhdWx0OiAndHlwZSdcbiAgICovXG4gIHR5cGVLZXk/OiBzdHJpbmc7XG5cbiAgLyoqXG4gICAqIEludGVydmFsIHRvIHRyeSBhbmQgcmVjb25uZWN0LlxuICAgKiBEZWZhdWx0OiA1MDAwXG4gICAqL1xuICByZWNvbm5lY3RJbnRlcnZhbD86IG51bWJlcjtcblxuICAvKipcbiAgICogTnVtYmVyIG9mIHJlY29ubmVjdCBhdHRlbXBzLlxuICAgKiBEZWZhdWx0OiAxMFxuICAgKi9cbiAgcmVjb25uZWN0QXR0ZW1wdHM/OiBudW1iZXI7XG5cbiAgLyoqXG4gICAqIFNlcmlhbGl6ZXIgdG8gY2FsbCBiZWZvcmUgc2VuZGluZyBtZXNzYWdlc1xuICAgKiBEZWZhdWx0OiBganNvbi5zdHJpbmdpZnlgXG4gICAqL1xuICBzZXJpYWxpemVyPzogKGRhdGE6IGFueSkgPT4gc3RyaW5nO1xuXG4gIC8qKlxuICAgKiBEZXNlcmFsaXplciBiZWZvcmUgcHVibGlzaGluZyB0aGUgbWVzc2FnZS5cbiAgICovXG4gIGRlc2VyaWFsaXplcj86IChlOiBNZXNzYWdlRXZlbnQpID0+IGFueTtcbn1cblxuLyoqXG4gKiBBY3Rpb24gdG8gY29ubmVjdCB0byB0aGUgd2Vic29ja2V0LiBPcHRpb25hbGx5IHBhc3MgYSBVUkwuXG4gKi9cbmV4cG9ydCBjbGFzcyBDb25uZWN0V2ViU29ja2V0IHtcbiAgc3RhdGljIHJlYWRvbmx5IHR5cGUgPSAnW1dlYlNvY2tldF0gQ29ubmVjdCc7XG5cbiAgY29uc3RydWN0b3IocHVibGljIHBheWxvYWQ/OiBOZ3hzV2ViU29ja2V0UGx1Z2luT3B0aW9ucykge31cbn1cblxuLyoqXG4gKiBBY3Rpb24gdHJpZ2dlcmVkIHdoZW4gYSBlcnJvciBvY3VycnNcbiAqL1xuZXhwb3J0IGNsYXNzIFdlYlNvY2tldE1lc3NhZ2VFcnJvciB7XG4gIHN0YXRpYyByZWFkb25seSB0eXBlID0gJ1tXZWJTb2NrZXRdIE1lc3NhZ2UgRXJyb3InO1xuXG4gIGNvbnN0cnVjdG9yKHB1YmxpYyBwYXlsb2FkOiBhbnkpIHt9XG59XG5cbi8qKlxuICogQWN0aW9uIHRvIGRpc2Nvbm5lY3QgdGhlIHdlYnNvY2tldC5cbiAqL1xuZXhwb3J0IGNsYXNzIERpc2Nvbm5lY3RXZWJTb2NrZXQge1xuICBzdGF0aWMgcmVhZG9ubHkgdHlwZSA9ICdbV2ViU29ja2V0XSBEaXNjb25uZWN0Jztcbn1cblxuLyoqXG4gKiBBY3Rpb24gdHJpZ2dlcmVkIHdoZW4gd2Vic29ja2V0IGlzIGNvbm5lY3RlZFxuICovXG5leHBvcnQgY2xhc3MgV2ViU29ja2V0Q29ubmVjdGVkIHtcbiAgc3RhdGljIHJlYWRvbmx5IHR5cGUgPSAnW1dlYlNvY2tldF0gQ29ubmVjdGVkJztcbn1cblxuLyoqXG4gKiBBY3Rpb24gdHJpZ2dlcmVkIHdoZW4gd2Vic29ja2V0IGlzIGRpc2Nvbm5lY3RlZFxuICovXG5leHBvcnQgY2xhc3MgV2ViU29ja2V0RGlzY29ubmVjdGVkIHtcbiAgc3RhdGljIHJlYWRvbmx5IHR5cGUgPSAnW1dlYlNvY2tldF0gRGlzY29ubmVjdGVkJztcbn1cblxuLyoqXG4gKiBBY3Rpb24gdG8gc2VuZCB0byB0aGUgc2VydmVyLlxuICovXG5leHBvcnQgY2xhc3MgU2VuZFdlYlNvY2tldE1lc3NhZ2Uge1xuICBzdGF0aWMgcmVhZG9ubHkgdHlwZSA9ICdbV2ViU29ja2V0XSBTZW5kIE1lc3NhZ2UnO1xuXG4gIGNvbnN0cnVjdG9yKHB1YmxpYyBwYXlsb2FkOiBhbnkpIHt9XG59XG5cbi8qKlxuICogQWN0aW9uIGRpc3BhdGNoZWQgd2hlbiB0aGUgdXNlciB0cmllcyB0byBjb25uZWN0IGlmIHRoZSBjb25uZWN0aW9uIGFscmVhZHkgZXhpc3RzLlxuICovXG5leHBvcnQgY2xhc3MgV2ViU29ja2V0Q29ubmVjdGlvblVwZGF0ZWQge1xuICBzdGF0aWMgcmVhZG9ubHkgdHlwZSA9ICdbV2ViU29ja2V0XSBDb25uZWN0aW9uIFVwZGF0ZWQnO1xufVxuXG4vKipcbiAqIFRoaXMgZXJyb3IgaXMgdGhyb3duIHdoZXJlIHRoZXJlIGlzIG5vIGB0eXBlYCAob3IgY3VzdG9tIGB0eXBlS2V5YCkgcHJvcGVydHlcbiAqIG9uIHRoZSBtZXNzYWdlIHRoYXQgY2FtZSBmcm9tIHRoZSBzZXJ2ZXIgc2lkZSBzb2NrZXRcbiAqL1xuZXhwb3J0IGNsYXNzIFR5cGVLZXlQcm9wZXJ0eU1pc3NpbmdFcnJvciBleHRlbmRzIEVycm9yIHtcbiAgY29uc3RydWN0b3IodHlwZUtleTogc3RyaW5nKSB7XG4gICAgc3VwZXIoYFByb3BlcnR5ICR7dHlwZUtleX0gaXMgbWlzc2luZyBvbiB0aGUgc29ja2V0IG1lc3NhZ2VgKTtcbiAgfVxufVxuIl19
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
import { Injectable, Inject, NgZone } from '@angular/core';
|
|
2
|
+
import { Actions, Store, ofActionDispatched } from '@ngxs/store';
|
|
3
|
+
import { getValue } from '@ngxs/store/plugins';
|
|
4
|
+
import { ReplaySubject, Subject, fromEvent } from 'rxjs';
|
|
5
|
+
import { takeUntil } from 'rxjs/operators';
|
|
6
|
+
import { ConnectWebSocket, DisconnectWebSocket, SendWebSocketMessage, NGXS_WEBSOCKET_OPTIONS, WebSocketMessageError, WebSocketDisconnected, TypeKeyPropertyMissingError, WebSocketConnectionUpdated, WebSocketConnected } from './symbols';
|
|
7
|
+
import * as i0 from "@angular/core";
|
|
8
|
+
import * as i1 from "@ngxs/store";
|
|
9
|
+
export class WebSocketHandler {
|
|
10
|
+
constructor(_store, _ngZone, _actions$, _options) {
|
|
11
|
+
this._store = _store;
|
|
12
|
+
this._ngZone = _ngZone;
|
|
13
|
+
this._actions$ = _actions$;
|
|
14
|
+
this._options = _options;
|
|
15
|
+
this._socket = null;
|
|
16
|
+
this._socketClosed$ = new Subject();
|
|
17
|
+
this._typeKey = this._options.typeKey;
|
|
18
|
+
this._destroy$ = new ReplaySubject(1);
|
|
19
|
+
this._setupActionsListeners();
|
|
20
|
+
}
|
|
21
|
+
ngOnDestroy() {
|
|
22
|
+
this._disconnect(/* forcelyCloseSocket */ true);
|
|
23
|
+
this._destroy$.next();
|
|
24
|
+
}
|
|
25
|
+
_setupActionsListeners() {
|
|
26
|
+
this._actions$
|
|
27
|
+
.pipe(ofActionDispatched(ConnectWebSocket), takeUntil(this._destroy$))
|
|
28
|
+
.subscribe(({ payload }) => {
|
|
29
|
+
this.connect(payload);
|
|
30
|
+
});
|
|
31
|
+
this._actions$
|
|
32
|
+
.pipe(ofActionDispatched(DisconnectWebSocket), takeUntil(this._destroy$))
|
|
33
|
+
.subscribe(() => {
|
|
34
|
+
this._disconnect(/* forcelyCloseSocket */ true);
|
|
35
|
+
});
|
|
36
|
+
this._actions$
|
|
37
|
+
.pipe(ofActionDispatched(SendWebSocketMessage), takeUntil(this._destroy$))
|
|
38
|
+
.subscribe(({ payload }) => {
|
|
39
|
+
this.send(payload);
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
connect(options) {
|
|
43
|
+
if (this._socket) {
|
|
44
|
+
this._closeConnection(/* forcelyCloseSocket */ true);
|
|
45
|
+
this._store.dispatch(new WebSocketConnectionUpdated());
|
|
46
|
+
}
|
|
47
|
+
// TODO(arturovt): we should not override default config values because this breaks support for having multiple socket connections.
|
|
48
|
+
if (options) {
|
|
49
|
+
if (options.serializer) {
|
|
50
|
+
this._options.serializer = options.serializer;
|
|
51
|
+
}
|
|
52
|
+
if (options.deserializer) {
|
|
53
|
+
this._options.deserializer = options.deserializer;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
this._ngZone.runOutsideAngular(() => {
|
|
57
|
+
// We either use options provided in the `ConnectWebSocket` action
|
|
58
|
+
// or fallback to default config values.
|
|
59
|
+
const url = options?.url || this._options.url;
|
|
60
|
+
const protocol = options?.protocol || this._options.protocol;
|
|
61
|
+
const binaryType = options?.binaryType || this._options.binaryType;
|
|
62
|
+
const socket = (this._socket = protocol
|
|
63
|
+
? new WebSocket(url, protocol)
|
|
64
|
+
: new WebSocket(url));
|
|
65
|
+
if (binaryType) {
|
|
66
|
+
socket.binaryType = binaryType;
|
|
67
|
+
}
|
|
68
|
+
fromEvent(socket, 'open')
|
|
69
|
+
.pipe(takeUntil(this._socketClosed$))
|
|
70
|
+
.subscribe(() => this._store.dispatch(new WebSocketConnected()));
|
|
71
|
+
fromEvent(socket, 'message')
|
|
72
|
+
.pipe(takeUntil(this._socketClosed$))
|
|
73
|
+
.subscribe(event => {
|
|
74
|
+
const message = this._options.deserializer(event);
|
|
75
|
+
const type = getValue(message, this._typeKey);
|
|
76
|
+
if (!type) {
|
|
77
|
+
throw new TypeKeyPropertyMissingError(this._typeKey);
|
|
78
|
+
}
|
|
79
|
+
this._store.dispatch({ ...message, type });
|
|
80
|
+
});
|
|
81
|
+
fromEvent(socket, 'error')
|
|
82
|
+
.pipe(takeUntil(this._socketClosed$))
|
|
83
|
+
.subscribe(error => {
|
|
84
|
+
// The error event indicates that an error has occurred during the
|
|
85
|
+
// WebSocket communication, and it is often appropriate to close the
|
|
86
|
+
// WebSocket connection when such an error occurs.
|
|
87
|
+
// We need to call `_disconnect()` after the error event has been fired.
|
|
88
|
+
// This ensures that the WebSocket connection is properly closed to prevent
|
|
89
|
+
// potential resource leaks.
|
|
90
|
+
this._disconnect(/* forcelyCloseSocket */ true);
|
|
91
|
+
this._store.dispatch(new WebSocketMessageError(error));
|
|
92
|
+
});
|
|
93
|
+
fromEvent(socket, 'close')
|
|
94
|
+
.pipe(takeUntil(this._socketClosed$))
|
|
95
|
+
.subscribe(event => {
|
|
96
|
+
if (event.wasClean) {
|
|
97
|
+
// It is not necessary to call `socket.close()` after the `close` event
|
|
98
|
+
// has been fired. In fact, calling `socket.close()` within the `close`
|
|
99
|
+
// event handler or immediately after the event has been fired can lead
|
|
100
|
+
// to unexpected behavior.
|
|
101
|
+
this._disconnect(/* forcelyCloseSocket */ false);
|
|
102
|
+
}
|
|
103
|
+
else {
|
|
104
|
+
// If the WebSocket `close` event has been fired and its `wasClean`
|
|
105
|
+
// property is falsy, it indicates that the WebSocket connection was
|
|
106
|
+
// closed in an unexpected or abnormal manner.
|
|
107
|
+
// We should call `socket.close()` in this scenario, we can ensure that
|
|
108
|
+
// the WebSocket connection is properly closed.
|
|
109
|
+
this._disconnect(/* forcelyCloseSocket */ true);
|
|
110
|
+
this._store.dispatch(new WebSocketMessageError(event));
|
|
111
|
+
}
|
|
112
|
+
});
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
_disconnect(forcelyCloseSocket) {
|
|
116
|
+
if (this._socket) {
|
|
117
|
+
this._closeConnection(forcelyCloseSocket);
|
|
118
|
+
this._store.dispatch(new WebSocketDisconnected());
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
send(data) {
|
|
122
|
+
if (!this._socket) {
|
|
123
|
+
throw new Error('You must connect to the socket before sending any data');
|
|
124
|
+
}
|
|
125
|
+
try {
|
|
126
|
+
this._socket.send(this._options.serializer(data));
|
|
127
|
+
}
|
|
128
|
+
catch (error) {
|
|
129
|
+
this._store.dispatch(new WebSocketMessageError(error));
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
_closeConnection(forcelyCloseSocket) {
|
|
133
|
+
if (forcelyCloseSocket) {
|
|
134
|
+
this._socket?.close();
|
|
135
|
+
}
|
|
136
|
+
this._socket = null;
|
|
137
|
+
this._socketClosed$.next();
|
|
138
|
+
}
|
|
139
|
+
/** @nocollapse */ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.3.3", ngImport: i0, type: WebSocketHandler, deps: [{ token: i1.Store }, { token: i0.NgZone }, { token: i1.Actions }, { token: NGXS_WEBSOCKET_OPTIONS }], target: i0.ɵɵFactoryTarget.Injectable }); }
|
|
140
|
+
/** @nocollapse */ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "17.3.3", ngImport: i0, type: WebSocketHandler, providedIn: 'root' }); }
|
|
141
|
+
}
|
|
142
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.3", ngImport: i0, type: WebSocketHandler, decorators: [{
|
|
143
|
+
type: Injectable,
|
|
144
|
+
args: [{ providedIn: 'root' }]
|
|
145
|
+
}], ctorParameters: () => [{ type: i1.Store }, { type: i0.NgZone }, { type: i1.Actions }, { type: undefined, decorators: [{
|
|
146
|
+
type: Inject,
|
|
147
|
+
args: [NGXS_WEBSOCKET_OPTIONS]
|
|
148
|
+
}] }] });
|
|
149
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoid2Vic29ja2V0LWhhbmRsZXIuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi8uLi8uLi9wYWNrYWdlcy93ZWJzb2NrZXQtcGx1Z2luL3NyYy93ZWJzb2NrZXQtaGFuZGxlci50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSxPQUFPLEVBQUUsVUFBVSxFQUFFLE1BQU0sRUFBYSxNQUFNLEVBQUUsTUFBTSxlQUFlLENBQUM7QUFDdEUsT0FBTyxFQUFFLE9BQU8sRUFBRSxLQUFLLEVBQUUsa0JBQWtCLEVBQUUsTUFBTSxhQUFhLENBQUM7QUFDakUsT0FBTyxFQUFFLFFBQVEsRUFBRSxNQUFNLHFCQUFxQixDQUFDO0FBQy9DLE9BQU8sRUFBRSxhQUFhLEVBQUUsT0FBTyxFQUFFLFNBQVMsRUFBRSxNQUFNLE1BQU0sQ0FBQztBQUN6RCxPQUFPLEVBQUUsU0FBUyxFQUFFLE1BQU0sZ0JBQWdCLENBQUM7QUFFM0MsT0FBTyxFQUNMLGdCQUFnQixFQUNoQixtQkFBbUIsRUFDbkIsb0JBQW9CLEVBQ3BCLHNCQUFzQixFQUV0QixxQkFBcUIsRUFDckIscUJBQXFCLEVBQ3JCLDJCQUEyQixFQUMzQiwwQkFBMEIsRUFDMUIsa0JBQWtCLEVBQ25CLE1BQU0sV0FBVyxDQUFDOzs7QUFHbkIsTUFBTSxPQUFPLGdCQUFnQjtJQVMzQixZQUNVLE1BQWEsRUFDYixPQUFlLEVBQ2YsU0FBa0IsRUFDYyxRQUFvQztRQUhwRSxXQUFNLEdBQU4sTUFBTSxDQUFPO1FBQ2IsWUFBTyxHQUFQLE9BQU8sQ0FBUTtRQUNmLGNBQVMsR0FBVCxTQUFTLENBQVM7UUFDYyxhQUFRLEdBQVIsUUFBUSxDQUE0QjtRQVp0RSxZQUFPLEdBQXFCLElBQUksQ0FBQztRQUV4QixtQkFBYyxHQUFHLElBQUksT0FBTyxFQUFRLENBQUM7UUFFckMsYUFBUSxHQUFHLElBQUksQ0FBQyxRQUFRLENBQUMsT0FBUSxDQUFDO1FBRWxDLGNBQVMsR0FBRyxJQUFJLGFBQWEsQ0FBTyxDQUFDLENB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O1lBQ0gsSUFBSSxDQUFDLE9BQU8sQ0FBQyxJQUFJLENBQUMsSUFBSSxDQUFDLFFBQVEsQ0FBQyxVQUFXLENBQUMsSUFBSSxDQUFDLENBQUMsQ0FBQztRQUNyRCxDQUFDO1FBQUMsT0FBTyxLQUFLLEVBQUUsQ0FBQztZQUNmLElBQUksQ0FBQyxNQUFNLENBQUMsUUFBUSxDQUFDLElBQUkscUJBQXFCLENBQUMsS0FBSyxDQUFDLENBQUMsQ0FBQztRQUN6RCxDQUFDO0lBQ0gsQ0FBQztJQUVPLGdCQUFnQixDQUFDLGtCQUEyQjtRQUNsRCxJQUFJLGtCQUFrQixFQUFFLENBQUM7WUFDdkIsSUFBSSxDQUFDLE9BQU8sRUFBRSxLQUFLLEVBQUUsQ0FBQztRQUN4QixDQUFDO1FBQ0QsSUFBSSxDQUFDLE9BQU8sR0FBRyxJQUFJLENBQUM7UUFDcEIsSUFBSSxDQUFDLGNBQWMsQ0FBQyxJQUFJLEVBQUUsQ0FBQztJQUM3QixDQUFDO2lJQXRKVSxnQkFBZ0Isb0ZBYWpCLHNCQUFzQjtxSUFickIsZ0JBQWdCLGNBREgsTUFBTTs7MkZBQ25CLGdCQUFnQjtrQkFENUIsVUFBVTttQkFBQyxFQUFFLFVBQVUsRUFBRSxNQUFNLEVBQUU7OzBCQWM3QixNQUFNOzJCQUFDLHNCQUFzQiIsInNvdXJjZXNDb250ZW50IjpbImltcG9ydCB7IEluamVjdGFibGUsIEluamVjdCwgT25EZXN0cm95LCBOZ1pvbmUgfSBmcm9tICdAYW5ndWxhci9jb3JlJztcbmltcG9ydCB7IEFjdGlvbnMsIFN0b3JlLCBvZkFjdGlvbkRpc3BhdGNoZWQgfSBmcm9tICdAbmd4cy9zdG9yZSc7XG5pbXBvcnQgeyBnZXRWYWx1ZSB9IGZyb20gJ0BuZ3hzL3N0b3JlL3BsdWdpbnMnO1xuaW1wb3J0IHsgUmVwbGF5U3ViamVjdCwgU3ViamVjdCwgZnJvbUV2ZW50IH0gZnJvbSAncnhqcyc7XG5pbXBvcnQgeyB0YWtlVW50aWwgfSBmcm9tICdyeGpzL29wZXJhdG9ycyc7XG5cbmltcG9ydCB7XG4gIENvbm5lY3RXZWJTb2NrZXQsXG4gIERpc2Nvbm5lY3RXZWJTb2NrZXQsXG4gIFNlbmRXZWJTb2NrZXRNZXNzYWdlLFxuICBOR1hTX1dFQlNPQ0tFVF9PUFRJT05TLFxuICBOZ3hzV2ViU29ja2V0UGx1Z2luT3B0aW9ucyxcbiAgV2ViU29ja2V0TWVzc2FnZUVycm9yLFxuICBXZWJTb2NrZXREaXNjb25uZWN0ZWQsXG4gIFR5cGVLZXlQcm9wZXJ0eU1pc3NpbmdFcnJvcixcbiAgV2ViU29ja2V0Q29ubmVjdGlvblVwZGF0ZWQsXG4gIFdlYlNvY2tldENvbm5lY3RlZFxufSBmcm9tICcuL3N5bWJvbHMnO1xuXG5ASW5qZWN0YWJsZSh7IHByb3ZpZGVkSW46ICdyb290JyB9KVxuZXhwb3J0IGNsYXNzIFdlYlNvY2tldEhhbmRsZXIgaW1wbGVtZW50cyBPbkRlc3Ryb3kge1xuICBwcml2YXRlIF9zb2NrZXQ6IFdlYlNvY2tldCB8IG51bGwgPSBudWxsO1xuXG4gIHByaXZhdGUgcmVhZG9ubHkgX3NvY2tldENsb3NlZCQgPSBuZXcgU3ViamVjdDx2b2lkPigpO1xuXG4gIHByaXZhdGUgcmVhZG9ubHkgX3R5cGVLZXkgPSB0aGlzLl9vcHRpb25zLnR5cGVLZXkhO1xuXG4gIHByaXZhdGUgcmVhZG9ubHkgX2Rlc3Ryb3kkID0gbmV3IFJlcGxheVN1YmplY3Q8dm9pZD4oMSk7XG5cbiAgY29uc3RydWN0b3IoXG4gICAgcHJpdmF0ZSBfc3RvcmU6IFN0b3JlLFxuICAgIHByaXZhdGUgX25nWm9uZTogTmdab25lLFxuICAgIHByaXZhdGUgX2FjdGlvbnMkOiBBY3Rpb25zLFxuICAgIEBJbmplY3QoTkdYU19XRUJTT0NLRVRfT1BUSU9OUykgcHJpdmF0ZSBfb3B0aW9uczogTmd4c1dlYlNvY2tldFBsdWdpbk9wdGlvbnNcbiAgKSB7XG4gICAgdGhpcy5fc2V0dXBBY3Rpb25zTGlzdGVuZXJzKCk7XG4gIH1cblxuICBuZ09uRGVzdHJveSgpOiB2b2lkIHtcbiAgICB0aGlzLl9kaXNjb25uZWN0KC8qIGZvcmNlbHlDbG9zZVNvY2tldCAqLyB0cnVlKTtcbiAgICB0aGlzLl9kZXN0cm95JC5uZXh0KCk7XG4gIH1cblxuICBwcml2YXRlIF9zZXR1cEFjdGlvbnNMaXN0ZW5lcnMoKTogdm9pZCB7XG4gICAgdGhpcy5fYWN0aW9ucyRcbiAgICAgIC5waXBlKG9mQWN0aW9uRGlzcGF0Y2hlZChDb25uZWN0V2ViU29ja2V0KSwgdGFrZVVudGlsKHRoaXMuX2Rlc3Ryb3kkKSlcbiAgICAgIC5zdWJzY3JpYmUoKHsgcGF5bG9hZCB9KSA9PiB7XG4gICAgICAgIHRoaXMuY29ubmVjdChwYXlsb2FkKTtcbiAgICAgIH0pO1xuXG4gICAgdGhpcy5fYWN0aW9ucyRcbiAgICAgIC5waXBlKG9mQWN0aW9uRGlzcGF0Y2hlZChEaXNjb25uZWN0V2ViU29ja2V0KSwgdGFrZVVudGlsKHRoaXMuX2Rlc3Ryb3kkKSlcbiAgICAgIC5zdWJzY3JpYmUoKCkgPT4ge1xuICAgICAgICB0aGlzLl9kaXNjb25uZWN0KC8qIGZvcmNlbHlDbG9zZVNvY2tldCAqLyB0cnVlKTtcbiAgICAgIH0pO1xuXG4gICAgdGhpcy5fYWN0aW9ucyRcbiAgICAgIC5waXBlKG9mQWN0aW9uRGlzcGF0Y2hlZChTZW5kV2ViU29ja2V0TWVzc2FnZSksIHRha2VVbnRpbCh0aGlzLl9kZXN0cm95JCkpXG4gICAgICAuc3Vic2NyaWJlKCh7IHBheWxvYWQgfSkgPT4ge1xuICAgICAgICB0aGlzLnNlbmQocGF5bG9hZCk7XG4gICAgICB9KTtcbiAgfVxuXG4gIHByaXZhdGUgY29ubmVjdChvcHRpb25zPzogTmd4c1dlYlNvY2tldFBsdWdpbk9wdGlvbnMpOiB2b2lkIHtcbiAgICBpZiAodGhpcy5fc29ja2V0KSB7XG4gICAgICB0aGlzLl9jbG9zZUNvbm5lY3Rpb24oLyogZm9yY2VseUNsb3NlU29ja2V0ICovIHRydWUpO1xuICAgICAgdGhpcy5fc3RvcmUuZGlzcGF0Y2gobmV3IFdlYlNvY2tldENvbm5lY3Rpb25VcGRhdGVkKCkpO1xuICAgIH1cblxuICAgIC8vIFRPRE8oYXJ0dXJvdnQpOiB3ZSBzaG91bGQgbm90IG92ZXJyaWRlIGRlZmF1bHQgY29uZmlnIHZhbHVlcyBiZWNhdXNlIHRoaXMgYnJlYWtzIHN1cHBvcnQgZm9yIGhhdmluZyBtdWx0aXBsZSBzb2NrZXQgY29ubmVjdGlvbnMuXG4gICAgaWYgKG9wdGlvbnMpIHtcbiAgICAgIGlmIChvcHRpb25zLnNlcmlhbGl6ZXIpIHtcbiAgICAgICAgdGhpcy5fb3B0aW9ucy5zZXJpYWxpemVyID0gb3B0aW9ucy5zZXJpYWxpemVyO1xuICAgICAgfVxuXG4gICAgICBpZiAob3B0aW9ucy5kZXNlcmlhbGl6ZXIpIHtcbiAgICAgICAgdGhpcy5fb3B0aW9ucy5kZXNlcmlhbGl6ZXIgPSBvcHRpb25zLmRlc2VyaWFsaXplcjtcbiAgICAgIH1cbiAgICB9XG5cbiAgICB0aGlzLl9uZ1pvbmUucnVuT3V0c2lkZUFuZ3VsYXIoKCkgPT4ge1xuICAgICAgLy8gV2UgZWl0aGVyIHVzZSBvcHRpb25zIHByb3ZpZGVkIGluIHRoZSBgQ29ubmVjdFdlYlNvY2tldGAgYWN0aW9uXG4gICAgICAvLyBvciBmYWxsYmFjayB0byBkZWZhdWx0IGNvbmZpZyB2YWx1ZXMuXG4gICAgICBjb25zdCB1cmwgPSBvcHRpb25zPy51cmwgfHwgdGhpcy5fb3B0aW9ucy51cmwhO1xuICAgICAgY29uc3QgcHJvdG9jb2wgPSBvcHRpb25zPy5wcm90b2NvbCB8fCB0aGlzLl9vcHRpb25zLnByb3RvY29sO1xuICAgICAgY29uc3QgYmluYXJ5VHlwZSA9IG9wdGlvbnM/LmJpbmFyeVR5cGUgfHwgdGhpcy5fb3B0aW9ucy5iaW5hcnlUeXBlO1xuXG4gICAgICBjb25zdCBzb2NrZXQgPSAodGhpcy5fc29ja2V0ID0gcHJvdG9jb2xcbiAgICAgICAgPyBuZXcgV2ViU29ja2V0KHVybCwgcHJvdG9jb2wpXG4gICAgICAgIDogbmV3IFdlYlNvY2tldCh1cmwpKTtcblxuICAgICAgaWYgKGJpbmFyeVR5cGUpIHtcbiAgICAgICAgc29ja2V0LmJpbmFyeVR5cGUgPSBiaW5hcnlUeXBlO1xuICAgICAgfVxuXG4gICAgICBmcm9tRXZlbnQoc29ja2V0LCAnb3BlbicpXG4gICAgICAgIC5waXBlKHRha2VVbnRpbCh0aGlzLl9zb2NrZXRDbG9zZWQkKSlcbiAgICAgICAgLnN1YnNjcmliZSgoKSA9PiB0aGlzLl9zdG9yZS5kaXNwYXRjaChuZXcgV2ViU29ja2V0Q29ubmVjdGVkKCkpKTtcblxuICAgICAgZnJvbUV2ZW50PE1lc3NhZ2VFdmVudD4oc29ja2V0LCAnbWVzc2FnZScpXG4gICAgICAgIC5waXBlKHRha2VVbnRpbCh0aGlzLl9zb2NrZXRDbG9zZWQkKSlcbiAgICAgICAgLnN1YnNjcmliZShldmVudCA9PiB7XG4gICAgICAgICAgY29uc3QgbWVzc2FnZSA9IHRoaXMuX29wdGlvbnMuZGVzZXJpYWxpemVyIShldmVudCk7XG4gICAgICAgICAgY29uc3QgdHlwZSA9IGdldFZhbHVlKG1lc3NhZ2UsIHRoaXMuX3R5cGVLZXkpO1xuICAgICAgICAgIGlmICghdHlwZSkge1xuICAgICAgICAgICAgdGhyb3cgbmV3IFR5cGVLZXlQcm9wZXJ0eU1pc3NpbmdFcnJvcih0aGlzLl90eXBlS2V5KTtcbiAgICAgICAgICB9XG4gICAgICAgICAgdGhpcy5fc3RvcmUuZGlzcGF0Y2goeyAuLi5tZXNzYWdlLCB0eXBlIH0pO1xuICAgICAgICB9KTtcblxuICAgICAgZnJvbUV2ZW50KHNvY2tldCwgJ2Vycm9yJylcbiAgICAgICAgLnBpcGUodGFrZVVudGlsKHRoaXMuX3NvY2tldENsb3NlZCQpKVxuICAgICAgICAuc3Vic2NyaWJlKGVycm9yID0+IHtcbiAgICAgICAgICAvLyBUaGUgZXJyb3IgZXZlbnQgaW5kaWNhdGVzIHRoYXQgYW4gZXJyb3IgaGFzIG9jY3VycmVkIGR1cmluZyB0aGVcbiAgICAgICAgICAvLyBXZWJTb2NrZXQgY29tbXVuaWNhdGlvbiwgYW5kIGl0IGlzIG9mdGVuIGFwcHJvcHJpYXRlIHRvIGNsb3NlIHRoZVxuICAgICAgICAgIC8vIFdlYlNvY2tldCBjb25uZWN0aW9uIHdoZW4gc3VjaCBhbiBlcnJvciBvY2N1cnMuXG4gICAgICAgICAgLy8gV2UgbmVlZCB0byBjYWxsIGBfZGlzY29ubmVjdCgpYCBhZnRlciB0aGUgZXJyb3IgZXZlbnQgaGFzIGJlZW4gZmlyZWQuXG4gICAgICAgICAgLy8gVGhpcyBlbnN1cmVzIHRoYXQgdGhlIFdlYlNvY2tldCBjb25uZWN0aW9uIGlzIHByb3Blcmx5IGNsb3NlZCB0byBwcmV2ZW50XG4gICAgICAgICAgLy8gcG90ZW50aWFsIHJlc291cmNlIGxlYWtzLlxuICAgICAgICAgIHRoaXMuX2Rpc2Nvbm5lY3QoLyogZm9yY2VseUNsb3NlU29ja2V0ICovIHRydWUpO1xuICAgICAgICAgIHRoaXMuX3N0b3JlLmRpc3BhdGNoKG5ldyBXZWJTb2NrZXRNZXNzYWdlRXJyb3IoZXJyb3IpKTtcbiAgICAgICAgfSk7XG5cbiAgICAgIGZyb21FdmVudDxDbG9zZUV2ZW50Pihzb2NrZXQsICdjbG9zZScpXG4gICAgICAgIC5waXBlKHRha2VVbnRpbCh0aGlzLl9zb2NrZXRDbG9zZWQkKSlcbiAgICAgICAgLnN1YnNjcmliZShldmVudCA9PiB7XG4gICAgICAgICAgaWYgKGV2ZW50Lndhc0NsZWFuKSB7XG4gICAgICAgICAgICAvLyBJdCBpcyBub3QgbmVjZXNzYXJ5IHRvIGNhbGwgYHNvY2tldC5jbG9zZSgpYCBhZnRlciB0aGUgYGNsb3NlYCBldmVudFxuICAgICAgICAgICAgLy8gaGFzIGJlZW4gZmlyZWQuIEluIGZhY3QsIGNhbGxpbmcgYHNvY2tldC5jbG9zZSgpYCB3aXRoaW4gdGhlIGBjbG9zZWBcbiAgICAgICAgICAgIC8vIGV2ZW50IGhhbmRsZXIgb3IgaW1tZWRpYXRlbHkgYWZ0ZXIgdGhlIGV2ZW50IGhhcyBiZWVuIGZpcmVkIGNhbiBsZWFkXG4gICAgICAgICAgICAvLyB0byB1bmV4cGVjdGVkIGJlaGF2aW9yLlxuICAgICAgICAgICAgdGhpcy5fZGlzY29ubmVjdCgvKiBmb3JjZWx5Q2xvc2VTb2NrZXQgKi8gZmFsc2UpO1xuICAgICAgICAgIH0gZWxzZSB7XG4gICAgICAgICAgICAvLyBJZiB0aGUgV2ViU29ja2V0IGBjbG9zZWAgZXZlbnQgaGFzIGJlZW4gZmlyZWQgYW5kIGl0cyBgd2FzQ2xlYW5gXG4gICAgICAgICAgICAvLyBwcm9wZXJ0eSBpcyBmYWxzeSwgaXQgaW5kaWNhdGVzIHRoYXQgdGhlIFdlYlNvY2tldCBjb25uZWN0aW9uIHdhc1xuICAgICAgICAgICAgLy8gY2xvc2VkIGluIGFuIHVuZXhwZWN0ZWQgb3IgYWJub3JtYWwgbWFubmVyLlxuICAgICAgICAgICAgLy8gV2Ugc2hvdWxkIGNhbGwgYHNvY2tldC5jbG9zZSgpYCBpbiB0aGlzIHNjZW5hcmlvLCB3ZSBjYW4gZW5zdXJlIHRoYXRcbiAgICAgICAgICAgIC8vIHRoZSBXZWJTb2NrZXQgY29ubmVjdGlvbiBpcyBwcm9wZXJseSBjbG9zZWQuXG4gICAgICAgICAgICB0aGlzLl9kaXNjb25uZWN0KC8qIGZvcmNlbHlDbG9zZVNvY2tldCAqLyB0cnVlKTtcbiAgICAgICAgICAgIHRoaXMuX3N0b3JlLmRpc3BhdGNoKG5ldyBXZWJTb2NrZXRNZXNzYWdlRXJyb3IoZXZlbnQpKTtcbiAgICAgICAgICB9XG4gICAgICAgIH0pO1xuICAgIH0pO1xuICB9XG5cbiAgcHJpdmF0ZSBfZGlzY29ubmVjdChmb3JjZWx5Q2xvc2VTb2NrZXQ6IGJvb2xlYW4pOiB2b2lkIHtcbiAgICBpZiAodGhpcy5fc29ja2V0KSB7XG4gICAgICB0aGlzLl9jbG9zZUNvbm5lY3Rpb24oZm9yY2VseUNsb3NlU29ja2V0KTtcbiAgICAgIHRoaXMuX3N0b3JlLmRpc3BhdGNoKG5ldyBXZWJTb2NrZXREaXNjb25uZWN0ZWQoKSk7XG4gICAgfVxuICB9XG5cbiAgcHJpdmF0ZSBzZW5kKGRhdGE6IGFueSk6IHZvaWQge1xuICAgIGlmICghdGhpcy5fc29ja2V0KSB7XG4gICAgICB0aHJvdyBuZXcgRXJyb3IoJ1lvdSBtdXN0IGNvbm5lY3QgdG8gdGhlIHNvY2tldCBiZWZvcmUgc2VuZGluZyBhbnkgZGF0YScpO1xuICAgIH1cblxuICAgIHRyeSB7XG4gICAgICB0aGlzLl9zb2NrZXQuc2VuZCh0aGlzLl9vcHRpb25zLnNlcmlhbGl6ZXIhKGRhdGEpKTtcbiAgICB9IGNhdGNoIChlcnJvcikge1xuICAgICAgdGhpcy5fc3RvcmUuZGlzcGF0Y2gobmV3IFdlYlNvY2tldE1lc3NhZ2VFcnJvcihlcnJvcikpO1xuICAgIH1cbiAgfVxuXG4gIHByaXZhdGUgX2Nsb3NlQ29ubmVjdGlvbihmb3JjZWx5Q2xvc2VTb2NrZXQ6IGJvb2xlYW4pOiB2b2lkIHtcbiAgICBpZiAoZm9yY2VseUNsb3NlU29ja2V0KSB7XG4gICAgICB0aGlzLl9zb2NrZXQ/LmNsb3NlKCk7XG4gICAgfVxuICAgIHRoaXMuX3NvY2tldCA9IG51bGw7XG4gICAgdGhpcy5fc29ja2V0Q2xvc2VkJC5uZXh0KCk7XG4gIH1cbn1cbiJdfQ==
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { NgModule, makeEnvironmentProviders } from '@angular/core';
|
|
2
|
+
import { ɵgetProviders } from './providers';
|
|
3
|
+
import * as i0 from "@angular/core";
|
|
4
|
+
export class NgxsWebSocketPluginModule {
|
|
5
|
+
static forRoot(options) {
|
|
6
|
+
return {
|
|
7
|
+
ngModule: NgxsWebSocketPluginModule,
|
|
8
|
+
providers: ɵgetProviders(options)
|
|
9
|
+
};
|
|
10
|
+
}
|
|
11
|
+
/** @nocollapse */ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.3.3", ngImport: i0, type: NgxsWebSocketPluginModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule }); }
|
|
12
|
+
/** @nocollapse */ static { this.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "17.3.3", ngImport: i0, type: NgxsWebSocketPluginModule }); }
|
|
13
|
+
/** @nocollapse */ static { this.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "17.3.3", ngImport: i0, type: NgxsWebSocketPluginModule }); }
|
|
14
|
+
}
|
|
15
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.3", ngImport: i0, type: NgxsWebSocketPluginModule, decorators: [{
|
|
16
|
+
type: NgModule
|
|
17
|
+
}] });
|
|
18
|
+
export function withNgxsWebSocketPlugin(options) {
|
|
19
|
+
return makeEnvironmentProviders(ɵgetProviders(options));
|
|
20
|
+
}
|
|
21
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoid2Vic29ja2V0Lm1vZHVsZS5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uLy4uLy4uL3BhY2thZ2VzL3dlYnNvY2tldC1wbHVnaW4vc3JjL3dlYnNvY2tldC5tb2R1bGUudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsT0FBTyxFQUNMLFFBQVEsRUFHUix3QkFBd0IsRUFDekIsTUFBTSxlQUFlLENBQUM7QUFFdkIsT0FBTyxFQUFFLGFBQWEsRUFBRSxNQUFNLGFBQWEsQ0FBQzs7QUFJNUMsTUFBTSxPQUFPLHlCQUF5QjtJQUNwQyxNQUFNLENBQUMsT0FBTyxDQUNaLE9BQW9DO1FBRXBDLE9BQU87WUFDTCxRQUFRLEVBQUUseUJBQXlCO1lBQ25DLFNBQVMsRUFBRSxhQUFhLENBQUMsT0FBTyxDQUFDO1NBQ2xDLENBQUM7SUFDSixDQUFDO2lJQVJVLHlCQUF5QjtrSUFBekIseUJBQXlCO2tJQUF6Qix5QkFBeUI7OzJGQUF6Qix5QkFBeUI7a0JBRHJDLFFBQVE7O0FBWVQsTUFBTSxVQUFVLHVCQUF1QixDQUNyQyxPQUFvQztJQUVwQyxPQUFPLHdCQUF3QixDQUFDLGFBQWEsQ0FBQyxPQUFPLENBQUMsQ0FBQyxDQUFDO0FBQzFELENBQUMiLCJzb3VyY2VzQ29udGVudCI6WyJpbXBvcnQge1xuICBOZ01vZHVsZSxcbiAgTW9kdWxlV2l0aFByb3ZpZGVycyxcbiAgRW52aXJvbm1lbnRQcm92aWRlcnMsXG4gIG1ha2VFbnZpcm9ubWVudFByb3ZpZGVyc1xufSBmcm9tICdAYW5ndWxhci9jb3JlJztcblxuaW1wb3J0IHsgybVnZXRQcm92aWRlcnMgfSBmcm9tICcuL3Byb3ZpZGVycyc7XG5pbXBvcnQgeyBOZ3hzV2ViU29ja2V0UGx1Z2luT3B0aW9ucyB9IGZyb20gJy4vc3ltYm9scyc7XG5cbkBOZ01vZHVsZSgpXG5leHBvcnQgY2xhc3MgTmd4c1dlYlNvY2tldFBsdWdpbk1vZHVsZSB7XG4gIHN0YXRpYyBmb3JSb290KFxuICAgIG9wdGlvbnM/OiBOZ3hzV2ViU29ja2V0UGx1Z2luT3B0aW9uc1xuICApOiBNb2R1bGVXaXRoUHJvdmlkZXJzPE5neHNXZWJTb2NrZXRQbHVnaW5Nb2R1bGU+IHtcbiAgICByZXR1cm4ge1xuICAgICAgbmdNb2R1bGU6IE5neHNXZWJTb2NrZXRQbHVnaW5Nb2R1bGUsXG4gICAgICBwcm92aWRlcnM6IMm1Z2V0UHJvdmlkZXJzKG9wdGlvbnMpXG4gICAgfTtcbiAgfVxufVxuXG5leHBvcnQgZnVuY3Rpb24gd2l0aE5neHNXZWJTb2NrZXRQbHVnaW4oXG4gIG9wdGlvbnM/OiBOZ3hzV2ViU29ja2V0UGx1Z2luT3B0aW9uc1xuKTogRW52aXJvbm1lbnRQcm92aWRlcnMge1xuICByZXR1cm4gbWFrZUVudmlyb25tZW50UHJvdmlkZXJzKMm1Z2V0UHJvdmlkZXJzKG9wdGlvbnMpKTtcbn1cbiJdfQ==
|
|
@@ -0,0 +1,272 @@
|
|
|
1
|
+
import * as i0 from '@angular/core';
|
|
2
|
+
import { InjectionToken, Injectable, Inject, APP_INITIALIZER, NgModule, makeEnvironmentProviders } from '@angular/core';
|
|
3
|
+
import * as i1 from '@ngxs/store';
|
|
4
|
+
import { ofActionDispatched } from '@ngxs/store';
|
|
5
|
+
import { getValue } from '@ngxs/store/plugins';
|
|
6
|
+
import { Subject, ReplaySubject, fromEvent } from 'rxjs';
|
|
7
|
+
import { takeUntil } from 'rxjs/operators';
|
|
8
|
+
|
|
9
|
+
const NG_DEV_MODE = typeof ngDevMode !== 'undefined' && ngDevMode;
|
|
10
|
+
const NGXS_WEBSOCKET_OPTIONS = new InjectionToken(NG_DEV_MODE ? 'NGXS_WEBSOCKET_OPTIONS' : '');
|
|
11
|
+
const USER_OPTIONS = new InjectionToken(NG_DEV_MODE ? 'USER_OPTIONS' : '');
|
|
12
|
+
/**
|
|
13
|
+
* Action to connect to the websocket. Optionally pass a URL.
|
|
14
|
+
*/
|
|
15
|
+
class ConnectWebSocket {
|
|
16
|
+
static { this.type = '[WebSocket] Connect'; }
|
|
17
|
+
constructor(payload) {
|
|
18
|
+
this.payload = payload;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Action triggered when a error ocurrs
|
|
23
|
+
*/
|
|
24
|
+
class WebSocketMessageError {
|
|
25
|
+
static { this.type = '[WebSocket] Message Error'; }
|
|
26
|
+
constructor(payload) {
|
|
27
|
+
this.payload = payload;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Action to disconnect the websocket.
|
|
32
|
+
*/
|
|
33
|
+
class DisconnectWebSocket {
|
|
34
|
+
static { this.type = '[WebSocket] Disconnect'; }
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Action triggered when websocket is connected
|
|
38
|
+
*/
|
|
39
|
+
class WebSocketConnected {
|
|
40
|
+
static { this.type = '[WebSocket] Connected'; }
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Action triggered when websocket is disconnected
|
|
44
|
+
*/
|
|
45
|
+
class WebSocketDisconnected {
|
|
46
|
+
static { this.type = '[WebSocket] Disconnected'; }
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Action to send to the server.
|
|
50
|
+
*/
|
|
51
|
+
class SendWebSocketMessage {
|
|
52
|
+
static { this.type = '[WebSocket] Send Message'; }
|
|
53
|
+
constructor(payload) {
|
|
54
|
+
this.payload = payload;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Action dispatched when the user tries to connect if the connection already exists.
|
|
59
|
+
*/
|
|
60
|
+
class WebSocketConnectionUpdated {
|
|
61
|
+
static { this.type = '[WebSocket] Connection Updated'; }
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* This error is thrown where there is no `type` (or custom `typeKey`) property
|
|
65
|
+
* on the message that came from the server side socket
|
|
66
|
+
*/
|
|
67
|
+
class TypeKeyPropertyMissingError extends Error {
|
|
68
|
+
constructor(typeKey) {
|
|
69
|
+
super(`Property ${typeKey} is missing on the socket message`);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
class WebSocketHandler {
|
|
74
|
+
constructor(_store, _ngZone, _actions$, _options) {
|
|
75
|
+
this._store = _store;
|
|
76
|
+
this._ngZone = _ngZone;
|
|
77
|
+
this._actions$ = _actions$;
|
|
78
|
+
this._options = _options;
|
|
79
|
+
this._socket = null;
|
|
80
|
+
this._socketClosed$ = new Subject();
|
|
81
|
+
this._typeKey = this._options.typeKey;
|
|
82
|
+
this._destroy$ = new ReplaySubject(1);
|
|
83
|
+
this._setupActionsListeners();
|
|
84
|
+
}
|
|
85
|
+
ngOnDestroy() {
|
|
86
|
+
this._disconnect(/* forcelyCloseSocket */ true);
|
|
87
|
+
this._destroy$.next();
|
|
88
|
+
}
|
|
89
|
+
_setupActionsListeners() {
|
|
90
|
+
this._actions$
|
|
91
|
+
.pipe(ofActionDispatched(ConnectWebSocket), takeUntil(this._destroy$))
|
|
92
|
+
.subscribe(({ payload }) => {
|
|
93
|
+
this.connect(payload);
|
|
94
|
+
});
|
|
95
|
+
this._actions$
|
|
96
|
+
.pipe(ofActionDispatched(DisconnectWebSocket), takeUntil(this._destroy$))
|
|
97
|
+
.subscribe(() => {
|
|
98
|
+
this._disconnect(/* forcelyCloseSocket */ true);
|
|
99
|
+
});
|
|
100
|
+
this._actions$
|
|
101
|
+
.pipe(ofActionDispatched(SendWebSocketMessage), takeUntil(this._destroy$))
|
|
102
|
+
.subscribe(({ payload }) => {
|
|
103
|
+
this.send(payload);
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
connect(options) {
|
|
107
|
+
if (this._socket) {
|
|
108
|
+
this._closeConnection(/* forcelyCloseSocket */ true);
|
|
109
|
+
this._store.dispatch(new WebSocketConnectionUpdated());
|
|
110
|
+
}
|
|
111
|
+
// TODO(arturovt): we should not override default config values because this breaks support for having multiple socket connections.
|
|
112
|
+
if (options) {
|
|
113
|
+
if (options.serializer) {
|
|
114
|
+
this._options.serializer = options.serializer;
|
|
115
|
+
}
|
|
116
|
+
if (options.deserializer) {
|
|
117
|
+
this._options.deserializer = options.deserializer;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
this._ngZone.runOutsideAngular(() => {
|
|
121
|
+
// We either use options provided in the `ConnectWebSocket` action
|
|
122
|
+
// or fallback to default config values.
|
|
123
|
+
const url = options?.url || this._options.url;
|
|
124
|
+
const protocol = options?.protocol || this._options.protocol;
|
|
125
|
+
const binaryType = options?.binaryType || this._options.binaryType;
|
|
126
|
+
const socket = (this._socket = protocol
|
|
127
|
+
? new WebSocket(url, protocol)
|
|
128
|
+
: new WebSocket(url));
|
|
129
|
+
if (binaryType) {
|
|
130
|
+
socket.binaryType = binaryType;
|
|
131
|
+
}
|
|
132
|
+
fromEvent(socket, 'open')
|
|
133
|
+
.pipe(takeUntil(this._socketClosed$))
|
|
134
|
+
.subscribe(() => this._store.dispatch(new WebSocketConnected()));
|
|
135
|
+
fromEvent(socket, 'message')
|
|
136
|
+
.pipe(takeUntil(this._socketClosed$))
|
|
137
|
+
.subscribe(event => {
|
|
138
|
+
const message = this._options.deserializer(event);
|
|
139
|
+
const type = getValue(message, this._typeKey);
|
|
140
|
+
if (!type) {
|
|
141
|
+
throw new TypeKeyPropertyMissingError(this._typeKey);
|
|
142
|
+
}
|
|
143
|
+
this._store.dispatch({ ...message, type });
|
|
144
|
+
});
|
|
145
|
+
fromEvent(socket, 'error')
|
|
146
|
+
.pipe(takeUntil(this._socketClosed$))
|
|
147
|
+
.subscribe(error => {
|
|
148
|
+
// The error event indicates that an error has occurred during the
|
|
149
|
+
// WebSocket communication, and it is often appropriate to close the
|
|
150
|
+
// WebSocket connection when such an error occurs.
|
|
151
|
+
// We need to call `_disconnect()` after the error event has been fired.
|
|
152
|
+
// This ensures that the WebSocket connection is properly closed to prevent
|
|
153
|
+
// potential resource leaks.
|
|
154
|
+
this._disconnect(/* forcelyCloseSocket */ true);
|
|
155
|
+
this._store.dispatch(new WebSocketMessageError(error));
|
|
156
|
+
});
|
|
157
|
+
fromEvent(socket, 'close')
|
|
158
|
+
.pipe(takeUntil(this._socketClosed$))
|
|
159
|
+
.subscribe(event => {
|
|
160
|
+
if (event.wasClean) {
|
|
161
|
+
// It is not necessary to call `socket.close()` after the `close` event
|
|
162
|
+
// has been fired. In fact, calling `socket.close()` within the `close`
|
|
163
|
+
// event handler or immediately after the event has been fired can lead
|
|
164
|
+
// to unexpected behavior.
|
|
165
|
+
this._disconnect(/* forcelyCloseSocket */ false);
|
|
166
|
+
}
|
|
167
|
+
else {
|
|
168
|
+
// If the WebSocket `close` event has been fired and its `wasClean`
|
|
169
|
+
// property is falsy, it indicates that the WebSocket connection was
|
|
170
|
+
// closed in an unexpected or abnormal manner.
|
|
171
|
+
// We should call `socket.close()` in this scenario, we can ensure that
|
|
172
|
+
// the WebSocket connection is properly closed.
|
|
173
|
+
this._disconnect(/* forcelyCloseSocket */ true);
|
|
174
|
+
this._store.dispatch(new WebSocketMessageError(event));
|
|
175
|
+
}
|
|
176
|
+
});
|
|
177
|
+
});
|
|
178
|
+
}
|
|
179
|
+
_disconnect(forcelyCloseSocket) {
|
|
180
|
+
if (this._socket) {
|
|
181
|
+
this._closeConnection(forcelyCloseSocket);
|
|
182
|
+
this._store.dispatch(new WebSocketDisconnected());
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
send(data) {
|
|
186
|
+
if (!this._socket) {
|
|
187
|
+
throw new Error('You must connect to the socket before sending any data');
|
|
188
|
+
}
|
|
189
|
+
try {
|
|
190
|
+
this._socket.send(this._options.serializer(data));
|
|
191
|
+
}
|
|
192
|
+
catch (error) {
|
|
193
|
+
this._store.dispatch(new WebSocketMessageError(error));
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
_closeConnection(forcelyCloseSocket) {
|
|
197
|
+
if (forcelyCloseSocket) {
|
|
198
|
+
this._socket?.close();
|
|
199
|
+
}
|
|
200
|
+
this._socket = null;
|
|
201
|
+
this._socketClosed$.next();
|
|
202
|
+
}
|
|
203
|
+
/** @nocollapse */ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.3.3", ngImport: i0, type: WebSocketHandler, deps: [{ token: i1.Store }, { token: i0.NgZone }, { token: i1.Actions }, { token: NGXS_WEBSOCKET_OPTIONS }], target: i0.ɵɵFactoryTarget.Injectable }); }
|
|
204
|
+
/** @nocollapse */ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "17.3.3", ngImport: i0, type: WebSocketHandler, providedIn: 'root' }); }
|
|
205
|
+
}
|
|
206
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.3", ngImport: i0, type: WebSocketHandler, decorators: [{
|
|
207
|
+
type: Injectable,
|
|
208
|
+
args: [{ providedIn: 'root' }]
|
|
209
|
+
}], ctorParameters: () => [{ type: i1.Store }, { type: i0.NgZone }, { type: i1.Actions }, { type: undefined, decorators: [{
|
|
210
|
+
type: Inject,
|
|
211
|
+
args: [NGXS_WEBSOCKET_OPTIONS]
|
|
212
|
+
}] }] });
|
|
213
|
+
|
|
214
|
+
function ɵwebsocketOptionsFactory(options) {
|
|
215
|
+
return {
|
|
216
|
+
reconnectInterval: 5000,
|
|
217
|
+
reconnectAttempts: 10,
|
|
218
|
+
typeKey: 'type',
|
|
219
|
+
deserializer(e) {
|
|
220
|
+
return JSON.parse(e.data);
|
|
221
|
+
},
|
|
222
|
+
serializer(value) {
|
|
223
|
+
return JSON.stringify(value);
|
|
224
|
+
},
|
|
225
|
+
...options
|
|
226
|
+
};
|
|
227
|
+
}
|
|
228
|
+
function ɵgetProviders(options) {
|
|
229
|
+
return [
|
|
230
|
+
{ provide: USER_OPTIONS, useValue: options },
|
|
231
|
+
{
|
|
232
|
+
provide: NGXS_WEBSOCKET_OPTIONS,
|
|
233
|
+
useFactory: ɵwebsocketOptionsFactory,
|
|
234
|
+
deps: [USER_OPTIONS]
|
|
235
|
+
},
|
|
236
|
+
{
|
|
237
|
+
provide: APP_INITIALIZER,
|
|
238
|
+
useFactory: () => () => { },
|
|
239
|
+
deps: [WebSocketHandler],
|
|
240
|
+
multi: true
|
|
241
|
+
}
|
|
242
|
+
];
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
class NgxsWebSocketPluginModule {
|
|
246
|
+
static forRoot(options) {
|
|
247
|
+
return {
|
|
248
|
+
ngModule: NgxsWebSocketPluginModule,
|
|
249
|
+
providers: ɵgetProviders(options)
|
|
250
|
+
};
|
|
251
|
+
}
|
|
252
|
+
/** @nocollapse */ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.3.3", ngImport: i0, type: NgxsWebSocketPluginModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule }); }
|
|
253
|
+
/** @nocollapse */ static { this.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "17.3.3", ngImport: i0, type: NgxsWebSocketPluginModule }); }
|
|
254
|
+
/** @nocollapse */ static { this.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "17.3.3", ngImport: i0, type: NgxsWebSocketPluginModule }); }
|
|
255
|
+
}
|
|
256
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.3", ngImport: i0, type: NgxsWebSocketPluginModule, decorators: [{
|
|
257
|
+
type: NgModule
|
|
258
|
+
}] });
|
|
259
|
+
function withNgxsWebSocketPlugin(options) {
|
|
260
|
+
return makeEnvironmentProviders(ɵgetProviders(options));
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
/**
|
|
264
|
+
* The public api for consumers of @ngxs/websocket-plugin
|
|
265
|
+
*/
|
|
266
|
+
|
|
267
|
+
/**
|
|
268
|
+
* Generated bundle index. Do not edit.
|
|
269
|
+
*/
|
|
270
|
+
|
|
271
|
+
export { ConnectWebSocket, DisconnectWebSocket, NgxsWebSocketPluginModule, SendWebSocketMessage, WebSocketConnected, WebSocketConnectionUpdated, WebSocketDisconnected, WebSocketMessageError, withNgxsWebSocketPlugin, NGXS_WEBSOCKET_OPTIONS as ɵNGXS_WEBSOCKET_OPTIONS };
|
|
272
|
+
//# sourceMappingURL=ngxs-websocket-plugin.mjs.map
|