@push-rpc/next 2.0.0-beta.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.prettierrc.json +7 -0
- package/LICENSE +21 -0
- package/README.md +26 -0
- package/dist/client/HttpClient.d.ts +10 -0
- package/dist/client/HttpClient.js +67 -0
- package/dist/client/HttpClient.js.map +1 -0
- package/dist/client/RemoteSubscriptions.d.ts +14 -0
- package/dist/client/RemoteSubscriptions.js +84 -0
- package/dist/client/RemoteSubscriptions.js.map +1 -0
- package/dist/client/RpcClientImpl.d.ts +22 -0
- package/dist/client/RpcClientImpl.js +96 -0
- package/dist/client/RpcClientImpl.js.map +1 -0
- package/dist/client/WebSocketConnection.d.ts +33 -0
- package/dist/client/WebSocketConnection.js +152 -0
- package/dist/client/WebSocketConnection.js.map +1 -0
- package/dist/client/index.d.ts +22 -0
- package/dist/client/index.js +28 -0
- package/dist/client/index.js.map +1 -0
- package/dist/client/remote.d.ts +14 -0
- package/dist/client/remote.js +66 -0
- package/dist/client/remote.js.map +1 -0
- package/dist/index.d.ts +12 -0
- package/dist/index.js +20 -0
- package/dist/index.js.map +1 -0
- package/dist/logger.d.ts +8 -0
- package/dist/logger.js +9 -0
- package/dist/logger.js.map +1 -0
- package/dist/rpc.d.ts +36 -0
- package/dist/rpc.js +32 -0
- package/dist/rpc.js.map +1 -0
- package/dist/server/ConnectionsServer.d.ts +13 -0
- package/dist/server/ConnectionsServer.js +60 -0
- package/dist/server/ConnectionsServer.js.map +1 -0
- package/dist/server/LocalSubscriptions.d.ts +12 -0
- package/dist/server/LocalSubscriptions.js +113 -0
- package/dist/server/LocalSubscriptions.js.map +1 -0
- package/dist/server/RpcServerImpl.d.ts +23 -0
- package/dist/server/RpcServerImpl.js +164 -0
- package/dist/server/RpcServerImpl.js.map +1 -0
- package/dist/server/http.d.ts +9 -0
- package/dist/server/http.js +83 -0
- package/dist/server/http.js.map +1 -0
- package/dist/server/index.d.ts +29 -0
- package/dist/server/index.js +31 -0
- package/dist/server/index.js.map +1 -0
- package/dist/server/local.d.ts +15 -0
- package/dist/server/local.js +46 -0
- package/dist/server/local.js.map +1 -0
- package/dist/utils/json.d.ts +2 -0
- package/dist/utils/json.js +34 -0
- package/dist/utils/json.js.map +1 -0
- package/dist/utils/middleware.d.ts +2 -0
- package/dist/utils/middleware.js +31 -0
- package/dist/utils/middleware.js.map +1 -0
- package/dist/utils/promises.d.ts +5 -0
- package/dist/utils/promises.js +29 -0
- package/dist/utils/promises.js.map +1 -0
- package/dist/utils/throttle.d.ts +4 -0
- package/dist/utils/throttle.js +40 -0
- package/dist/utils/throttle.js.map +1 -0
- package/dist/utils/types.d.ts +1 -0
- package/dist/utils/types.js +3 -0
- package/dist/utils/types.js.map +1 -0
- package/example/api.ts +15 -0
- package/example/client.ts +16 -0
- package/example/server.ts +37 -0
- package/package.json +34 -0
- package/src/client/HttpClient.ts +80 -0
- package/src/client/RemoteSubscriptions.ts +121 -0
- package/src/client/RpcClientImpl.ts +177 -0
- package/src/client/WebSocketConnection.ts +183 -0
- package/src/client/index.ts +56 -0
- package/src/client/remote.ts +118 -0
- package/src/index.ts +18 -0
- package/src/logger.ts +12 -0
- package/src/rpc.ts +51 -0
- package/src/server/ConnectionsServer.ts +78 -0
- package/src/server/LocalSubscriptions.ts +155 -0
- package/src/server/RpcServerImpl.ts +252 -0
- package/src/server/http.ts +109 -0
- package/src/server/index.ts +65 -0
- package/src/server/local.ts +80 -0
- package/src/utils/json.ts +32 -0
- package/src/utils/middleware.ts +38 -0
- package/src/utils/promises.ts +25 -0
- package/src/utils/throttle.ts +48 -0
- package/src/utils/types.ts +1 -0
- package/tests/calls.ts +215 -0
- package/tests/connection.ts +107 -0
- package/tests/context.ts +176 -0
- package/tests/middleware.ts +112 -0
- package/tests/misc.ts +187 -0
- package/tests/subscriptions.ts +442 -0
- package/tests/testUtils.ts +52 -0
- package/tests/triggers.ts +138 -0
- package/tsconfig.cjs.json +20 -0
- package/tsconfig.json +26 -0
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import {RemoteFunction, Services} from "../rpc.js"
|
|
2
|
+
import {LocalSubscriptions} from "./LocalSubscriptions.js"
|
|
3
|
+
import {ExtractPromiseResult} from "../utils/types.js"
|
|
4
|
+
import {ThrottleArgsReducer} from "../utils/throttle.js"
|
|
5
|
+
|
|
6
|
+
export type ThrottleSettings<D> = {
|
|
7
|
+
timeout: number
|
|
8
|
+
reducer?: ThrottleArgsReducer<D>
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function withTriggers<T extends Services<T>>(
|
|
12
|
+
localSubscriptions: LocalSubscriptions,
|
|
13
|
+
services: T,
|
|
14
|
+
name = ""
|
|
15
|
+
): ServicesWithTriggers<T> {
|
|
16
|
+
const cachedItems: any = {}
|
|
17
|
+
const skippedProps = ["length", "name", "prototype", "arguments", "caller"]
|
|
18
|
+
|
|
19
|
+
return new Proxy(services, {
|
|
20
|
+
get(target: any, propName: string) {
|
|
21
|
+
// skip internal props
|
|
22
|
+
if (typeof propName != "string") return target[propName]
|
|
23
|
+
|
|
24
|
+
// skip other system props
|
|
25
|
+
if (["then", "catch", "toJSON", ...skippedProps].includes(propName)) return target[propName]
|
|
26
|
+
|
|
27
|
+
const itemName = name ? name + "/" + propName : propName
|
|
28
|
+
|
|
29
|
+
if (typeof target[propName] == "function") {
|
|
30
|
+
const delegate = (...params: unknown[]) => {
|
|
31
|
+
return target[propName](...params)
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
delegate.trigger = (filter: Record<string, unknown> = {}, suppliedData?: unknown) => {
|
|
35
|
+
// triggers are delayed for consumers to receive updates after the current call ends.
|
|
36
|
+
setTimeout(() => {
|
|
37
|
+
localSubscriptions.trigger(itemName, filter, suppliedData)
|
|
38
|
+
}, 0)
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
delegate.throttle = (settings: ThrottleSettings<unknown>) => {
|
|
42
|
+
localSubscriptions.throttleItem(itemName, settings)
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
return delegate
|
|
46
|
+
} else if (!cachedItems[propName]) {
|
|
47
|
+
cachedItems[propName] = withTriggers(
|
|
48
|
+
localSubscriptions,
|
|
49
|
+
services[propName as keyof T] as any,
|
|
50
|
+
itemName
|
|
51
|
+
)
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
return cachedItems[propName]
|
|
55
|
+
},
|
|
56
|
+
|
|
57
|
+
set(target, propName, value) {
|
|
58
|
+
cachedItems[propName] = value
|
|
59
|
+
return true
|
|
60
|
+
},
|
|
61
|
+
|
|
62
|
+
ownKeys() {
|
|
63
|
+
return [...skippedProps, ...Object.keys(cachedItems)]
|
|
64
|
+
},
|
|
65
|
+
})
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export type ServicesWithTriggers<T extends Services<T>> = {
|
|
69
|
+
[K in keyof T]: T[K] extends RemoteFunction
|
|
70
|
+
? T[K] & {
|
|
71
|
+
trigger(
|
|
72
|
+
filter?: Partial<Parameters<T[K]>[0]>,
|
|
73
|
+
suppliedData?: ExtractPromiseResult<ReturnType<T[K]>>
|
|
74
|
+
): void
|
|
75
|
+
throttle(settings: ThrottleSettings<ExtractPromiseResult<ReturnType<T[K]>>>): void
|
|
76
|
+
}
|
|
77
|
+
: T[K] extends object
|
|
78
|
+
? ServicesWithTriggers<T[K]>
|
|
79
|
+
: never
|
|
80
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import stringify from "fast-stringify"
|
|
2
|
+
|
|
3
|
+
export function safeStringify(value: any): string {
|
|
4
|
+
// @ts-ignore
|
|
5
|
+
return stringify(value)
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export function safeParseJson(json: string): any {
|
|
9
|
+
return JSON.parse(json, dateReviver)
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
function dateReviver(key: string, val: any) {
|
|
13
|
+
if (typeof val == "string") {
|
|
14
|
+
if (ISO8601_secs.test(val)) {
|
|
15
|
+
return new Date(val)
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
if (ISO8601.test(val)) {
|
|
19
|
+
return new Date(val)
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
if (ISO8601_date.test(val)) {
|
|
23
|
+
return new Date(val)
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
return val
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const ISO8601 = /^\d\d\d\d-\d\d-\d\dT\d\d:\d\d:\d\d.\d\d\dZ$/
|
|
31
|
+
const ISO8601_secs = /^\d\d\d\d-\d\d-\d\dT\d\d:\d\d:\d\dZ$/
|
|
32
|
+
const ISO8601_date = /^\d\d\d\d-\d\d-\d\d$/
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
export type Middleware<Context> = (
|
|
2
|
+
ctx: Context,
|
|
3
|
+
next: (...params: unknown[]) => Promise<unknown>,
|
|
4
|
+
...params: unknown[]
|
|
5
|
+
) => Promise<unknown>
|
|
6
|
+
|
|
7
|
+
export function withMiddlewares<Context>(
|
|
8
|
+
ctx: Context,
|
|
9
|
+
middlewares: Middleware<Context>[],
|
|
10
|
+
final: (...params: unknown[]) => Promise<unknown>,
|
|
11
|
+
...params: any
|
|
12
|
+
) {
|
|
13
|
+
return (function (next, ...params) {
|
|
14
|
+
let index = -1
|
|
15
|
+
return dispatch(0, ...params)
|
|
16
|
+
|
|
17
|
+
function dispatch(i: number, ...p: unknown[]): Promise<unknown> {
|
|
18
|
+
if (i <= index) return Promise.reject(new Error("next() called multiple times"))
|
|
19
|
+
|
|
20
|
+
// use previous invocation params
|
|
21
|
+
if (!p.length) {
|
|
22
|
+
p = params
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
index = i
|
|
26
|
+
|
|
27
|
+
try {
|
|
28
|
+
if (i === middlewares.length) {
|
|
29
|
+
return Promise.resolve(next(...p))
|
|
30
|
+
} else {
|
|
31
|
+
return Promise.resolve(middlewares[i](ctx, dispatch.bind(null, i + 1), ...p))
|
|
32
|
+
}
|
|
33
|
+
} catch (err) {
|
|
34
|
+
return Promise.reject(err)
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
})(final, ...params)
|
|
38
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
export class PromiseCache {
|
|
2
|
+
invoke<T>(cacheKey: unknown, supplier: () => Promise<T>): Promise<T> {
|
|
3
|
+
const key = JSON.stringify(cacheKey)
|
|
4
|
+
|
|
5
|
+
if (!this.cache[key]) {
|
|
6
|
+
this.cache[key] = supplier()
|
|
7
|
+
.then((r) => {
|
|
8
|
+
delete this.cache[key]
|
|
9
|
+
return r
|
|
10
|
+
})
|
|
11
|
+
.catch((e) => {
|
|
12
|
+
delete this.cache[key]
|
|
13
|
+
throw e
|
|
14
|
+
})
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
return this.cache[key]
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
private cache: {[key: string]: Promise<any>} = {}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export async function adelay(ms: number) {
|
|
24
|
+
return new Promise((r) => setTimeout(r, ms))
|
|
25
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
export type ThrottleArgsReducer<D> = (prevValue: D, newValue: D) => D
|
|
2
|
+
|
|
3
|
+
export function lastValueReducer<D>(prevValue: D, newValue: D): D {
|
|
4
|
+
return newValue
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export function groupReducer<D>(prevValue: D[], newValue: D[]): D[] {
|
|
8
|
+
if (!Array.isArray(newValue))
|
|
9
|
+
throw new Error("groupReducer should only be used with topics that return arrays")
|
|
10
|
+
|
|
11
|
+
return prevValue ? [...prevValue, ...newValue] : newValue
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function throttle<D>(
|
|
15
|
+
callback: (d: D) => void,
|
|
16
|
+
delay: number,
|
|
17
|
+
reducer: ThrottleArgsReducer<D>
|
|
18
|
+
): (d: D) => void {
|
|
19
|
+
let timer: NodeJS.Timeout
|
|
20
|
+
let lastExec = 0
|
|
21
|
+
|
|
22
|
+
let reducedArg: any
|
|
23
|
+
|
|
24
|
+
function wrapper(this: any, d: D) {
|
|
25
|
+
let self = this
|
|
26
|
+
let elapsed = Date.now() - lastExec
|
|
27
|
+
|
|
28
|
+
function exec() {
|
|
29
|
+
lastExec = Date.now()
|
|
30
|
+
callback.call(self, reducedArg)
|
|
31
|
+
reducedArg = undefined
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
if (timer) {
|
|
35
|
+
clearTimeout(timer)
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
reducedArg = reducer(reducedArg, d)
|
|
39
|
+
|
|
40
|
+
if (elapsed > delay) {
|
|
41
|
+
exec()
|
|
42
|
+
} else {
|
|
43
|
+
timer = setTimeout(exec, delay - elapsed)
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
return wrapper
|
|
48
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export type ExtractPromiseResult<Type> = Type extends Promise<infer X> ? X : never
|
package/tests/calls.ts
ADDED
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
import {assert} from "chai"
|
|
2
|
+
import {createTestClient, startTestServer} from "./testUtils.js"
|
|
3
|
+
import {CallOptions, RpcError, RpcErrors} from "../src/index.js"
|
|
4
|
+
import {adelay} from "../src/utils/promises.js"
|
|
5
|
+
|
|
6
|
+
describe("calls", () => {
|
|
7
|
+
it("client call server", async () => {
|
|
8
|
+
const resp = {r: "asf"}
|
|
9
|
+
|
|
10
|
+
const invocation = {
|
|
11
|
+
req: null as unknown,
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const services = await startTestServer({
|
|
15
|
+
test: {
|
|
16
|
+
async getSomething(req: unknown) {
|
|
17
|
+
invocation.req = req
|
|
18
|
+
return resp
|
|
19
|
+
},
|
|
20
|
+
},
|
|
21
|
+
})
|
|
22
|
+
|
|
23
|
+
const client = await createTestClient<typeof services>()
|
|
24
|
+
|
|
25
|
+
const req = {key: "value"}
|
|
26
|
+
const r = await client.test.getSomething(req)
|
|
27
|
+
|
|
28
|
+
assert.deepEqual(invocation.req, req)
|
|
29
|
+
assert.deepEqual(r, resp)
|
|
30
|
+
})
|
|
31
|
+
|
|
32
|
+
it("error", async () => {
|
|
33
|
+
const message = "bla"
|
|
34
|
+
|
|
35
|
+
const services = await startTestServer({
|
|
36
|
+
test: {
|
|
37
|
+
async getSomething() {
|
|
38
|
+
throw new Error(message)
|
|
39
|
+
},
|
|
40
|
+
},
|
|
41
|
+
})
|
|
42
|
+
|
|
43
|
+
const client = await createTestClient<typeof services>()
|
|
44
|
+
|
|
45
|
+
try {
|
|
46
|
+
await client.test.getSomething()
|
|
47
|
+
assert.fail()
|
|
48
|
+
} catch (e: any) {
|
|
49
|
+
console.log(e)
|
|
50
|
+
assert.equal(e.message, message)
|
|
51
|
+
}
|
|
52
|
+
})
|
|
53
|
+
|
|
54
|
+
it("timeout", async () => {
|
|
55
|
+
const callTimeout = 200
|
|
56
|
+
|
|
57
|
+
const services = await startTestServer({
|
|
58
|
+
test: {
|
|
59
|
+
async longOp() {
|
|
60
|
+
await new Promise((r) => setTimeout(r, 2 * callTimeout))
|
|
61
|
+
},
|
|
62
|
+
},
|
|
63
|
+
})
|
|
64
|
+
|
|
65
|
+
const client = await createTestClient<typeof services>({
|
|
66
|
+
callTimeout,
|
|
67
|
+
})
|
|
68
|
+
|
|
69
|
+
try {
|
|
70
|
+
await client.test.longOp()
|
|
71
|
+
assert.fail()
|
|
72
|
+
} catch (e: any) {
|
|
73
|
+
assert.equal(e.code, RpcErrors.Timeout)
|
|
74
|
+
}
|
|
75
|
+
}).timeout(1000)
|
|
76
|
+
|
|
77
|
+
it("per-call timeout override default", async () => {
|
|
78
|
+
const callTimeout = 200
|
|
79
|
+
|
|
80
|
+
const services = await startTestServer({
|
|
81
|
+
test: {
|
|
82
|
+
async longOp() {
|
|
83
|
+
await adelay(2 * callTimeout)
|
|
84
|
+
},
|
|
85
|
+
},
|
|
86
|
+
})
|
|
87
|
+
|
|
88
|
+
const client = await createTestClient<typeof services>({
|
|
89
|
+
callTimeout: 4 * callTimeout,
|
|
90
|
+
})
|
|
91
|
+
|
|
92
|
+
try {
|
|
93
|
+
await client.test.longOp(new CallOptions({timeout: callTimeout}))
|
|
94
|
+
assert.fail()
|
|
95
|
+
} catch (e: any) {
|
|
96
|
+
assert.equal(e.code, RpcErrors.Timeout)
|
|
97
|
+
}
|
|
98
|
+
}).timeout(5000)
|
|
99
|
+
|
|
100
|
+
it("binds this object", async () => {
|
|
101
|
+
const resp = {r: "asf"}
|
|
102
|
+
|
|
103
|
+
const ss = {
|
|
104
|
+
test: {
|
|
105
|
+
async getSomething() {
|
|
106
|
+
return this.method()
|
|
107
|
+
},
|
|
108
|
+
|
|
109
|
+
async method() {
|
|
110
|
+
return resp
|
|
111
|
+
},
|
|
112
|
+
},
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
const services = await startTestServer(ss)
|
|
116
|
+
|
|
117
|
+
const client = await createTestClient<typeof services>()
|
|
118
|
+
|
|
119
|
+
const r = await client.test.getSomething()
|
|
120
|
+
assert.deepEqual(r, resp)
|
|
121
|
+
})
|
|
122
|
+
|
|
123
|
+
it("binds this class", async () => {
|
|
124
|
+
const resp = {r: "asf"}
|
|
125
|
+
|
|
126
|
+
class B extends A {
|
|
127
|
+
async method() {
|
|
128
|
+
return resp
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
[x: string]: any
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
const services = {
|
|
135
|
+
test: new B(),
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
await startTestServer(services)
|
|
139
|
+
|
|
140
|
+
const client = await createTestClient<typeof services>()
|
|
141
|
+
|
|
142
|
+
const r = await client.test.getSomething()
|
|
143
|
+
assert.deepEqual(r, resp)
|
|
144
|
+
})
|
|
145
|
+
|
|
146
|
+
it("first level lookup", async () => {
|
|
147
|
+
const services = await startTestServer({
|
|
148
|
+
async hello() {
|
|
149
|
+
return "yes"
|
|
150
|
+
},
|
|
151
|
+
})
|
|
152
|
+
|
|
153
|
+
const client = await createTestClient<typeof services>()
|
|
154
|
+
|
|
155
|
+
const r = await client.hello()
|
|
156
|
+
assert.equal("yes", r)
|
|
157
|
+
})
|
|
158
|
+
|
|
159
|
+
it("nested lookup", async () => {
|
|
160
|
+
const services = await startTestServer({
|
|
161
|
+
obj: {
|
|
162
|
+
async hello() {
|
|
163
|
+
return "yes"
|
|
164
|
+
},
|
|
165
|
+
},
|
|
166
|
+
})
|
|
167
|
+
|
|
168
|
+
const client = await createTestClient<typeof services>()
|
|
169
|
+
const r = await client.obj.hello()
|
|
170
|
+
assert.equal("yes", r)
|
|
171
|
+
})
|
|
172
|
+
|
|
173
|
+
it("concurrent call cache", async () => {
|
|
174
|
+
const item = {r: "1"}
|
|
175
|
+
let supplied = 0
|
|
176
|
+
|
|
177
|
+
const server = {
|
|
178
|
+
test: {
|
|
179
|
+
item: async () => {
|
|
180
|
+
await adelay(1)
|
|
181
|
+
supplied++
|
|
182
|
+
return item
|
|
183
|
+
},
|
|
184
|
+
},
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
await startTestServer(server)
|
|
188
|
+
|
|
189
|
+
const client = await createTestClient<typeof server>()
|
|
190
|
+
|
|
191
|
+
let item1
|
|
192
|
+
client.test.item().then((item) => {
|
|
193
|
+
item1 = item
|
|
194
|
+
})
|
|
195
|
+
|
|
196
|
+
let item2
|
|
197
|
+
client.test.item().then((item) => {
|
|
198
|
+
item2 = item
|
|
199
|
+
})
|
|
200
|
+
|
|
201
|
+
await adelay(50)
|
|
202
|
+
assert.deepEqual(item1, item)
|
|
203
|
+
assert.deepEqual(item2, item)
|
|
204
|
+
|
|
205
|
+
assert.equal(supplied, 1)
|
|
206
|
+
})
|
|
207
|
+
})
|
|
208
|
+
|
|
209
|
+
abstract class A {
|
|
210
|
+
async getSomething(): Promise<{r: string}> {
|
|
211
|
+
return this.method()
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
abstract method(): Promise<{r: string}>
|
|
215
|
+
}
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import {assert} from "chai"
|
|
2
|
+
import {createTestClient, startTestServer, testClient, testServer} from "./testUtils.js"
|
|
3
|
+
import WebSocket from "ws"
|
|
4
|
+
import {adelay} from "../src/utils/promises.js"
|
|
5
|
+
|
|
6
|
+
describe("connection", () => {
|
|
7
|
+
it("server close connection on ping timeout", async () => {
|
|
8
|
+
let oldPing: typeof WebSocket.prototype.ping
|
|
9
|
+
|
|
10
|
+
oldPing = WebSocket.prototype.ping
|
|
11
|
+
WebSocket.prototype.ping = () => {}
|
|
12
|
+
|
|
13
|
+
const pingInterval = 100
|
|
14
|
+
|
|
15
|
+
const services = await startTestServer(
|
|
16
|
+
{
|
|
17
|
+
test: {
|
|
18
|
+
async call() {},
|
|
19
|
+
},
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
pingInterval,
|
|
23
|
+
}
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
const remote = await createTestClient<typeof services>({
|
|
27
|
+
reconnectDelay: pingInterval * 4, // so we don't reconnect fast and can catch disconnected state
|
|
28
|
+
})
|
|
29
|
+
|
|
30
|
+
await remote.test.call.subscribe(() => {})
|
|
31
|
+
assert.equal(testServer?._allSubscriptions().length, 1)
|
|
32
|
+
|
|
33
|
+
// wait for timeout
|
|
34
|
+
await adelay(pingInterval * 2.5)
|
|
35
|
+
|
|
36
|
+
// should be closed
|
|
37
|
+
assert.equal(testServer?._allSubscriptions().length, 0)
|
|
38
|
+
|
|
39
|
+
WebSocket.prototype.ping = oldPing
|
|
40
|
+
}).timeout(5000)
|
|
41
|
+
|
|
42
|
+
it("client close connection on ping timeout", async () => {
|
|
43
|
+
const pingInterval = 100 // less than server pings, so client will close connection first
|
|
44
|
+
|
|
45
|
+
const services = await startTestServer({
|
|
46
|
+
test: {
|
|
47
|
+
async call() {},
|
|
48
|
+
},
|
|
49
|
+
})
|
|
50
|
+
|
|
51
|
+
const remote = await createTestClient<typeof services>({
|
|
52
|
+
pingInterval,
|
|
53
|
+
reconnectDelay: pingInterval * 4, // so we don't reconnect fast and can catch disconnected state
|
|
54
|
+
})
|
|
55
|
+
|
|
56
|
+
await remote.test.call.subscribe(() => {})
|
|
57
|
+
|
|
58
|
+
assert.equal(testClient?.isConnected(), true)
|
|
59
|
+
|
|
60
|
+
// wait for timeout
|
|
61
|
+
await new Promise((r) => setTimeout(r, pingInterval * 2))
|
|
62
|
+
|
|
63
|
+
// should be closed
|
|
64
|
+
assert.equal(testClient?.isConnected(), false)
|
|
65
|
+
})
|
|
66
|
+
|
|
67
|
+
it("client reconnects on disconnect", async () => {
|
|
68
|
+
const pingInterval = 100 // less than server pings, so client will close connection first
|
|
69
|
+
|
|
70
|
+
const services = await startTestServer({
|
|
71
|
+
test: {
|
|
72
|
+
async call() {},
|
|
73
|
+
},
|
|
74
|
+
})
|
|
75
|
+
|
|
76
|
+
const remote = await createTestClient<typeof services>({
|
|
77
|
+
pingInterval,
|
|
78
|
+
reconnectDelay: 0, // will reconnect after failed ping
|
|
79
|
+
})
|
|
80
|
+
|
|
81
|
+
await remote.test.call.subscribe(() => {})
|
|
82
|
+
|
|
83
|
+
assert.equal(testClient!.isConnected(), true)
|
|
84
|
+
|
|
85
|
+
// wait for timeout
|
|
86
|
+
await new Promise((r) => setTimeout(r, pingInterval * 2))
|
|
87
|
+
|
|
88
|
+
// should be reconnected again
|
|
89
|
+
assert.equal(testClient!.isConnected(), true)
|
|
90
|
+
})
|
|
91
|
+
|
|
92
|
+
it("close will stop reconnection loop", async () => {
|
|
93
|
+
const services = await startTestServer({
|
|
94
|
+
item: async () => "1",
|
|
95
|
+
})
|
|
96
|
+
|
|
97
|
+
const remote = await createTestClient<typeof services>()
|
|
98
|
+
|
|
99
|
+
await remote.item.subscribe(() => {})
|
|
100
|
+
|
|
101
|
+
assert.equal(testClient!.isConnected(), true)
|
|
102
|
+
|
|
103
|
+
await testClient!.close()
|
|
104
|
+
|
|
105
|
+
assert.equal(testClient!.isConnected(), false)
|
|
106
|
+
})
|
|
107
|
+
})
|