@pathscale/wss-adapter 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,68 @@
1
+ interface IService {
2
+ remote: string;
3
+ methods: {
4
+ [methodCode: string]: {
5
+ name: string;
6
+ parameters: string[];
7
+ };
8
+ };
9
+ }
10
+ interface IServiceConfig extends IService {
11
+ onDisconnect: () => void | null;
12
+ }
13
+ interface IServices {
14
+ [serviceName: string]: IServiceConfig;
15
+ }
16
+ interface IErrors {
17
+ [errorCode: number]: string;
18
+ }
19
+ interface IConfiguration {
20
+ timeout: number;
21
+ services: IServices;
22
+ errors: IErrors;
23
+ onError: (error: {
24
+ error: number;
25
+ message: string;
26
+ }) => void;
27
+ }
28
+ interface IServiceConnect {
29
+ connect<T>(payload: string | string[] | undefined): Promise<T>;
30
+ disconnect: () => void;
31
+ }
32
+ interface IWssAdapter {
33
+ services: {
34
+ [serviceName: string]: IServiceConnect;
35
+ };
36
+ sessions: {
37
+ [serviceName: string]: unknown;
38
+ };
39
+ configure: (configuration: IConfiguration) => void;
40
+ }
41
+ interface ISequence {
42
+ value: number;
43
+ getSeq: () => number;
44
+ decreaseSeq: () => void;
45
+ }
46
+ interface ISessions {
47
+ [serviceName: string]: WebSocket;
48
+ }
49
+ interface IPendingPromises {
50
+ [seq: number]: {
51
+ resolve: (payload: unknown) => void;
52
+ reject: (error: Error) => void;
53
+ toHandler: ReturnType<typeof setTimeout>;
54
+ };
55
+ }
56
+ interface IStore {
57
+ timeout: number;
58
+ errors: IErrors;
59
+ services: IServices;
60
+ sequence: ISequence;
61
+ sessions: ISessions;
62
+ pendingPromises: IPendingPromises;
63
+ onError: (error: {
64
+ error: number;
65
+ message: string;
66
+ }) => void;
67
+ }
68
+ export { IStore, IWssAdapter, IServiceConfig, IConfiguration, IErrors, IServices, IService };
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -0,0 +1,3 @@
1
+ import { IWssAdapter } from './types';
2
+ declare const wssAdapter: IWssAdapter;
3
+ export default wssAdapter;
@@ -0,0 +1,169 @@
1
+ "use strict";
2
+ /* eslint-disable compat/compat */
3
+ /* eslint-disable promise/avoid-new */
4
+ /* eslint-disable @typescript-eslint/no-empty-function */
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ var wssAdapter = {
7
+ services: {},
8
+ sessions: {},
9
+ configure: function () { },
10
+ };
11
+ var store = {
12
+ timeout: 0,
13
+ errors: {},
14
+ services: {},
15
+ sequence: {
16
+ value: 1,
17
+ getSeq: function () {
18
+ store.sequence.value += 1;
19
+ return store.sequence.value;
20
+ },
21
+ decreaseSeq: function () {
22
+ store.sequence.value -= 1;
23
+ },
24
+ },
25
+ sessions: {},
26
+ pendingPromises: {},
27
+ onError: function () { },
28
+ };
29
+ wssAdapter.configure = function (configuration) {
30
+ var timeout = configuration.timeout, services = configuration.services, errors = configuration.errors, onError = configuration.onError;
31
+ // save some stuff for later retrieval
32
+ store.timeout = timeout;
33
+ store.errors = errors;
34
+ store.services = services;
35
+ store.onError = onError;
36
+ var _loop_1 = function (serviceName, serviceConfig) {
37
+ // construct services objects with two simple functions
38
+ // intended use: `wssAdapter.services.admin.connect([1, 2, 3])` or `wssAdapter.services.auth.connect([1, 2, 3])`
39
+ wssAdapter.services[serviceName] = {
40
+ connect: function (payload) {
41
+ return connectHandler(serviceName, serviceConfig, payload);
42
+ },
43
+ disconnect: function () { return disconnectHandler(serviceName); },
44
+ };
45
+ // construct sessions objects that contain a proxy so you can ask unknown property
46
+ // intended use: `wssAdapter.sessions.admin.updatePassword({ newPassword: 'hotdog6737637' })`
47
+ wssAdapter.sessions[serviceName] = new Proxy({}, {
48
+ get: function (target, methodName) { return function (payload) {
49
+ return sendHandler(serviceName, serviceConfig, methodName, payload);
50
+ }; },
51
+ });
52
+ };
53
+ for (var _i = 0, _a = Object.entries(services); _i < _a.length; _i++) {
54
+ var _b = _a[_i], serviceName = _b[0], serviceConfig = _b[1];
55
+ _loop_1(serviceName, serviceConfig);
56
+ }
57
+ };
58
+ var connectHandler = function (serviceName, serviceConfig, payload) {
59
+ return new Promise(function (resolve, reject) {
60
+ store.sessions[serviceName] = new WebSocket(serviceConfig.remote, payload);
61
+ store.sessions[serviceName].onmessage = function (event) {
62
+ var _a;
63
+ var response = JSON.parse(event.data);
64
+ console.log(response);
65
+ if (response.params.error) {
66
+ reject(new Error((_a = store.errors[response.params.error]) !== null && _a !== void 0 ? _a : 'Something went wrong'));
67
+ return;
68
+ }
69
+ store.sessions[serviceName].onmessage = receiveHandler;
70
+ resolve(response.params);
71
+ };
72
+ store.sessions[serviceName].onclose = serviceConfig.onDisconnect;
73
+ });
74
+ };
75
+ var disconnectHandler = function (serviceName) {
76
+ var _a;
77
+ (_a = store.sessions[serviceName]) === null || _a === void 0 ? void 0 : _a.close();
78
+ };
79
+ var sendHandler = function (serviceName, serviceConfig, methodName, params) {
80
+ var _a;
81
+ var methodCode = (_a = Object.entries(serviceConfig.methods)
82
+ .map(function (_a) {
83
+ var code = _a[0], info = _a[1];
84
+ return ({ code: code, info: info });
85
+ })
86
+ .find(function (_a) {
87
+ var info = _a.info;
88
+ return info.name === methodName;
89
+ })) === null || _a === void 0 ? void 0 : _a.code;
90
+ if (!methodCode) {
91
+ throw new Error("method ".concat(methodName, " not available in ").concat(serviceName, " service"));
92
+ }
93
+ if (!Object.keys(params).every(function (param) {
94
+ return serviceConfig.methods[methodCode].parameters.includes(param);
95
+ })) {
96
+ throw new Error("method ".concat(methodCode, " is being called with missing parameters"));
97
+ }
98
+ var purgedParams = {};
99
+ serviceConfig.methods[methodCode].parameters.forEach(function (k) {
100
+ purgedParams[k] = params[k];
101
+ });
102
+ var difference = Object.keys(params).filter(function (x) { return !serviceConfig.methods[methodCode].parameters.includes(x); });
103
+ if (difference.length) {
104
+ throw new Error("method ".concat(methodCode, " is being called with unknow parameters, ").concat(difference));
105
+ }
106
+ var payload = {
107
+ method: Number.parseInt(methodCode),
108
+ seq: store.sequence.getSeq(),
109
+ params: purgedParams,
110
+ };
111
+ console.log("".concat(serviceName, "::").concat(methodName, " sends:"), payload);
112
+ return new Promise(function (resolve, reject) {
113
+ store.sessions[serviceName].send(JSON.stringify(payload));
114
+ // save executor so that when response for this request comes the promise can be resolved
115
+ store.pendingPromises[payload.seq] = {
116
+ resolve: resolve,
117
+ reject: reject,
118
+ toHandler: setTimeout(function () {
119
+ reject(new Error(methodCode.toString() + ' took to long, aborting'));
120
+ }, store.timeout),
121
+ };
122
+ });
123
+ };
124
+ var receiveHandler = function (event) {
125
+ var response = JSON.parse(event.data);
126
+ console.log("app::".concat(response.method, " got:"), response);
127
+ var error = response.method === 0;
128
+ var done = response.method.toString().endsWith('1');
129
+ var resolve = function (payload, code) {
130
+ console.log(code);
131
+ var executor = store.pendingPromises[response.seq];
132
+ clearTimeout(store.pendingPromises[response.seq].toHandler);
133
+ delete store.pendingPromises[response.seq];
134
+ executor.resolve(payload);
135
+ };
136
+ // handle error
137
+ if (error) {
138
+ onError(response);
139
+ }
140
+ else if (done) {
141
+ var code = response.method - 1;
142
+ resolve(response, code);
143
+ }
144
+ };
145
+ function onError(response) {
146
+ var _a, _b;
147
+ var errorCode = response.params.error;
148
+ var errorMsg = (_a = store.errors[errorCode]) !== null && _a !== void 0 ? _a : 'Something went wrong';
149
+ if ([45349638, 45349637].includes(errorCode)) {
150
+ store.sequence.decreaseSeq();
151
+ console.log('seq has been decreased because of error');
152
+ }
153
+ (_b = store.onError) === null || _b === void 0 ? void 0 : _b.call(store, {
154
+ error: errorCode,
155
+ message: errorMsg,
156
+ });
157
+ // if there was only one executor saved in store.pendingPromises, then it was that request that failed
158
+ if (Object.keys(store.pendingPromises).length === 1) {
159
+ var onlyKey = Number.parseInt(Object.keys(store.pendingPromises)[0]);
160
+ clearTimeout(store.pendingPromises[onlyKey].toHandler);
161
+ store.pendingPromises[onlyKey].reject(new Error(errorMsg));
162
+ delete store.pendingPromises[onlyKey];
163
+ }
164
+ // if there were more than one, there is no way of knowing who failed
165
+ else {
166
+ throw new Error('Unkown request failed');
167
+ }
168
+ }
169
+ exports.default = wssAdapter;
package/package.json ADDED
@@ -0,0 +1,23 @@
1
+ {
2
+ "name": "@pathscale/wss-adapter",
3
+ "version": "1.0.0",
4
+ "description": "Websocket adapter for the Pathscale WSS",
5
+ "main": "dist/wssAdapter.js",
6
+ "types": "dist/wssAdapter.d.ts",
7
+ "scripts": {
8
+ "build": "tsc"
9
+ },
10
+ "author": "PathScale",
11
+ "license": "MIT",
12
+ "devDependencies": {
13
+ "typescript": "^4.5.5"
14
+ },
15
+ "repository": {
16
+ "type": "git",
17
+ "url": "git+https://github.com/skaptox/wss-adapter.git"
18
+ },
19
+ "bugs": {
20
+ "url": "https://github.com/skaptox/wss-adapter/issues"
21
+ },
22
+ "homepage": "https://github.com/skaptox/wss-adapter#readme"
23
+ }
package/tsconfig.json ADDED
@@ -0,0 +1,15 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "es5",
4
+ "module": "commonjs",
5
+ "lib": ["es2017", "es7", "es6", "dom"],
6
+ "declaration": true,
7
+ "outDir": "dist",
8
+ "strict": true,
9
+ "esModuleInterop": true
10
+ },
11
+ "exclude": [
12
+ "node_modules",
13
+ "dist"
14
+ ]
15
+ }
package/types/index.ts ADDED
@@ -0,0 +1,73 @@
1
+ interface IService {
2
+ remote: string
3
+ methods: {
4
+ [methodCode: string]: {
5
+ name: string
6
+ parameters: string[]
7
+ }
8
+ }
9
+ }
10
+
11
+ interface IServiceConfig extends IService {
12
+ onDisconnect: () => void | null
13
+ }
14
+
15
+ interface IServices {
16
+ [serviceName: string]: IServiceConfig
17
+ }
18
+
19
+ interface IErrors {
20
+ [errorCode: number]: string
21
+ }
22
+
23
+ interface IConfiguration {
24
+ timeout: number
25
+ services: IServices
26
+ errors: IErrors
27
+ onError: (error: { error: number; message: string }) => void
28
+ }
29
+
30
+ interface IServiceConnect {
31
+ connect<T>(payload: string | string[] | undefined): Promise<T>
32
+ disconnect: () => void
33
+ }
34
+
35
+ interface IWssAdapter {
36
+ services: {
37
+ [serviceName: string]: IServiceConnect
38
+ }
39
+ sessions: {
40
+ [serviceName: string]: unknown
41
+ }
42
+ configure: (configuration: IConfiguration) => void
43
+ }
44
+
45
+ interface ISequence {
46
+ value: number
47
+ getSeq: () => number
48
+ decreaseSeq: () => void
49
+ }
50
+
51
+ interface ISessions {
52
+ [serviceName: string]: WebSocket
53
+ }
54
+
55
+ interface IPendingPromises {
56
+ [seq: number]: {
57
+ resolve: (payload: unknown) => void
58
+ reject: (error: Error) => void
59
+ toHandler: ReturnType<typeof setTimeout>
60
+ }
61
+ }
62
+
63
+ interface IStore {
64
+ timeout: number
65
+ errors: IErrors
66
+ services: IServices
67
+ sequence: ISequence
68
+ sessions: ISessions
69
+ pendingPromises: IPendingPromises
70
+ onError: (error: { error: number; message: string }) => void
71
+ }
72
+
73
+ export { IStore, IWssAdapter, IServiceConfig, IConfiguration, IErrors, IServices, IService }
package/wssAdapter.ts ADDED
@@ -0,0 +1,213 @@
1
+ /* eslint-disable compat/compat */
2
+ /* eslint-disable promise/avoid-new */
3
+ /* eslint-disable @typescript-eslint/no-empty-function */
4
+
5
+ import { IStore, IWssAdapter, IServiceConfig } from './types'
6
+
7
+ const wssAdapter: IWssAdapter = {
8
+ services: {},
9
+ sessions: {},
10
+ configure() {},
11
+ }
12
+
13
+ const store: IStore = {
14
+ timeout: 0,
15
+ errors: {},
16
+ services: {},
17
+
18
+ sequence: {
19
+ value: 1,
20
+ getSeq() {
21
+ store.sequence.value += 1
22
+ return store.sequence.value
23
+ },
24
+ decreaseSeq() {
25
+ store.sequence.value -= 1
26
+ },
27
+ },
28
+
29
+ sessions: {},
30
+ pendingPromises: {},
31
+ onError() {},
32
+ }
33
+
34
+ wssAdapter.configure = configuration => {
35
+ const { timeout, services, errors, onError } = configuration
36
+
37
+ // save some stuff for later retrieval
38
+ store.timeout = timeout
39
+ store.errors = errors
40
+ store.services = services
41
+ store.onError = onError
42
+
43
+ for (const [serviceName, serviceConfig] of Object.entries(services)) {
44
+ // construct services objects with two simple functions
45
+ // intended use: `wssAdapter.services.admin.connect([1, 2, 3])` or `wssAdapter.services.auth.connect([1, 2, 3])`
46
+ wssAdapter.services[serviceName] = {
47
+ connect: <T>(payload: string | string[] | undefined) =>
48
+ connectHandler<T>(serviceName, serviceConfig, payload),
49
+ disconnect: () => disconnectHandler(serviceName),
50
+ }
51
+
52
+ // construct sessions objects that contain a proxy so you can ask unknown property
53
+ // intended use: `wssAdapter.sessions.admin.updatePassword({ newPassword: 'hotdog6737637' })`
54
+ wssAdapter.sessions[serviceName] = new Proxy(
55
+ {},
56
+ {
57
+ get: (target, methodName: string) => (payload: Record<string, unknown>) =>
58
+ sendHandler(serviceName, serviceConfig, methodName, payload),
59
+ },
60
+ )
61
+ }
62
+ }
63
+
64
+ const connectHandler = <T>(
65
+ serviceName: string,
66
+ serviceConfig: IServiceConfig,
67
+ payload: string | string[] | undefined,
68
+ ) => {
69
+ return new Promise((resolve, reject) => {
70
+ store.sessions[serviceName] = new WebSocket(serviceConfig.remote, payload)
71
+
72
+ store.sessions[serviceName].onmessage = function (event: { data: string }) {
73
+ const response = JSON.parse(event.data)
74
+ console.log(response)
75
+
76
+ if (response.params.error) {
77
+ reject(new Error(store.errors[response.params.error] ?? 'Something went wrong'))
78
+ return
79
+ }
80
+
81
+ store.sessions[serviceName].onmessage = receiveHandler
82
+ resolve(response.params)
83
+ }
84
+
85
+ store.sessions[serviceName].onclose = serviceConfig.onDisconnect
86
+ }) as Promise<T>
87
+ }
88
+
89
+ const disconnectHandler = (serviceName: string) => {
90
+ store.sessions[serviceName]?.close()
91
+ }
92
+
93
+ const sendHandler = (
94
+ serviceName: string,
95
+ serviceConfig: IServiceConfig,
96
+ methodName: string,
97
+ params: Record<string, unknown>,
98
+ ) => {
99
+ const methodCode = Object.entries(serviceConfig.methods)
100
+ .map(([code, info]) => ({ code, info }))
101
+ .find(({ info }) => info.name === methodName)?.code
102
+
103
+ if (!methodCode) {
104
+ throw new Error(`method ${methodName} not available in ${serviceName} service`)
105
+ }
106
+
107
+ if (
108
+ !Object.keys(params).every(param =>
109
+ serviceConfig.methods[methodCode].parameters.includes(param),
110
+ )
111
+ ) {
112
+ throw new Error(`method ${methodCode} is being called with missing parameters`)
113
+ }
114
+
115
+ const purgedParams: Record<string, unknown> = {}
116
+ serviceConfig.methods[methodCode].parameters.forEach(k => {
117
+ purgedParams[k] = params[k]
118
+ })
119
+
120
+ const difference = Object.keys(params).filter(
121
+ x => !serviceConfig.methods[methodCode].parameters.includes(x),
122
+ )
123
+
124
+ if (difference.length) {
125
+ throw new Error(`method ${methodCode} is being called with unknow parameters, ${difference}`)
126
+ }
127
+
128
+ const payload = {
129
+ method: Number.parseInt(methodCode),
130
+ seq: store.sequence.getSeq(),
131
+ params: purgedParams,
132
+ }
133
+
134
+ console.log(`${serviceName}::${methodName} sends:`, payload)
135
+
136
+ return new Promise((resolve, reject) => {
137
+ store.sessions[serviceName].send(JSON.stringify(payload))
138
+
139
+ // save executor so that when response for this request comes the promise can be resolved
140
+ store.pendingPromises[payload.seq] = {
141
+ resolve,
142
+ reject,
143
+ toHandler: setTimeout(() => {
144
+ reject(new Error(methodCode.toString() + ' took to long, aborting'))
145
+ }, store.timeout),
146
+ }
147
+ })
148
+ }
149
+
150
+ const receiveHandler = (event: { data: string }) => {
151
+ const response = JSON.parse(event.data)
152
+ console.log(`app::${response.method} got:`, response)
153
+
154
+ const error = response.method === 0
155
+ const done = response.method.toString().endsWith('1')
156
+
157
+ const resolve = (payload: unknown, code: number) => {
158
+ console.log(code)
159
+ const executor = store.pendingPromises[response.seq]
160
+ clearTimeout(store.pendingPromises[response.seq].toHandler)
161
+ delete store.pendingPromises[response.seq]
162
+ executor.resolve(payload)
163
+ }
164
+
165
+ // handle error
166
+ if (error) {
167
+ onError(response)
168
+ } else if (done) {
169
+ const code = response.method - 1
170
+ resolve(response, code)
171
+ }
172
+ }
173
+
174
+ // if it was one of these guys
175
+ // 1) protocol violation
176
+ // 2) malformed request
177
+ // seq must be decreased
178
+
179
+ interface IResponse {
180
+ params: {
181
+ error: number
182
+ }
183
+ }
184
+
185
+ function onError(response: IResponse) {
186
+ const { error: errorCode } = response.params
187
+ const errorMsg = store.errors[errorCode] ?? 'Something went wrong'
188
+
189
+ if ([45349638, 45349637].includes(errorCode)) {
190
+ store.sequence.decreaseSeq()
191
+ console.log('seq has been decreased because of error')
192
+ }
193
+
194
+ store.onError?.({
195
+ error: errorCode,
196
+ message: errorMsg,
197
+ })
198
+
199
+ // if there was only one executor saved in store.pendingPromises, then it was that request that failed
200
+ if (Object.keys(store.pendingPromises).length === 1) {
201
+ const onlyKey = Number.parseInt(Object.keys(store.pendingPromises)[0])
202
+ clearTimeout(store.pendingPromises[onlyKey].toHandler)
203
+ store.pendingPromises[onlyKey].reject(new Error(errorMsg))
204
+ delete store.pendingPromises[onlyKey]
205
+ }
206
+
207
+ // if there were more than one, there is no way of knowing who failed
208
+ else {
209
+ throw new Error('Unkown request failed')
210
+ }
211
+ }
212
+
213
+ export default wssAdapter