@push-rpc/next 2.0.7 → 2.0.9
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 +6 -0
- package/dist/client/HttpClient.d.ts +3 -1
- package/dist/client/HttpClient.js +15 -1
- package/dist/client/HttpClient.js.map +1 -1
- package/dist/client/RpcClientImpl.d.ts +2 -1
- package/dist/client/RpcClientImpl.js +4 -2
- package/dist/client/RpcClientImpl.js.map +1 -1
- package/dist/client/WebSocketConnection.d.ts +5 -2
- package/dist/client/WebSocketConnection.js +37 -15
- package/dist/client/WebSocketConnection.js.map +1 -1
- package/dist/client/index.d.ts +1 -0
- package/dist/client/index.js.map +1 -1
- package/dist/utils/cookies.d.ts +7 -0
- package/dist/utils/cookies.js +31 -0
- package/dist/utils/cookies.js.map +1 -0
- package/dist/utils/env.d.ts +6 -0
- package/dist/utils/env.js +22 -0
- package/dist/utils/env.js.map +1 -0
- package/package.json +1 -1
- package/src/client/HttpClient.ts +20 -1
- package/src/client/RpcClientImpl.ts +5 -2
- package/src/client/WebSocketConnection.ts +47 -13
- package/src/client/index.ts +3 -1
- package/src/utils/cookies.ts +27 -0
- package/src/utils/env.ts +17 -0
- package/tests/connection.ts +87 -2
- package/tests/subscriptions.ts +62 -1
package/README.md
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
Client/server framework
|
|
2
|
+
|
|
1
3
|
## Glossary
|
|
2
4
|
|
|
3
5
|
**Remote function**. A function that is implemented at the server side and can be called from the client side. Function
|
|
@@ -40,3 +42,7 @@ triggers.
|
|
|
40
42
|
- Supports compressed HTTP requests.
|
|
41
43
|
- Server runs on Node.JS, client runs in the Node.JS/Browser/ReactNative. For RN some extra setup is required (
|
|
42
44
|
document). Bun/Deno should also work, but not officially supported.
|
|
45
|
+
- Limited cookie support. Due to limited support in React Native, Cookies can be set and read during HTTP requests and
|
|
46
|
+
set during WS connection establishment. So if you need to share cookies between HTTP and WS, you need to make call (
|
|
47
|
+
HTTP POST) right after creating client, and establish WS connection (ie make subscribe, HTTP PUT) only after that.
|
|
48
|
+
Cookies cannot be shared when `connectOnCreate` client option is true.
|
|
@@ -1,8 +1,10 @@
|
|
|
1
|
+
import { ClientCookies } from "../utils/cookies.js";
|
|
1
2
|
export declare class HttpClient {
|
|
2
3
|
private url;
|
|
3
4
|
private clientId;
|
|
4
5
|
private getHeaders;
|
|
5
|
-
|
|
6
|
+
private cookies;
|
|
7
|
+
constructor(url: string, clientId: string, getHeaders: () => Promise<Record<string, string>>, cookies: ClientCookies);
|
|
6
8
|
call(itemName: string, params: unknown[], callTimeout: number): Promise<unknown>;
|
|
7
9
|
subscribe(itemName: string, params: unknown[], callTimeout: number): Promise<unknown>;
|
|
8
10
|
unsubscribe(itemName: string, params: unknown[], callTimeout: number): Promise<void>;
|
|
@@ -3,11 +3,13 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.HttpClient = void 0;
|
|
4
4
|
const rpc_js_1 = require("../rpc.js");
|
|
5
5
|
const json_js_1 = require("../utils/json.js");
|
|
6
|
+
const env_js_1 = require("../utils/env.js");
|
|
6
7
|
class HttpClient {
|
|
7
|
-
constructor(url, clientId, getHeaders) {
|
|
8
|
+
constructor(url, clientId, getHeaders, cookies) {
|
|
8
9
|
this.url = url;
|
|
9
10
|
this.clientId = clientId;
|
|
10
11
|
this.getHeaders = getHeaders;
|
|
12
|
+
this.cookies = cookies;
|
|
11
13
|
}
|
|
12
14
|
async call(itemName, params, callTimeout) {
|
|
13
15
|
return this.httpRequest("POST", itemName, params, callTimeout, await this.getHeaders());
|
|
@@ -25,6 +27,12 @@ class HttpClient {
|
|
|
25
27
|
const itemUrl = this.getItemUrl(itemName);
|
|
26
28
|
try {
|
|
27
29
|
const { signal, finished } = timeoutSignal(callTimeout);
|
|
30
|
+
if (env_js_1.environment != env_js_1.Environment.Browser) {
|
|
31
|
+
const cookie = this.cookies.getCookieString();
|
|
32
|
+
if (cookie) {
|
|
33
|
+
headers["Cookie"] = cookie;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
28
36
|
const response = await fetch(itemUrl, {
|
|
29
37
|
method,
|
|
30
38
|
headers: {
|
|
@@ -36,6 +44,12 @@ class HttpClient {
|
|
|
36
44
|
signal,
|
|
37
45
|
});
|
|
38
46
|
finished();
|
|
47
|
+
if (env_js_1.environment != env_js_1.Environment.Browser) {
|
|
48
|
+
const cookie = response.headers.get("set-cookie");
|
|
49
|
+
if (cookie) {
|
|
50
|
+
this.cookies.updateCookies(cookie.split(","));
|
|
51
|
+
}
|
|
52
|
+
}
|
|
39
53
|
if (response.status == 204) {
|
|
40
54
|
return;
|
|
41
55
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"HttpClient.js","sourceRoot":"","sources":["../../src/client/HttpClient.ts"],"names":[],"mappings":";;;AAAA,sCAAqD;AACrD,8CAA6D;AAE7D,MAAa,UAAU;IACrB,YACU,GAAW,EACX,QAAgB,EAChB,UAAiD;
|
|
1
|
+
{"version":3,"file":"HttpClient.js","sourceRoot":"","sources":["../../src/client/HttpClient.ts"],"names":[],"mappings":"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"}
|
|
@@ -4,10 +4,11 @@ import { ConsumeServicesOptions, RpcClient } from "./index.js";
|
|
|
4
4
|
export declare class RpcClientImpl<S extends Services<S>> implements RpcClient {
|
|
5
5
|
private readonly options;
|
|
6
6
|
constructor(url: string, options: ConsumeServicesOptions);
|
|
7
|
-
|
|
7
|
+
readonly clientId: string;
|
|
8
8
|
private readonly httpClient;
|
|
9
9
|
private readonly remoteSubscriptions;
|
|
10
10
|
private readonly connection;
|
|
11
|
+
private readonly cookies;
|
|
11
12
|
isConnected(): boolean;
|
|
12
13
|
close(): Promise<void>;
|
|
13
14
|
_allSubscriptions(): [itemName: string, parameters: unknown[], consumers: (d: unknown) => void][];
|
|
@@ -8,10 +8,12 @@ const WebSocketConnection_js_1 = require("./WebSocketConnection.js");
|
|
|
8
8
|
const nanoid_1 = require("nanoid");
|
|
9
9
|
const remote_js_1 = require("./remote.js");
|
|
10
10
|
const middleware_js_1 = require("../utils/middleware.js");
|
|
11
|
+
const cookies_js_1 = require("../utils/cookies.js");
|
|
11
12
|
class RpcClientImpl {
|
|
12
13
|
constructor(url, options) {
|
|
13
14
|
this.options = options;
|
|
14
15
|
this.clientId = (0, nanoid_1.nanoid)();
|
|
16
|
+
this.cookies = new cookies_js_1.ClientCookies();
|
|
15
17
|
this.call = (itemName, parameters, callOptions) => {
|
|
16
18
|
return this.invoke(itemName, rpc_js_1.InvocationType.Call, (...parameters) => this.httpClient.call(itemName, parameters, callOptions?.timeout ?? this.options.callTimeout), parameters);
|
|
17
19
|
};
|
|
@@ -58,9 +60,9 @@ class RpcClientImpl {
|
|
|
58
60
|
});
|
|
59
61
|
}
|
|
60
62
|
};
|
|
61
|
-
this.httpClient = new HttpClient_js_1.HttpClient(url, this.clientId, options.getHeaders);
|
|
63
|
+
this.httpClient = new HttpClient_js_1.HttpClient(url, this.clientId, options.getHeaders, this.cookies);
|
|
62
64
|
this.remoteSubscriptions = new RemoteSubscriptions_js_1.RemoteSubscriptions();
|
|
63
|
-
this.connection = new WebSocketConnection_js_1.WebSocketConnection(options.getSubscriptionsUrl(url), this.clientId, {
|
|
65
|
+
this.connection = new WebSocketConnection_js_1.WebSocketConnection(options.getSubscriptionsUrl(url), this.clientId, this.cookies, {
|
|
64
66
|
subscriptions: options.subscriptions,
|
|
65
67
|
errorDelayMaxDuration: options.errorDelayMaxDuration,
|
|
66
68
|
reconnectDelay: options.reconnectDelay,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"RpcClientImpl.js","sourceRoot":"","sources":["../../src/client/RpcClientImpl.ts"],"names":[],"mappings":";;;AAAA,sCAA2E;AAC3E,mDAA0C;AAC1C,qEAA4D;AAC5D,qEAA4D;AAC5D,mCAA6B;AAC7B,2CAAmE;AAEnE,0DAAsD;
|
|
1
|
+
{"version":3,"file":"RpcClientImpl.js","sourceRoot":"","sources":["../../src/client/RpcClientImpl.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,11 +1,13 @@
|
|
|
1
|
+
import { ClientCookies } from "../utils/cookies.js";
|
|
1
2
|
export declare class WebSocketConnection {
|
|
2
3
|
private readonly url;
|
|
3
4
|
private readonly clientId;
|
|
5
|
+
private readonly cookies;
|
|
4
6
|
private readonly options;
|
|
5
7
|
private readonly consume;
|
|
6
8
|
private readonly onConnected;
|
|
7
9
|
private readonly onDisconnected;
|
|
8
|
-
constructor(url: string, clientId: string, options: {
|
|
10
|
+
constructor(url: string, clientId: string, cookies: ClientCookies, options: {
|
|
9
11
|
subscriptions: boolean;
|
|
10
12
|
reconnectDelay: number;
|
|
11
13
|
errorDelayMaxDuration: number;
|
|
@@ -13,9 +15,10 @@ export declare class WebSocketConnection {
|
|
|
13
15
|
}, consume: (itemName: string, parameters: unknown[], data: unknown) => void, onConnected: () => void, onDisconnected: () => void);
|
|
14
16
|
private resolveClose;
|
|
15
17
|
close(): Promise<void>;
|
|
18
|
+
private waitConnectionPromise;
|
|
16
19
|
/**
|
|
17
20
|
* Connect to the server, on each disconnect try to disconnect.
|
|
18
|
-
* Resolves at
|
|
21
|
+
* Resolves at next successful connect. Reconnection loop continues even after resolution
|
|
19
22
|
* Never rejects
|
|
20
23
|
*/
|
|
21
24
|
connect(): Promise<void>;
|
|
@@ -4,17 +4,19 @@ exports.WebSocketConnection = void 0;
|
|
|
4
4
|
const logger_js_1 = require("../logger.js");
|
|
5
5
|
const json_js_1 = require("../utils/json.js");
|
|
6
6
|
const promises_js_1 = require("../utils/promises.js");
|
|
7
|
+
const env_js_1 = require("../utils/env.js");
|
|
7
8
|
class WebSocketConnection {
|
|
8
|
-
constructor(url, clientId, options, consume, onConnected, onDisconnected) {
|
|
9
|
+
constructor(url, clientId, cookies, options, consume, onConnected, onDisconnected) {
|
|
9
10
|
this.url = url;
|
|
10
11
|
this.clientId = clientId;
|
|
12
|
+
this.cookies = cookies;
|
|
11
13
|
this.options = options;
|
|
12
14
|
this.consume = consume;
|
|
13
15
|
this.onConnected = onConnected;
|
|
14
16
|
this.onDisconnected = onDisconnected;
|
|
15
17
|
this.resolveClose = () => { };
|
|
16
18
|
this.socket = null;
|
|
17
|
-
this.disconnectedMark =
|
|
19
|
+
this.disconnectedMark = false;
|
|
18
20
|
this.pingTimeout = null;
|
|
19
21
|
this.clientId = clientId;
|
|
20
22
|
}
|
|
@@ -29,7 +31,7 @@ class WebSocketConnection {
|
|
|
29
31
|
}
|
|
30
32
|
/**
|
|
31
33
|
* Connect to the server, on each disconnect try to disconnect.
|
|
32
|
-
* Resolves at
|
|
34
|
+
* Resolves at next successful connect. Reconnection loop continues even after resolution
|
|
33
35
|
* Never rejects
|
|
34
36
|
*/
|
|
35
37
|
connect() {
|
|
@@ -38,24 +40,26 @@ class WebSocketConnection {
|
|
|
38
40
|
return Promise.resolve();
|
|
39
41
|
}
|
|
40
42
|
// already started connecting
|
|
41
|
-
if (this.
|
|
42
|
-
return
|
|
43
|
+
if (this.waitConnectionPromise)
|
|
44
|
+
return this.waitConnectionPromise;
|
|
43
45
|
// start connection process
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
46
|
+
let resolveConnectionPromise;
|
|
47
|
+
let errorDelay = 0;
|
|
48
|
+
this.waitConnectionPromise = new Promise(async (resolve) => {
|
|
49
|
+
resolveConnectionPromise = resolve;
|
|
48
50
|
while (true) {
|
|
49
51
|
// connect, and wait for ...
|
|
50
52
|
await new Promise((resolve) => {
|
|
51
|
-
|
|
52
|
-
|
|
53
|
+
const connectionPromise = this.establishConnection(() => {
|
|
54
|
+
// 1. ...disconnected
|
|
55
|
+
// recreate promise so new clients will wait for new connection
|
|
56
|
+
this.waitConnectionPromise = new Promise((resolve) => (resolveConnectionPromise = resolve));
|
|
57
|
+
resolve();
|
|
58
|
+
});
|
|
53
59
|
connectionPromise.then(() => {
|
|
54
60
|
// first reconnect after successful connection is done without delay
|
|
55
61
|
errorDelay = 0;
|
|
56
|
-
|
|
57
|
-
onFirstConnection();
|
|
58
|
-
onFirstConnection = () => { };
|
|
62
|
+
resolveConnectionPromise();
|
|
59
63
|
}, (e) => {
|
|
60
64
|
logger_js_1.log.warn("Unable to connect WS", e);
|
|
61
65
|
// 2. ... unable to establish connection
|
|
@@ -74,6 +78,7 @@ class WebSocketConnection {
|
|
|
74
78
|
errorDelay = Math.round(Math.random() * this.options.errorDelayMaxDuration);
|
|
75
79
|
}
|
|
76
80
|
});
|
|
81
|
+
return this.waitConnectionPromise;
|
|
77
82
|
}
|
|
78
83
|
isConnected() {
|
|
79
84
|
return this.socket !== null;
|
|
@@ -86,7 +91,24 @@ class WebSocketConnection {
|
|
|
86
91
|
async establishConnection(onDisconnected) {
|
|
87
92
|
return new Promise(async (resolve, reject) => {
|
|
88
93
|
try {
|
|
89
|
-
|
|
94
|
+
let socket;
|
|
95
|
+
if ([env_js_1.Environment.ReactNative, env_js_1.Environment.Node].includes(env_js_1.environment)) {
|
|
96
|
+
// use RN WS or node-ws headers extensions to set cookie
|
|
97
|
+
let options = undefined;
|
|
98
|
+
const cookie = this.cookies.getCookieString();
|
|
99
|
+
if (cookie) {
|
|
100
|
+
options = {
|
|
101
|
+
headers: {
|
|
102
|
+
Cookie: cookie,
|
|
103
|
+
},
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
socket = new WebSocket(this.url, this.clientId, options);
|
|
107
|
+
}
|
|
108
|
+
else {
|
|
109
|
+
// rely on browser cookie handling
|
|
110
|
+
socket = new WebSocket(this.url, this.clientId);
|
|
111
|
+
}
|
|
90
112
|
let connected = false;
|
|
91
113
|
socket.addEventListener("open", () => {
|
|
92
114
|
this.socket = socket;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"WebSocketConnection.js","sourceRoot":"","sources":["../../src/client/WebSocketConnection.ts"],"names":[],"mappings":";;;AAAA,4CAAgC;AAChC,8CAA8C;AAC9C,sDAA2C;
|
|
1
|
+
{"version":3,"file":"WebSocketConnection.js","sourceRoot":"","sources":["../../src/client/WebSocketConnection.ts"],"names":[],"mappings":"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"}
|
package/dist/client/index.d.ts
CHANGED
|
@@ -2,6 +2,7 @@ import { RpcContext, Services } from "../rpc.js";
|
|
|
2
2
|
import { ServicesWithSubscriptions } from "./remote.js";
|
|
3
3
|
import { Middleware } from "../utils/middleware.js";
|
|
4
4
|
export type RpcClient = {
|
|
5
|
+
readonly clientId: string;
|
|
5
6
|
isConnected(): boolean;
|
|
6
7
|
close(): Promise<void>;
|
|
7
8
|
_allSubscriptions(): Array<any[]>;
|
package/dist/client/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/client/index.ts"],"names":[],"mappings":";;;AAEA,yDAAgD;
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/client/index.ts"],"names":[],"mappings":";;;AAEA,yDAAgD;AA4BzC,KAAK,UAAU,eAAe,CACnC,GAAW,EACX,kBAAmD,EAAE;IAKrD,IAAI,GAAG,CAAC,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC;QACtB,MAAM,IAAI,KAAK,CAAC,yBAAyB,CAAC,CAAA;IAC5C,CAAC;IAED,MAAM,OAAO,GAAG;QACd,GAAG,cAAc;QACjB,GAAG,eAAe;KACnB,CAAA;IAED,MAAM,MAAM,GAAG,IAAI,gCAAa,CAAI,GAAG,EAAE,OAAO,CAAC,CAAA;IAEjD,IAAI,OAAO,CAAC,eAAe,EAAE,CAAC;QAC5B,MAAM,MAAM,CAAC,OAAO,EAAE,CAAA;IACxB,CAAC;IAED,OAAO;QACL,MAAM;QACN,MAAM,EAAE,MAAM,CAAC,YAAY,EAAE;KAC9B,CAAA;AACH,CAAC;AA1BD,0CA0BC;AAED,MAAM,cAAc,GAA2B;IAC7C,WAAW,EAAE,CAAC,GAAG,IAAI;IACrB,cAAc,EAAE,CAAC;IACjB,qBAAqB,EAAE,EAAE,GAAG,IAAI;IAChC,YAAY,EAAE,IAAI,EAAE,sDAAsD;IAC1E,aAAa,EAAE,IAAI;IACnB,UAAU,EAAE,EAAE;IACd,eAAe,EAAE,KAAK;IACtB,WAAW,EAAE,GAAG,EAAE,GAAE,CAAC;IACrB,cAAc,EAAE,GAAG,EAAE,GAAE,CAAC;IACxB,UAAU,EAAE,KAAK,IAAI,EAAE,CAAC,CAAC,EAAE,CAAC;IAE5B,mBAAmB,CAAC,GAAW;QAC7B,OAAO,GAAG,CAAC,OAAO,CAAC,YAAY,EAAE,OAAO,CAAC,CAAC,OAAO,CAAC,WAAW,EAAE,MAAM,CAAC,CAAA;IACxE,CAAC;CACF,CAAA"}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.parseCookies = exports.ClientCookies = void 0;
|
|
4
|
+
/** Limited Cookie support for non-browser clients */
|
|
5
|
+
class ClientCookies {
|
|
6
|
+
constructor() {
|
|
7
|
+
this.cookies = {};
|
|
8
|
+
}
|
|
9
|
+
updateCookies(setCookies) {
|
|
10
|
+
setCookies.forEach((c) => {
|
|
11
|
+
const [name, value] = c.split(";")[0].split("=");
|
|
12
|
+
this.cookies[name] = value;
|
|
13
|
+
});
|
|
14
|
+
}
|
|
15
|
+
getCookieString() {
|
|
16
|
+
return Object.entries(this.cookies)
|
|
17
|
+
.map(([k, v]) => `${k}=${v}`)
|
|
18
|
+
.join("; ");
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
exports.ClientCookies = ClientCookies;
|
|
22
|
+
function parseCookies(str) {
|
|
23
|
+
const rx = /([^;=\s]*)=([^;]*)/g;
|
|
24
|
+
const r = {};
|
|
25
|
+
for (let m; (m = rx.exec(str));) {
|
|
26
|
+
r[m[1]] = decodeURIComponent(m[2]);
|
|
27
|
+
}
|
|
28
|
+
return r;
|
|
29
|
+
}
|
|
30
|
+
exports.parseCookies = parseCookies;
|
|
31
|
+
//# sourceMappingURL=cookies.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cookies.js","sourceRoot":"","sources":["../../src/utils/cookies.ts"],"names":[],"mappings":";;;AAAA,qDAAqD;AACrD,MAAa,aAAa;IAA1B;QACU,YAAO,GAA2B,EAAE,CAAA;IAc9C,CAAC;IAZC,aAAa,CAAC,UAAoB;QAChC,UAAU,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,EAAE;YACvB,MAAM,CAAC,IAAI,EAAE,KAAK,CAAC,GAAG,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,CAAA;YAChD,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,GAAG,KAAK,CAAA;QAC5B,CAAC,CAAC,CAAA;IACJ,CAAC;IAED,eAAe;QACb,OAAO,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,OAAO,CAAC;aAChC,GAAG,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC;aAC5B,IAAI,CAAC,IAAI,CAAC,CAAA;IACf,CAAC;CACF;AAfD,sCAeC;AAED,SAAgB,YAAY,CAAC,GAAW;IACtC,MAAM,EAAE,GAAG,qBAAqB,CAAA;IAChC,MAAM,CAAC,GAA2B,EAAE,CAAA;IAEpC,KAAK,IAAI,CAAC,EAAE,CAAC,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,GAAI,CAAC;QACjC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,kBAAkB,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAA;IACpC,CAAC;IACD,OAAO,CAAC,CAAA;AACV,CAAC;AARD,oCAQC"}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.environment = exports.Environment = void 0;
|
|
4
|
+
var Environment;
|
|
5
|
+
(function (Environment) {
|
|
6
|
+
Environment[Environment["Browser"] = 0] = "Browser";
|
|
7
|
+
Environment[Environment["Node"] = 1] = "Node";
|
|
8
|
+
Environment[Environment["ReactNative"] = 2] = "ReactNative";
|
|
9
|
+
})(Environment || (exports.Environment = Environment = {}));
|
|
10
|
+
exports.environment = getEnvironment();
|
|
11
|
+
function getEnvironment() {
|
|
12
|
+
if (typeof document !== "undefined") {
|
|
13
|
+
return Environment.Browser;
|
|
14
|
+
}
|
|
15
|
+
else if (typeof navigator !== "undefined" && navigator.product === "ReactNative") {
|
|
16
|
+
return Environment.ReactNative;
|
|
17
|
+
}
|
|
18
|
+
else {
|
|
19
|
+
return Environment.Node;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
//# sourceMappingURL=env.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"env.js","sourceRoot":"","sources":["../../src/utils/env.ts"],"names":[],"mappings":";;;AAAA,IAAY,WAIX;AAJD,WAAY,WAAW;IACrB,mDAAO,CAAA;IACP,6CAAI,CAAA;IACJ,2DAAW,CAAA;AACb,CAAC,EAJW,WAAW,2BAAX,WAAW,QAItB;AAEY,QAAA,WAAW,GAAG,cAAc,EAAE,CAAA;AAE3C,SAAS,cAAc;IACrB,IAAI,OAAO,QAAQ,KAAK,WAAW,EAAE,CAAC;QACpC,OAAO,WAAW,CAAC,OAAO,CAAA;IAC5B,CAAC;SAAM,IAAI,OAAO,SAAS,KAAK,WAAW,IAAI,SAAS,CAAC,OAAO,KAAK,aAAa,EAAE,CAAC;QACnF,OAAO,WAAW,CAAC,WAAW,CAAA;IAChC,CAAC;SAAM,CAAC;QACN,OAAO,WAAW,CAAC,IAAI,CAAA;IACzB,CAAC;AACH,CAAC"}
|
package/package.json
CHANGED
package/src/client/HttpClient.ts
CHANGED
|
@@ -1,11 +1,14 @@
|
|
|
1
1
|
import {CLIENT_ID_HEADER, RpcErrors} from "../rpc.js"
|
|
2
2
|
import {safeParseJson, safeStringify} from "../utils/json.js"
|
|
3
|
+
import {ClientCookies} from "../utils/cookies.js"
|
|
4
|
+
import {environment, Environment} from "../utils/env.js"
|
|
3
5
|
|
|
4
6
|
export class HttpClient {
|
|
5
7
|
constructor(
|
|
6
8
|
private url: string,
|
|
7
9
|
private clientId: string,
|
|
8
|
-
private getHeaders: () => Promise<Record<string, string
|
|
10
|
+
private getHeaders: () => Promise<Record<string, string>>,
|
|
11
|
+
private cookies: ClientCookies
|
|
9
12
|
) {}
|
|
10
13
|
|
|
11
14
|
async call(itemName: string, params: unknown[], callTimeout: number): Promise<unknown> {
|
|
@@ -36,6 +39,14 @@ export class HttpClient {
|
|
|
36
39
|
try {
|
|
37
40
|
const {signal, finished} = timeoutSignal(callTimeout)
|
|
38
41
|
|
|
42
|
+
if (environment != Environment.Browser) {
|
|
43
|
+
const cookie = this.cookies.getCookieString()
|
|
44
|
+
|
|
45
|
+
if (cookie) {
|
|
46
|
+
headers["Cookie"] = cookie
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
39
50
|
const response = await fetch(itemUrl, {
|
|
40
51
|
method,
|
|
41
52
|
headers: {
|
|
@@ -49,6 +60,14 @@ export class HttpClient {
|
|
|
49
60
|
|
|
50
61
|
finished()
|
|
51
62
|
|
|
63
|
+
if (environment != Environment.Browser) {
|
|
64
|
+
const cookie = response.headers.get("set-cookie")
|
|
65
|
+
|
|
66
|
+
if (cookie) {
|
|
67
|
+
this.cookies.updateCookies(cookie.split(","))
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
52
71
|
if (response.status == 204) {
|
|
53
72
|
return
|
|
54
73
|
}
|
|
@@ -6,18 +6,20 @@ import {nanoid} from "nanoid"
|
|
|
6
6
|
import {createRemote, ServicesWithSubscriptions} from "./remote.js"
|
|
7
7
|
import {ConsumeServicesOptions, RpcClient} from "./index.js"
|
|
8
8
|
import {withMiddlewares} from "../utils/middleware.js"
|
|
9
|
+
import {ClientCookies} from "../utils/cookies.js"
|
|
9
10
|
|
|
10
11
|
export class RpcClientImpl<S extends Services<S>> implements RpcClient {
|
|
11
12
|
constructor(
|
|
12
13
|
url: string,
|
|
13
14
|
private readonly options: ConsumeServicesOptions
|
|
14
15
|
) {
|
|
15
|
-
this.httpClient = new HttpClient(url, this.clientId, options.getHeaders)
|
|
16
|
+
this.httpClient = new HttpClient(url, this.clientId, options.getHeaders, this.cookies)
|
|
16
17
|
this.remoteSubscriptions = new RemoteSubscriptions()
|
|
17
18
|
|
|
18
19
|
this.connection = new WebSocketConnection(
|
|
19
20
|
options.getSubscriptionsUrl(url),
|
|
20
21
|
this.clientId,
|
|
22
|
+
this.cookies,
|
|
21
23
|
{
|
|
22
24
|
subscriptions: options.subscriptions,
|
|
23
25
|
errorDelayMaxDuration: options.errorDelayMaxDuration,
|
|
@@ -37,10 +39,11 @@ export class RpcClientImpl<S extends Services<S>> implements RpcClient {
|
|
|
37
39
|
)
|
|
38
40
|
}
|
|
39
41
|
|
|
40
|
-
|
|
42
|
+
public readonly clientId = nanoid()
|
|
41
43
|
private readonly httpClient: HttpClient
|
|
42
44
|
private readonly remoteSubscriptions: RemoteSubscriptions
|
|
43
45
|
private readonly connection: WebSocketConnection
|
|
46
|
+
private readonly cookies: ClientCookies = new ClientCookies()
|
|
44
47
|
|
|
45
48
|
isConnected() {
|
|
46
49
|
return this.connection.isConnected()
|
|
@@ -1,11 +1,15 @@
|
|
|
1
1
|
import {log} from "../logger.js"
|
|
2
2
|
import {safeParseJson} from "../utils/json.js"
|
|
3
3
|
import {adelay} from "../utils/promises.js"
|
|
4
|
+
import {environment, Environment} from "../utils/env.js"
|
|
5
|
+
import {ClientCookies} from "../utils/cookies.js"
|
|
6
|
+
import type {IncomingMessage} from "http"
|
|
4
7
|
|
|
5
8
|
export class WebSocketConnection {
|
|
6
9
|
constructor(
|
|
7
10
|
private readonly url: string,
|
|
8
11
|
private readonly clientId: string,
|
|
12
|
+
private readonly cookies: ClientCookies,
|
|
9
13
|
private readonly options: {
|
|
10
14
|
subscriptions: boolean
|
|
11
15
|
reconnectDelay: number
|
|
@@ -33,9 +37,11 @@ export class WebSocketConnection {
|
|
|
33
37
|
}
|
|
34
38
|
}
|
|
35
39
|
|
|
40
|
+
private waitConnectionPromise: Promise<void> | undefined
|
|
41
|
+
|
|
36
42
|
/**
|
|
37
43
|
* Connect to the server, on each disconnect try to disconnect.
|
|
38
|
-
* Resolves at
|
|
44
|
+
* Resolves at next successful connect. Reconnection loop continues even after resolution
|
|
39
45
|
* Never rejects
|
|
40
46
|
*/
|
|
41
47
|
connect() {
|
|
@@ -45,29 +51,36 @@ export class WebSocketConnection {
|
|
|
45
51
|
}
|
|
46
52
|
|
|
47
53
|
// already started connecting
|
|
48
|
-
if (this.
|
|
54
|
+
if (this.waitConnectionPromise) return this.waitConnectionPromise
|
|
49
55
|
|
|
50
56
|
// start connection process
|
|
51
|
-
this.disconnectedMark = false
|
|
52
57
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
58
|
+
let resolveConnectionPromise: () => void
|
|
59
|
+
let errorDelay = 0
|
|
60
|
+
|
|
61
|
+
this.waitConnectionPromise = new Promise(async (resolve) => {
|
|
62
|
+
resolveConnectionPromise = resolve
|
|
56
63
|
|
|
57
64
|
while (true) {
|
|
58
65
|
// connect, and wait for ...
|
|
59
66
|
await new Promise<void>((resolve) => {
|
|
60
|
-
|
|
61
|
-
|
|
67
|
+
const connectionPromise = this.establishConnection(() => {
|
|
68
|
+
// 1. ...disconnected
|
|
69
|
+
|
|
70
|
+
// recreate promise so new clients will wait for new connection
|
|
71
|
+
this.waitConnectionPromise = new Promise(
|
|
72
|
+
(resolve) => (resolveConnectionPromise = resolve)
|
|
73
|
+
)
|
|
74
|
+
|
|
75
|
+
resolve()
|
|
76
|
+
})
|
|
62
77
|
|
|
63
78
|
connectionPromise.then(
|
|
64
79
|
() => {
|
|
65
80
|
// first reconnect after successful connection is done without delay
|
|
66
81
|
errorDelay = 0
|
|
67
82
|
|
|
68
|
-
|
|
69
|
-
onFirstConnection()
|
|
70
|
-
onFirstConnection = () => {}
|
|
83
|
+
resolveConnectionPromise()
|
|
71
84
|
},
|
|
72
85
|
(e) => {
|
|
73
86
|
log.warn("Unable to connect WS", e)
|
|
@@ -93,6 +106,8 @@ export class WebSocketConnection {
|
|
|
93
106
|
errorDelay = Math.round(Math.random() * this.options.errorDelayMaxDuration)
|
|
94
107
|
}
|
|
95
108
|
})
|
|
109
|
+
|
|
110
|
+
return this.waitConnectionPromise
|
|
96
111
|
}
|
|
97
112
|
|
|
98
113
|
public isConnected() {
|
|
@@ -107,7 +122,26 @@ export class WebSocketConnection {
|
|
|
107
122
|
private async establishConnection(onDisconnected: () => void): Promise<void> {
|
|
108
123
|
return new Promise(async (resolve, reject) => {
|
|
109
124
|
try {
|
|
110
|
-
|
|
125
|
+
let socket: WebSocket
|
|
126
|
+
|
|
127
|
+
if ([Environment.ReactNative, Environment.Node].includes(environment)) {
|
|
128
|
+
// use RN WS or node-ws headers extensions to set cookie
|
|
129
|
+
let options = undefined
|
|
130
|
+
|
|
131
|
+
const cookie = this.cookies.getCookieString()
|
|
132
|
+
if (cookie) {
|
|
133
|
+
options = {
|
|
134
|
+
headers: {
|
|
135
|
+
Cookie: cookie,
|
|
136
|
+
},
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
socket = new (WebSocket as any)(this.url, this.clientId, options)
|
|
141
|
+
} else {
|
|
142
|
+
// rely on browser cookie handling
|
|
143
|
+
socket = new WebSocket(this.url, this.clientId)
|
|
144
|
+
}
|
|
111
145
|
|
|
112
146
|
let connected = false
|
|
113
147
|
|
|
@@ -162,7 +196,7 @@ export class WebSocketConnection {
|
|
|
162
196
|
}
|
|
163
197
|
|
|
164
198
|
private socket: WebSocket | null = null
|
|
165
|
-
private disconnectedMark =
|
|
199
|
+
private disconnectedMark = false
|
|
166
200
|
private pingTimeout: NodeJS.Timeout | null = null
|
|
167
201
|
|
|
168
202
|
private heartbeat() {
|
package/src/client/index.ts
CHANGED
|
@@ -4,6 +4,8 @@ import {RpcClientImpl} from "./RpcClientImpl.js"
|
|
|
4
4
|
import {Middleware} from "../utils/middleware.js"
|
|
5
5
|
|
|
6
6
|
export type RpcClient = {
|
|
7
|
+
readonly clientId: string
|
|
8
|
+
|
|
7
9
|
isConnected(): boolean
|
|
8
10
|
close(): Promise<void>
|
|
9
11
|
|
|
@@ -65,7 +67,7 @@ const defaultOptions: ConsumeServicesOptions = {
|
|
|
65
67
|
onConnected: () => {},
|
|
66
68
|
onDisconnected: () => {},
|
|
67
69
|
getHeaders: async () => ({}),
|
|
68
|
-
|
|
70
|
+
|
|
69
71
|
getSubscriptionsUrl(url: string): string {
|
|
70
72
|
return url.replace(/^https(.*)/, "wss$1").replace(/^http(.*)/, "ws$1")
|
|
71
73
|
},
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/** Limited Cookie support for non-browser clients */
|
|
2
|
+
export class ClientCookies {
|
|
3
|
+
private cookies: Record<string, string> = {}
|
|
4
|
+
|
|
5
|
+
updateCookies(setCookies: string[]) {
|
|
6
|
+
setCookies.forEach((c) => {
|
|
7
|
+
const [name, value] = c.split(";")[0].split("=")
|
|
8
|
+
this.cookies[name] = value
|
|
9
|
+
})
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
getCookieString(): string {
|
|
13
|
+
return Object.entries(this.cookies)
|
|
14
|
+
.map(([k, v]) => `${k}=${v}`)
|
|
15
|
+
.join("; ")
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function parseCookies(str: string): Record<string, string> {
|
|
20
|
+
const rx = /([^;=\s]*)=([^;]*)/g
|
|
21
|
+
const r: Record<string, string> = {}
|
|
22
|
+
|
|
23
|
+
for (let m; (m = rx.exec(str)); ) {
|
|
24
|
+
r[m[1]] = decodeURIComponent(m[2])
|
|
25
|
+
}
|
|
26
|
+
return r
|
|
27
|
+
}
|
package/src/utils/env.ts
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export enum Environment {
|
|
2
|
+
Browser,
|
|
3
|
+
Node,
|
|
4
|
+
ReactNative,
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export const environment = getEnvironment()
|
|
8
|
+
|
|
9
|
+
function getEnvironment(): Environment {
|
|
10
|
+
if (typeof document !== "undefined") {
|
|
11
|
+
return Environment.Browser
|
|
12
|
+
} else if (typeof navigator !== "undefined" && navigator.product === "ReactNative") {
|
|
13
|
+
return Environment.ReactNative
|
|
14
|
+
} else {
|
|
15
|
+
return Environment.Node
|
|
16
|
+
}
|
|
17
|
+
}
|
package/tests/connection.ts
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import {assert} from "chai"
|
|
2
|
-
import {createTestClient, startTestServer, testClient, testServer} from "./testUtils.js"
|
|
3
|
-
import WebSocket from "ws"
|
|
2
|
+
import {createTestClient, startTestServer, TEST_PORT, testClient, testServer} from "./testUtils.js"
|
|
3
|
+
import WebSocket, {WebSocketServer} from "ws"
|
|
4
4
|
import {adelay} from "../src/utils/promises.js"
|
|
5
|
+
import http, {IncomingMessage} from "http"
|
|
6
|
+
import {parseCookies} from "../src/utils/cookies.js"
|
|
5
7
|
|
|
6
8
|
describe("connection", () => {
|
|
7
9
|
it("server close connection on ping timeout", async () => {
|
|
@@ -132,4 +134,87 @@ describe("connection", () => {
|
|
|
132
134
|
|
|
133
135
|
assert.equal(disconnected, true)
|
|
134
136
|
})
|
|
137
|
+
|
|
138
|
+
describe("cookies", () => {
|
|
139
|
+
it("handle cookies in subseq requests", async () => {
|
|
140
|
+
let call = 0
|
|
141
|
+
|
|
142
|
+
let sentClientCookies: Record<string, string> = {}
|
|
143
|
+
|
|
144
|
+
const httpServer = http.createServer((req, res) => {
|
|
145
|
+
const headers: Record<string, string> = {"Content-Type": "text/plain"}
|
|
146
|
+
|
|
147
|
+
if (!call++) {
|
|
148
|
+
headers["Set-Cookie"] = `name=value; path=/; secure; samesite=none; httponly`
|
|
149
|
+
} else {
|
|
150
|
+
sentClientCookies = parseCookies(req.headers.cookie || "")
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
res.writeHead(200, headers)
|
|
154
|
+
|
|
155
|
+
res.end("ok")
|
|
156
|
+
})
|
|
157
|
+
|
|
158
|
+
let resolveStarted = () => {}
|
|
159
|
+
const started = new Promise<void>((r) => (resolveStarted = r))
|
|
160
|
+
httpServer.listen(TEST_PORT, () => resolveStarted())
|
|
161
|
+
await started
|
|
162
|
+
|
|
163
|
+
const client = await createTestClient<{call(): Promise<string>}>()
|
|
164
|
+
|
|
165
|
+
await client.call()
|
|
166
|
+
await client.call()
|
|
167
|
+
|
|
168
|
+
assert.equal(Object.keys(sentClientCookies).length, 1)
|
|
169
|
+
assert.equal(sentClientCookies["name"], "value")
|
|
170
|
+
|
|
171
|
+
let resolveStopped = () => {}
|
|
172
|
+
const stopped = new Promise<void>((r) => (resolveStopped = r))
|
|
173
|
+
httpServer.closeAllConnections()
|
|
174
|
+
httpServer.close(() => resolveStopped())
|
|
175
|
+
await stopped
|
|
176
|
+
})
|
|
177
|
+
|
|
178
|
+
it("set cookie in http, use in ws", async () => {
|
|
179
|
+
const httpServer = http.createServer((req, res) => {
|
|
180
|
+
const headers: Record<string, string> = {
|
|
181
|
+
"Content-Type": "text/plain",
|
|
182
|
+
"Set-Cookie": "name=value; path=/; secure; samesite=none; httponly",
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
res.writeHead(200, headers)
|
|
186
|
+
|
|
187
|
+
res.end("ok")
|
|
188
|
+
})
|
|
189
|
+
|
|
190
|
+
const wss = new WebSocketServer({server: httpServer})
|
|
191
|
+
|
|
192
|
+
let sentClientCookies: Record<string, string> = {}
|
|
193
|
+
wss.on("connection", (ws: unknown, req: IncomingMessage) => {
|
|
194
|
+
sentClientCookies = parseCookies(req.headers.cookie || "")
|
|
195
|
+
})
|
|
196
|
+
|
|
197
|
+
let resolveStarted = () => {}
|
|
198
|
+
const started = new Promise<void>((r) => (resolveStarted = r))
|
|
199
|
+
httpServer.listen(TEST_PORT, () => resolveStarted())
|
|
200
|
+
await started
|
|
201
|
+
|
|
202
|
+
const client = await createTestClient<{call(): Promise<string>}>()
|
|
203
|
+
await client.call()
|
|
204
|
+
|
|
205
|
+
await client.call.subscribe(() => {})
|
|
206
|
+
|
|
207
|
+
assert.equal(Object.keys(sentClientCookies).length, 1)
|
|
208
|
+
assert.equal(sentClientCookies["name"], "value")
|
|
209
|
+
|
|
210
|
+
await testClient!.close()
|
|
211
|
+
|
|
212
|
+
let resolveStopped = () => {}
|
|
213
|
+
const stopped = new Promise<void>((r) => (resolveStopped = r))
|
|
214
|
+
httpServer.closeIdleConnections()
|
|
215
|
+
httpServer.closeAllConnections()
|
|
216
|
+
httpServer.close(() => resolveStopped())
|
|
217
|
+
await stopped
|
|
218
|
+
})
|
|
219
|
+
})
|
|
135
220
|
})
|
package/tests/subscriptions.ts
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import {assert} from "chai"
|
|
2
2
|
import {createTestClient, startTestServer, testClient, testServer} from "./testUtils.js"
|
|
3
3
|
import {adelay} from "../src/utils/promises.js"
|
|
4
|
-
import {CallOptions, RpcErrors} from "../src/index.js"
|
|
4
|
+
import {CallOptions, RpcConnectionContext, RpcErrors} from "../src/index.js"
|
|
5
|
+
import {IncomingMessage} from "http"
|
|
6
|
+
import {CLIENT_ID_HEADER} from "../src/rpc.js"
|
|
5
7
|
|
|
6
8
|
describe("Subscriptions", () => {
|
|
7
9
|
it("subscribe delivers data", async () => {
|
|
@@ -593,4 +595,63 @@ describe("Subscriptions", () => {
|
|
|
593
595
|
|
|
594
596
|
assert.equal(received, 1)
|
|
595
597
|
})
|
|
598
|
+
|
|
599
|
+
it("subscribe waits for connection", async () => {
|
|
600
|
+
const delay = 50
|
|
601
|
+
|
|
602
|
+
let connectedClients = 0
|
|
603
|
+
let serverCalled = 0
|
|
604
|
+
|
|
605
|
+
const services = await startTestServer(
|
|
606
|
+
{
|
|
607
|
+
test: {
|
|
608
|
+
async op(params: {key: number}): Promise<number> {
|
|
609
|
+
serverCalled++
|
|
610
|
+
return 1
|
|
611
|
+
},
|
|
612
|
+
},
|
|
613
|
+
},
|
|
614
|
+
{
|
|
615
|
+
async createConnectionContext(req: IncomingMessage): Promise<RpcConnectionContext> {
|
|
616
|
+
const header = req.headers[CLIENT_ID_HEADER]
|
|
617
|
+
|
|
618
|
+
connectedClients++
|
|
619
|
+
|
|
620
|
+
return {
|
|
621
|
+
clientId: (Array.isArray(header) ? header[0] : header) || "anon",
|
|
622
|
+
}
|
|
623
|
+
},
|
|
624
|
+
}
|
|
625
|
+
)
|
|
626
|
+
|
|
627
|
+
const client = await createTestClient<typeof services>({
|
|
628
|
+
callTimeout: 2 * delay,
|
|
629
|
+
})
|
|
630
|
+
|
|
631
|
+
let received1
|
|
632
|
+
let received2
|
|
633
|
+
|
|
634
|
+
client.test.op.subscribe(
|
|
635
|
+
(val) => {
|
|
636
|
+
received1 = val
|
|
637
|
+
},
|
|
638
|
+
{key: 1}
|
|
639
|
+
)
|
|
640
|
+
|
|
641
|
+
await adelay(40)
|
|
642
|
+
|
|
643
|
+
client.test.op.subscribe(
|
|
644
|
+
(val) => {
|
|
645
|
+
received2 = val
|
|
646
|
+
},
|
|
647
|
+
{key: 2}
|
|
648
|
+
)
|
|
649
|
+
|
|
650
|
+
await adelay(1.5 * delay)
|
|
651
|
+
|
|
652
|
+
assert.equal(received1, 1)
|
|
653
|
+
assert.equal(received2, 1)
|
|
654
|
+
assert.equal(serverCalled, 2)
|
|
655
|
+
assert.equal(testServer!._allSubscriptions().length, 2)
|
|
656
|
+
})
|
|
596
657
|
})
|