@planet-matrix/mobius-model 0.6.0 → 0.10.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/CHANGELOG.md +50 -0
- package/oxlint.config.ts +1 -2
- package/package.json +29 -17
- package/scripts/build.ts +2 -52
- package/src/ai/README.md +1 -0
- package/src/ai/ai.ts +107 -0
- package/src/ai/chat-completion-ai/aihubmix-chat-completion.ts +78 -0
- package/src/ai/chat-completion-ai/chat-completion-ai.ts +270 -0
- package/src/ai/chat-completion-ai/chat-completion.ts +189 -0
- package/src/ai/chat-completion-ai/index.ts +7 -0
- package/src/ai/chat-completion-ai/lingyiwanwu-chat-completion.ts +78 -0
- package/src/ai/chat-completion-ai/ohmygpt-chat-completion.ts +78 -0
- package/src/ai/chat-completion-ai/openai-next-chat-completion.ts +78 -0
- package/src/ai/embedding-ai/embedding-ai.ts +63 -0
- package/src/ai/embedding-ai/embedding.ts +50 -0
- package/src/ai/embedding-ai/index.ts +4 -0
- package/src/ai/embedding-ai/openai-next-embedding.ts +23 -0
- package/src/ai/index.ts +4 -0
- package/src/aio/README.md +100 -0
- package/src/aio/content.ts +141 -0
- package/src/aio/index.ts +3 -0
- package/src/aio/json.ts +127 -0
- package/src/aio/prompt.ts +246 -0
- package/src/basic/README.md +20 -15
- package/src/basic/error.ts +19 -5
- package/src/basic/function.ts +2 -2
- package/src/basic/index.ts +1 -0
- package/src/basic/promise.ts +141 -71
- package/src/basic/schedule.ts +111 -0
- package/src/basic/stream.ts +135 -25
- package/src/credential/README.md +107 -0
- package/src/credential/api-key.ts +158 -0
- package/src/credential/bearer.ts +73 -0
- package/src/credential/index.ts +4 -0
- package/src/credential/json-web-token.ts +96 -0
- package/src/credential/password.ts +170 -0
- package/src/cron/README.md +86 -0
- package/src/cron/cron.ts +87 -0
- package/src/cron/index.ts +1 -0
- package/src/drizzle/README.md +1 -0
- package/src/drizzle/drizzle.ts +1 -0
- package/src/drizzle/helper.ts +47 -0
- package/src/drizzle/index.ts +5 -0
- package/src/drizzle/infer.ts +52 -0
- package/src/drizzle/kysely.ts +8 -0
- package/src/drizzle/pagination.ts +198 -0
- package/src/email/README.md +1 -0
- package/src/email/index.ts +1 -0
- package/src/email/resend.ts +25 -0
- package/src/event/class-event-proxy.ts +5 -6
- package/src/event/common.ts +13 -3
- package/src/event/event-manager.ts +3 -3
- package/src/event/instance-event-proxy.ts +5 -6
- package/src/event/internal.ts +4 -4
- package/src/exception/README.md +28 -19
- package/src/exception/error/error.ts +123 -0
- package/src/exception/error/index.ts +2 -0
- package/src/exception/error/match.ts +38 -0
- package/src/exception/error/must-fix.ts +17 -0
- package/src/exception/index.ts +2 -0
- package/src/file-system/find.ts +53 -0
- package/src/file-system/index.ts +2 -0
- package/src/file-system/path.ts +76 -0
- package/src/file-system/resolve.ts +22 -0
- package/src/form/README.md +25 -0
- package/src/form/index.ts +1 -0
- package/src/form/inputor-controller/base.ts +861 -0
- package/src/form/inputor-controller/boolean.ts +39 -0
- package/src/form/inputor-controller/file.ts +39 -0
- package/src/form/inputor-controller/form.ts +179 -0
- package/src/form/inputor-controller/helper.ts +117 -0
- package/src/form/inputor-controller/index.ts +17 -0
- package/src/form/inputor-controller/multi-select.ts +99 -0
- package/src/form/inputor-controller/number.ts +116 -0
- package/src/form/inputor-controller/select.ts +109 -0
- package/src/form/inputor-controller/text.ts +82 -0
- package/src/http/READMD.md +1 -0
- package/src/http/api/api-core.ts +84 -0
- package/src/http/api/api-handler.ts +79 -0
- package/src/http/api/api-host.ts +47 -0
- package/src/http/api/api-result.ts +56 -0
- package/src/http/api/api-schema.ts +154 -0
- package/src/http/api/api-server.ts +130 -0
- package/src/http/api/api-test.ts +142 -0
- package/src/http/api/api-type.ts +34 -0
- package/src/http/api/api.ts +81 -0
- package/src/http/api/index.ts +11 -0
- package/src/http/api-adapter/api-core-node-http.ts +260 -0
- package/src/http/api-adapter/api-host-node-http.ts +156 -0
- package/src/http/api-adapter/api-result-arktype.ts +294 -0
- package/src/http/api-adapter/api-result-zod.ts +286 -0
- package/src/http/api-adapter/index.ts +5 -0
- package/src/http/bin/gen-api-list/gen-api-list.ts +126 -0
- package/src/http/bin/gen-api-list/index.ts +1 -0
- package/src/http/bin/gen-api-test/gen-api-test.ts +136 -0
- package/src/http/bin/gen-api-test/index.ts +1 -0
- package/src/http/bin/gen-api-type/calc-code.ts +25 -0
- package/src/http/bin/gen-api-type/gen-api-type.ts +127 -0
- package/src/http/bin/gen-api-type/index.ts +2 -0
- package/src/http/bin/index.ts +2 -0
- package/src/http/index.ts +3 -0
- package/src/huawei/README.md +1 -0
- package/src/huawei/index.ts +2 -0
- package/src/huawei/moderation/index.ts +1 -0
- package/src/huawei/moderation/moderation.ts +355 -0
- package/src/huawei/obs/esdk-obs-nodejs.d.ts +87 -0
- package/src/huawei/obs/index.ts +1 -0
- package/src/huawei/obs/obs.ts +42 -0
- package/src/index.ts +21 -2
- package/src/json/README.md +92 -0
- package/src/json/index.ts +1 -0
- package/src/json/repair.ts +18 -0
- package/src/log/logger.ts +15 -4
- package/src/openai/README.md +1 -0
- package/src/openai/index.ts +1 -0
- package/src/openai/openai.ts +509 -0
- package/src/orchestration/README.md +9 -7
- package/src/orchestration/dispatching/dispatcher.ts +83 -0
- package/src/orchestration/dispatching/index.ts +2 -0
- package/src/orchestration/dispatching/selector/base-selector.ts +39 -0
- package/src/orchestration/dispatching/selector/down-count-selector.ts +119 -0
- package/src/orchestration/dispatching/selector/index.ts +2 -0
- package/src/orchestration/index.ts +2 -0
- package/src/orchestration/scheduling/index.ts +2 -0
- package/src/orchestration/scheduling/scheduler.ts +103 -0
- package/src/orchestration/scheduling/task.ts +32 -0
- package/src/random/README.md +8 -7
- package/src/random/base.ts +66 -0
- package/src/random/index.ts +5 -1
- package/src/random/random-boolean.ts +40 -0
- package/src/random/random-integer.ts +60 -0
- package/src/random/random-number.ts +72 -0
- package/src/random/random-string.ts +66 -0
- package/src/request/README.md +108 -0
- package/src/request/fetch/base.ts +108 -0
- package/src/request/fetch/browser.ts +280 -0
- package/src/request/fetch/general.ts +20 -0
- package/src/request/fetch/index.ts +4 -0
- package/src/request/fetch/nodejs.ts +280 -0
- package/src/request/index.ts +2 -0
- package/src/request/request/base.ts +246 -0
- package/src/request/request/general.ts +63 -0
- package/src/request/request/index.ts +3 -0
- package/src/request/request/resource.ts +68 -0
- package/src/result/README.md +4 -0
- package/src/result/controller.ts +58 -0
- package/src/result/either.ts +363 -0
- package/src/result/generator.ts +168 -0
- package/src/result/index.ts +3 -0
- package/src/route/README.md +105 -0
- package/src/route/adapter/browser.ts +122 -0
- package/src/route/adapter/driver.ts +56 -0
- package/src/route/adapter/index.ts +2 -0
- package/src/route/index.ts +3 -0
- package/src/route/router/index.ts +2 -0
- package/src/route/router/route.ts +630 -0
- package/src/route/router/router.ts +1641 -0
- package/src/route/uri/hash.ts +307 -0
- package/src/route/uri/index.ts +7 -0
- package/src/route/uri/pathname.ts +376 -0
- package/src/route/uri/search.ts +412 -0
- package/src/service/README.md +1 -0
- package/src/service/index.ts +1 -0
- package/src/service/service.ts +110 -0
- package/src/socket/README.md +105 -0
- package/src/socket/client/index.ts +2 -0
- package/src/socket/client/socket-unit.ts +658 -0
- package/src/socket/client/socket.ts +203 -0
- package/src/socket/common/index.ts +2 -0
- package/src/socket/common/socket-unit-common.ts +23 -0
- package/src/socket/common/socket-unit-heartbeat.ts +427 -0
- package/src/socket/index.ts +3 -0
- package/src/socket/server/index.ts +3 -0
- package/src/socket/server/server.ts +183 -0
- package/src/socket/server/socket-unit.ts +448 -0
- package/src/socket/server/socket.ts +264 -0
- package/src/storage/table.ts +3 -3
- package/src/timer/expiration/expiration-manager.ts +3 -3
- package/src/timer/expiration/remaining-manager.ts +3 -3
- package/src/tube/README.md +99 -0
- package/src/tube/helper.ts +137 -0
- package/src/tube/index.ts +2 -0
- package/src/tube/tube.ts +880 -0
- package/src/weixin/README.md +1 -0
- package/src/weixin/index.ts +2 -0
- package/src/weixin/official-account/authorization.ts +157 -0
- package/src/weixin/official-account/index.ts +2 -0
- package/src/weixin/official-account/js-api.ts +132 -0
- package/src/weixin/open/index.ts +1 -0
- package/src/weixin/open/oauth2.ts +131 -0
- package/tests/unit/ai/ai.spec.ts +85 -0
- package/tests/unit/aio/content.spec.ts +105 -0
- package/tests/unit/aio/json.spec.ts +146 -0
- package/tests/unit/aio/prompt.spec.ts +111 -0
- package/tests/unit/basic/error.spec.ts +16 -4
- package/tests/unit/basic/promise.spec.ts +158 -50
- package/tests/unit/basic/schedule.spec.ts +74 -0
- package/tests/unit/basic/stream.spec.ts +90 -37
- package/tests/unit/credential/api-key.spec.ts +36 -0
- package/tests/unit/credential/bearer.spec.ts +23 -0
- package/tests/unit/credential/json-web-token.spec.ts +23 -0
- package/tests/unit/credential/password.spec.ts +40 -0
- package/tests/unit/cron/cron.spec.ts +84 -0
- package/tests/unit/event/class-event-proxy.spec.ts +3 -3
- package/tests/unit/event/event-manager.spec.ts +3 -3
- package/tests/unit/event/instance-event-proxy.spec.ts +3 -3
- package/tests/unit/exception/error/error.spec.ts +83 -0
- package/tests/unit/exception/error/match.spec.ts +81 -0
- package/tests/unit/form/inputor-controller/base.spec.ts +458 -0
- package/tests/unit/form/inputor-controller/boolean.spec.ts +30 -0
- package/tests/unit/form/inputor-controller/file.spec.ts +27 -0
- package/tests/unit/form/inputor-controller/form.spec.ts +120 -0
- package/tests/unit/form/inputor-controller/helper.spec.ts +67 -0
- package/tests/unit/form/inputor-controller/multi-select.spec.ts +34 -0
- package/tests/unit/form/inputor-controller/number.spec.ts +36 -0
- package/tests/unit/form/inputor-controller/select.spec.ts +49 -0
- package/tests/unit/form/inputor-controller/text.spec.ts +34 -0
- package/tests/unit/http/api/api-core-host.spec.ts +207 -0
- package/tests/unit/http/api/api-schema.spec.ts +120 -0
- package/tests/unit/http/api/api-server.spec.ts +363 -0
- package/tests/unit/http/api/api-test.spec.ts +117 -0
- package/tests/unit/http/api/api.spec.ts +121 -0
- package/tests/unit/http/api-adapter/node-http.spec.ts +187 -0
- package/tests/unit/identifier/uuid.spec.ts +0 -1
- package/tests/unit/json/repair.spec.ts +11 -0
- package/tests/unit/log/logger.spec.ts +19 -4
- package/tests/unit/openai/openai.spec.ts +64 -0
- package/tests/unit/orchestration/dispatching/dispatcher.spec.ts +41 -0
- package/tests/unit/orchestration/dispatching/selector/down-count-selector.spec.ts +81 -0
- package/tests/unit/orchestration/scheduling/scheduler.spec.ts +103 -0
- package/tests/unit/random/base.spec.ts +58 -0
- package/tests/unit/random/random-boolean.spec.ts +25 -0
- package/tests/unit/random/random-integer.spec.ts +32 -0
- package/tests/unit/random/random-number.spec.ts +33 -0
- package/tests/unit/random/random-string.spec.ts +22 -0
- package/tests/unit/request/fetch/browser.spec.ts +222 -0
- package/tests/unit/request/fetch/general.spec.ts +43 -0
- package/tests/unit/request/fetch/nodejs.spec.ts +225 -0
- package/tests/unit/request/request/base.spec.ts +382 -0
- package/tests/unit/request/request/general.spec.ts +160 -0
- package/tests/unit/result/controller.spec.ts +82 -0
- package/tests/unit/result/either.spec.ts +377 -0
- package/tests/unit/result/generator.spec.ts +273 -0
- package/tests/unit/route/router/route.spec.ts +430 -0
- package/tests/unit/route/router/router.spec.ts +407 -0
- package/tests/unit/route/uri/hash.spec.ts +72 -0
- package/tests/unit/route/uri/pathname.spec.ts +146 -0
- package/tests/unit/route/uri/search.spec.ts +107 -0
- package/tests/unit/socket/client.spec.ts +208 -0
- package/tests/unit/socket/server.spec.ts +133 -0
- package/tests/unit/socket/socket-unit-heartbeat.spec.ts +214 -0
- package/tests/unit/tube/helper.spec.ts +139 -0
- package/tests/unit/tube/tube.spec.ts +501 -0
- package/vite.config.ts +2 -1
- package/dist/index.js +0 -50
- package/dist/index.js.map +0 -209
- package/src/random/string.ts +0 -35
- package/tests/unit/random/string.spec.ts +0 -11
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
import type { LoggerFriendly } from "#Project/src/log/logger.ts"
|
|
2
|
+
import { Logger } from "#Project/src/log/logger.ts"
|
|
3
|
+
import type { BuildEvents, SubscriberEntry } from "#Source/event/index.ts"
|
|
4
|
+
import { EventManager } from "#Source/event/index.ts"
|
|
5
|
+
|
|
6
|
+
import type { SocketUnitEvents, SocketUnitOptions, SocketUnitSnapshot, SocketUnitStatus } from "./socket-unit.ts"
|
|
7
|
+
import { SocketUnit } from "./socket-unit.ts"
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* 表示客户端 Socket 进入 ready 态时的事件负载。
|
|
11
|
+
*/
|
|
12
|
+
export interface SocketConnectedEvent {
|
|
13
|
+
clientId: string | undefined
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* 表示客户端 Socket 离开 ready 态时的事件负载。
|
|
18
|
+
*/
|
|
19
|
+
export interface SocketClosedEvent {
|
|
20
|
+
clientId: string | undefined
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* 表示客户端 Socket 派发业务消息时的事件负载。
|
|
25
|
+
*/
|
|
26
|
+
export interface SocketMessageEvent<Message> {
|
|
27
|
+
clientId: string
|
|
28
|
+
message: Message
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* 表示客户端 Socket 对外派发的事件表。
|
|
33
|
+
*/
|
|
34
|
+
export type SocketEvents<Message> = BuildEvents<{
|
|
35
|
+
connect: (payload: SocketConnectedEvent) => void
|
|
36
|
+
close: (payload: SocketClosedEvent) => void
|
|
37
|
+
message: (payload: SocketMessageEvent<Message>) => void
|
|
38
|
+
}>
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* 表示客户端 Socket 的公开状态类型。
|
|
42
|
+
*/
|
|
43
|
+
export type SocketStatus = SocketUnitStatus
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* 表示客户端 Socket 的构造参数。
|
|
47
|
+
*/
|
|
48
|
+
export interface SocketOptions<Message> extends SocketUnitOptions<Message> {
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* 此 Socket 类并不直接创建和管理 WebSocket 实例,而是管理很多 SocketUnit,
|
|
52
|
+
* SocketUnit 是 WebSocket 实例的实际管理者。这样可以做到当 WebSocket 实例
|
|
53
|
+
* 出现连接不稳定或断线重连等情况时,Socket 的功能对使用者来说始终是稳定的。
|
|
54
|
+
*/
|
|
55
|
+
export class Socket<Message> implements LoggerFriendly {
|
|
56
|
+
protected readonly options: SocketUnitOptions<Message>
|
|
57
|
+
|
|
58
|
+
readonly logger: Logger
|
|
59
|
+
|
|
60
|
+
protected clientId: string | undefined
|
|
61
|
+
protected legacyUnitSet: Set<SocketUnit<Message>>
|
|
62
|
+
protected unit: SocketUnit<Message>
|
|
63
|
+
|
|
64
|
+
eventManager: EventManager<SocketEvents<Message>>
|
|
65
|
+
protected messageSubscriberEntry!: SubscriberEntry<SocketUnitEvents<Message>, 'message'>
|
|
66
|
+
protected statusSubscriberEntry!: SubscriberEntry<SocketUnitEvents<Message>, 'status'>
|
|
67
|
+
protected lastUnitReadyToWork: boolean
|
|
68
|
+
|
|
69
|
+
constructor(options: SocketUnitOptions<Message>) {
|
|
70
|
+
this.options = options
|
|
71
|
+
|
|
72
|
+
this.logger = Logger.fromOptions(options).setDefaultName("Socket")
|
|
73
|
+
this.clientId = undefined
|
|
74
|
+
this.legacyUnitSet = new Set()
|
|
75
|
+
this.unit = this.createUnit()
|
|
76
|
+
this.lastUnitReadyToWork = this.unit.getSnapshot().isReadyToWork
|
|
77
|
+
|
|
78
|
+
this.eventManager = new EventManager<SocketEvents<Message>>()
|
|
79
|
+
this.prepareEventManager()
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
protected createUnit(): SocketUnit<Message> {
|
|
83
|
+
return new SocketUnit<Message>({
|
|
84
|
+
...this.options,
|
|
85
|
+
logger: Logger.derive(this.logger).setName("SocketUnit")
|
|
86
|
+
})
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
protected replaceCurrentUnit(unit: SocketUnit<Message>): void {
|
|
90
|
+
this.unit = unit
|
|
91
|
+
this.lastUnitReadyToWork = unit.getSnapshot().isReadyToWork
|
|
92
|
+
this.prepareEventManager()
|
|
93
|
+
|
|
94
|
+
if (this.clientId !== undefined) {
|
|
95
|
+
this.unit.setClientId(this.clientId)
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
protected prepareEventManager(): void {
|
|
100
|
+
if (this.messageSubscriberEntry !== undefined) {
|
|
101
|
+
this.messageSubscriberEntry.unsubscribe()
|
|
102
|
+
}
|
|
103
|
+
if (this.statusSubscriberEntry !== undefined) {
|
|
104
|
+
this.statusSubscriberEntry.unsubscribe()
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
this.messageSubscriberEntry = this.unit.eventManager.subscribe('message', (message) => {
|
|
108
|
+
const clientId = this.unit.getSnapshot().clientId
|
|
109
|
+
if (clientId === undefined) {
|
|
110
|
+
this.logger.warn('Message event skipped because clientId is not ready.')
|
|
111
|
+
return
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
this.eventManager.emit('message', { clientId, message })
|
|
115
|
+
})
|
|
116
|
+
|
|
117
|
+
this.statusSubscriberEntry = this.unit.eventManager.subscribe('status', (status) => {
|
|
118
|
+
this.handleUnitStatusChange(status)
|
|
119
|
+
})
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
protected handleUnitStatusChange(_status: SocketUnitStatus): void {
|
|
123
|
+
const previousReadyToWork = this.lastUnitReadyToWork
|
|
124
|
+
const snapshot = this.unit.getSnapshot()
|
|
125
|
+
const nextReadyToWork = snapshot.isReadyToWork
|
|
126
|
+
|
|
127
|
+
this.lastUnitReadyToWork = nextReadyToWork
|
|
128
|
+
|
|
129
|
+
if (previousReadyToWork === false && nextReadyToWork === true) {
|
|
130
|
+
this.eventManager.emit('connect', { clientId: snapshot.clientId })
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
if (
|
|
134
|
+
previousReadyToWork === true
|
|
135
|
+
&& nextReadyToWork === false
|
|
136
|
+
) {
|
|
137
|
+
this.eventManager.emit('close', { clientId: snapshot.clientId })
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* 设置当前 Socket 对外暴露的 clientId。
|
|
143
|
+
*/
|
|
144
|
+
setClientId(clientId: string): void {
|
|
145
|
+
this.clientId = clientId
|
|
146
|
+
this.unit.setClientId(clientId)
|
|
147
|
+
this.handleUnitStatusChange(this.unit.getStatus())
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* 返回当前活跃 SocketUnit 的状态。
|
|
152
|
+
*/
|
|
153
|
+
getStatus(): SocketStatus {
|
|
154
|
+
return this.unit.getStatus()
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* 返回当前活跃 SocketUnit 的快照。
|
|
159
|
+
*/
|
|
160
|
+
getSnapshot(): SocketUnitSnapshot {
|
|
161
|
+
return this.unit.getSnapshot()
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* 打开当前活跃的 SocketUnit。
|
|
166
|
+
*/
|
|
167
|
+
async open(): Promise<void> {
|
|
168
|
+
await this.unit.open()
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* 重建当前活跃的 SocketUnit 连接。
|
|
173
|
+
*/
|
|
174
|
+
async reopen(): Promise<void> {
|
|
175
|
+
await this.unit.reopen()
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* 关闭当前活跃的 SocketUnit 连接。
|
|
180
|
+
*/
|
|
181
|
+
async close(): Promise<void> {
|
|
182
|
+
await this.unit.close()
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* 异步回收当前 SocketUnit,并切换到新的运行单元。
|
|
187
|
+
*/
|
|
188
|
+
reset(): void {
|
|
189
|
+
const legacyUnit = this.unit
|
|
190
|
+
this.legacyUnitSet.add(legacyUnit)
|
|
191
|
+
void legacyUnit.reset(() => {
|
|
192
|
+
this.legacyUnitSet.delete(legacyUnit)
|
|
193
|
+
})
|
|
194
|
+
this.replaceCurrentUnit(this.createUnit())
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
/**
|
|
198
|
+
* 向当前活跃的 SocketUnit 发送业务消息。
|
|
199
|
+
*/
|
|
200
|
+
sendMessage(message: Message): void {
|
|
201
|
+
this.unit.sendMessage(message)
|
|
202
|
+
}
|
|
203
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 表示 SocketUnit 当前所处的生命周期阶段。
|
|
3
|
+
*/
|
|
4
|
+
export type SocketUnitLifecycleState = "IDLE" | "RUNNING" | "CLOSED"
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* 表示当前传输对象由谁负责创建和销毁。
|
|
8
|
+
*/
|
|
9
|
+
export type SocketUnitTransportOwnership = "OWNED" | "INJECTED"
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* 表示客户端与服务端 SocketUnit 共享的基础快照结构。
|
|
13
|
+
*/
|
|
14
|
+
export interface SocketUnitBaseSnapshot<ClientId, Status, HeartbeatSnapshot> {
|
|
15
|
+
status: Status
|
|
16
|
+
clientId: ClientId
|
|
17
|
+
lifecycleState: SocketUnitLifecycleState
|
|
18
|
+
isReadyToWork: boolean
|
|
19
|
+
hasTransport: boolean
|
|
20
|
+
pendingActionCount: number
|
|
21
|
+
transportOwnership: SocketUnitTransportOwnership
|
|
22
|
+
heartbeat: HeartbeatSnapshot
|
|
23
|
+
}
|
|
@@ -0,0 +1,427 @@
|
|
|
1
|
+
import type { LoggerFriendly, LoggerFriendlyOptions } from "#Source/log/index.ts"
|
|
2
|
+
import { Logger } from "#Source/log/index.ts"
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* 表示主动心跳轮次使用的消息处理器。
|
|
6
|
+
*/
|
|
7
|
+
export interface ActiveHeartbeatMessageHandler<Message> {
|
|
8
|
+
buildPingMessage: (clientId: string) => Message
|
|
9
|
+
verifyPongMessage: (clientId: string, message: Message) => boolean
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* 表示被动响应心跳时使用的消息处理器。
|
|
14
|
+
*/
|
|
15
|
+
export interface PassiveHeartbeatMessageHandler<Message> {
|
|
16
|
+
verifyPingMessage: (clientId: string, message: Message) => boolean
|
|
17
|
+
buildPongMessage: (clientId: string, message: Message) => Message
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* 表示主动心跳消息处理器的构造器。
|
|
22
|
+
*/
|
|
23
|
+
export type ActiveHeartbeatMessageHandlerGenerator<Message> = () => ActiveHeartbeatMessageHandler<Message>
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* 表示被动心跳消息处理器的构造器。
|
|
27
|
+
*/
|
|
28
|
+
export type PassiveHeartbeatMessageHandlerGenerator<Message> = () => PassiveHeartbeatMessageHandler<Message>
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* 表示 SocketUnitHeartbeat 的配置项。
|
|
32
|
+
*/
|
|
33
|
+
export interface SocketUnitHeartbeatOptions<Message> extends LoggerFriendlyOptions {
|
|
34
|
+
enableActiveHeartbeat?: boolean | undefined
|
|
35
|
+
enablePassiveHeartbeat?: boolean | undefined
|
|
36
|
+
heartbeatInterval?: number | undefined
|
|
37
|
+
maxResponseTime?: number | undefined
|
|
38
|
+
maxHeartbeatLostCount?: number | undefined
|
|
39
|
+
activeHeartbeatMessageHandlerGenerator?: ActiveHeartbeatMessageHandlerGenerator<Message> | undefined
|
|
40
|
+
passiveHeartbeatMessageHandlerGenerator?: PassiveHeartbeatMessageHandlerGenerator<Message> | undefined
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* 验证心跳配置是否满足统一运行时的基本约束。
|
|
45
|
+
*/
|
|
46
|
+
export const validateSocketUnitHeartbeatOptions = <Message>(options: SocketUnitHeartbeatOptions<Message>): void => {
|
|
47
|
+
if (
|
|
48
|
+
options.enableActiveHeartbeat === true
|
|
49
|
+
&& options.activeHeartbeatMessageHandlerGenerator === undefined
|
|
50
|
+
) {
|
|
51
|
+
throw new Error("Active heartbeat message handler is required when active heartbeat is enabled.")
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
if (
|
|
55
|
+
options.enablePassiveHeartbeat === true
|
|
56
|
+
&& options.passiveHeartbeatMessageHandlerGenerator === undefined
|
|
57
|
+
) {
|
|
58
|
+
throw new Error("Passive heartbeat message handler is required when passive heartbeat is enabled.")
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
if (
|
|
62
|
+
options.heartbeatInterval !== undefined
|
|
63
|
+
&& (!Number.isFinite(options.heartbeatInterval) || options.heartbeatInterval <= 0)
|
|
64
|
+
) {
|
|
65
|
+
throw new Error("Heartbeat interval must be a positive finite number.")
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
if (
|
|
69
|
+
options.maxResponseTime !== undefined
|
|
70
|
+
&& (!Number.isFinite(options.maxResponseTime) || options.maxResponseTime <= 0)
|
|
71
|
+
) {
|
|
72
|
+
throw new Error("Max response time must be a positive finite number.")
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
if (
|
|
76
|
+
options.maxHeartbeatLostCount !== undefined
|
|
77
|
+
&& (!Number.isFinite(options.maxHeartbeatLostCount) || options.maxHeartbeatLostCount < 1)
|
|
78
|
+
) {
|
|
79
|
+
throw new Error("Max heartbeat lost count must be at least 1.")
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* 表示补齐默认值后的心跳配置。
|
|
85
|
+
*/
|
|
86
|
+
export interface ResolvedSocketUnitHeartbeatOptions<Message> extends LoggerFriendlyOptions {
|
|
87
|
+
enableActiveHeartbeat: boolean
|
|
88
|
+
enablePassiveHeartbeat: boolean
|
|
89
|
+
heartbeatInterval: number
|
|
90
|
+
maxResponseTime: number
|
|
91
|
+
maxHeartbeatLostCount: number
|
|
92
|
+
activeHeartbeatMessageHandlerGenerator?: ActiveHeartbeatMessageHandlerGenerator<Message> | undefined
|
|
93
|
+
passiveHeartbeatMessageHandlerGenerator?: PassiveHeartbeatMessageHandlerGenerator<Message> | undefined
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* 为心跳配置补齐统一运行时所需的默认值。
|
|
98
|
+
*/
|
|
99
|
+
export const resolveSocketUnitHeartbeatOptions = <Message>(options: SocketUnitHeartbeatOptions<Message>): ResolvedSocketUnitHeartbeatOptions<Message> => {
|
|
100
|
+
return {
|
|
101
|
+
...options,
|
|
102
|
+
enableActiveHeartbeat: options.enableActiveHeartbeat ?? false,
|
|
103
|
+
enablePassiveHeartbeat: options.enablePassiveHeartbeat ?? false,
|
|
104
|
+
heartbeatInterval: options.heartbeatInterval ?? 30 * 1_000,
|
|
105
|
+
maxResponseTime: options.maxResponseTime ?? 10 * 1_000,
|
|
106
|
+
maxHeartbeatLostCount: options.maxHeartbeatLostCount ?? 3,
|
|
107
|
+
activeHeartbeatMessageHandlerGenerator: options.activeHeartbeatMessageHandlerGenerator,
|
|
108
|
+
passiveHeartbeatMessageHandlerGenerator: options.passiveHeartbeatMessageHandlerGenerator,
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* 表示心跳运行时与外部 SocketUnit 之间的回调边界。
|
|
114
|
+
*/
|
|
115
|
+
export interface SocketUnitHeartbeatCallbacks {
|
|
116
|
+
sendMessage: (message: { action: string, messageString: string }) => boolean
|
|
117
|
+
close: () => void
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* 表示单个心跳轮次的状态。
|
|
122
|
+
*/
|
|
123
|
+
export type SocketUnitHeartbeatStateStatus = "waiting" | "success" | "fail"
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* 表示统一心跳运行时内部保留的一条轮次记录。
|
|
127
|
+
*/
|
|
128
|
+
export interface SocketUnitHeartbeatState<Message> {
|
|
129
|
+
activeHeartbeatMessageHandler: ActiveHeartbeatMessageHandler<Message>
|
|
130
|
+
responseTimer: ReturnType<typeof setTimeout> | undefined
|
|
131
|
+
status: SocketUnitHeartbeatStateStatus
|
|
132
|
+
pingMessageString: string | undefined
|
|
133
|
+
pongMessageString: string | undefined
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* 表示单个心跳轮次对外暴露的只读快照。
|
|
138
|
+
*/
|
|
139
|
+
export interface SocketUnitHeartbeatStateSnapshot {
|
|
140
|
+
status: SocketUnitHeartbeatStateStatus
|
|
141
|
+
hasPendingResponseTimer: boolean
|
|
142
|
+
pingMessageString: string | undefined
|
|
143
|
+
pongMessageString: string | undefined
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* 表示统一心跳运行时的聚合快照。
|
|
148
|
+
*/
|
|
149
|
+
export interface SocketUnitHeartbeatSnapshot {
|
|
150
|
+
enabledActiveHeartbeat: boolean
|
|
151
|
+
enabledPassiveHeartbeat: boolean
|
|
152
|
+
isRunning: boolean
|
|
153
|
+
waitingCount: number
|
|
154
|
+
successCount: number
|
|
155
|
+
failCount: number
|
|
156
|
+
recentStates: SocketUnitHeartbeatStateSnapshot[]
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* 管理 SocketUnit 的统一心跳运行时。
|
|
161
|
+
*/
|
|
162
|
+
export class SocketUnitHeartbeat<Message> implements LoggerFriendly {
|
|
163
|
+
protected readonly options: ResolvedSocketUnitHeartbeatOptions<Message>
|
|
164
|
+
protected readonly callbacks: SocketUnitHeartbeatCallbacks
|
|
165
|
+
|
|
166
|
+
readonly logger: Logger
|
|
167
|
+
|
|
168
|
+
protected heartbeatTimer: ReturnType<typeof setInterval> | undefined
|
|
169
|
+
protected heartbeatStates: Array<SocketUnitHeartbeatState<Message>>
|
|
170
|
+
|
|
171
|
+
constructor(
|
|
172
|
+
options: SocketUnitHeartbeatOptions<Message>,
|
|
173
|
+
callbacks: SocketUnitHeartbeatCallbacks,
|
|
174
|
+
) {
|
|
175
|
+
validateSocketUnitHeartbeatOptions(options)
|
|
176
|
+
|
|
177
|
+
this.options = resolveSocketUnitHeartbeatOptions(options)
|
|
178
|
+
this.callbacks = callbacks
|
|
179
|
+
|
|
180
|
+
this.logger = Logger.fromOptions(options).setDefaultName("SocketUnitHeartbeat")
|
|
181
|
+
|
|
182
|
+
this.heartbeatTimer = undefined
|
|
183
|
+
this.heartbeatStates = []
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
|
|
187
|
+
/**
|
|
188
|
+
* 返回统一心跳运行时的诊断快照。
|
|
189
|
+
*/
|
|
190
|
+
getSnapshot(): SocketUnitHeartbeatSnapshot {
|
|
191
|
+
let waitingCount = 0
|
|
192
|
+
let successCount = 0
|
|
193
|
+
let failCount = 0
|
|
194
|
+
|
|
195
|
+
const recentStates = this.heartbeatStates.map((heartbeatState) => {
|
|
196
|
+
switch (heartbeatState.status) {
|
|
197
|
+
case "waiting": {
|
|
198
|
+
waitingCount = waitingCount + 1
|
|
199
|
+
break
|
|
200
|
+
}
|
|
201
|
+
case "success": {
|
|
202
|
+
successCount = successCount + 1
|
|
203
|
+
break
|
|
204
|
+
}
|
|
205
|
+
case "fail": {
|
|
206
|
+
failCount = failCount + 1
|
|
207
|
+
break
|
|
208
|
+
}
|
|
209
|
+
default: {
|
|
210
|
+
throw new Error("Unexpected heartbeat state status.")
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
return {
|
|
215
|
+
status: heartbeatState.status,
|
|
216
|
+
hasPendingResponseTimer: heartbeatState.responseTimer !== undefined,
|
|
217
|
+
pingMessageString: heartbeatState.pingMessageString,
|
|
218
|
+
pongMessageString: heartbeatState.pongMessageString,
|
|
219
|
+
}
|
|
220
|
+
})
|
|
221
|
+
|
|
222
|
+
return {
|
|
223
|
+
enabledActiveHeartbeat: this.options.enableActiveHeartbeat,
|
|
224
|
+
enabledPassiveHeartbeat: this.options.enablePassiveHeartbeat,
|
|
225
|
+
isRunning: this.heartbeatTimer !== undefined,
|
|
226
|
+
waitingCount,
|
|
227
|
+
successCount,
|
|
228
|
+
failCount,
|
|
229
|
+
recentStates,
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
/**
|
|
234
|
+
* 在启用主动心跳时启动轮询调度。
|
|
235
|
+
*/
|
|
236
|
+
start(clientId: string): void {
|
|
237
|
+
if (this.options.enableActiveHeartbeat === false) {
|
|
238
|
+
return
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
if (this.heartbeatTimer !== undefined) {
|
|
242
|
+
return
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
this.heartbeatTimer = setInterval(() => {
|
|
246
|
+
this.runActiveHeartbeat(clientId)
|
|
247
|
+
}, this.options.heartbeatInterval)
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
/**
|
|
251
|
+
* 停止心跳调度并清理全部轮次状态。
|
|
252
|
+
*/
|
|
253
|
+
stop(): void {
|
|
254
|
+
if (this.heartbeatTimer !== undefined) {
|
|
255
|
+
clearInterval(this.heartbeatTimer)
|
|
256
|
+
this.heartbeatTimer = undefined
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
this.heartbeatStates.forEach((heartbeatState) => {
|
|
260
|
+
if (heartbeatState.responseTimer !== undefined) {
|
|
261
|
+
clearTimeout(heartbeatState.responseTimer)
|
|
262
|
+
}
|
|
263
|
+
})
|
|
264
|
+
this.heartbeatStates = []
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
protected runActiveHeartbeat(clientId: string): void {
|
|
268
|
+
const activeHeartbeatMessageHandler = this.options.activeHeartbeatMessageHandlerGenerator!()
|
|
269
|
+
const heartbeatState: SocketUnitHeartbeatState<Message> = {
|
|
270
|
+
activeHeartbeatMessageHandler,
|
|
271
|
+
responseTimer: undefined,
|
|
272
|
+
status: "waiting",
|
|
273
|
+
pingMessageString: undefined,
|
|
274
|
+
pongMessageString: undefined,
|
|
275
|
+
}
|
|
276
|
+
this.heartbeatStates.push(heartbeatState)
|
|
277
|
+
|
|
278
|
+
const pingMessage = activeHeartbeatMessageHandler.buildPingMessage(clientId)
|
|
279
|
+
const pingMessageString = JSON.stringify(pingMessage)
|
|
280
|
+
heartbeatState.pingMessageString = pingMessageString
|
|
281
|
+
|
|
282
|
+
const sendResult = this.callbacks.sendMessage({
|
|
283
|
+
action: "Active heartbeat ping send",
|
|
284
|
+
messageString: pingMessageString,
|
|
285
|
+
})
|
|
286
|
+
if (sendResult === true) {
|
|
287
|
+
this.logger.info(`Active heartbeat ping message sent: ${clientId}, ${pingMessageString}`)
|
|
288
|
+
}
|
|
289
|
+
else {
|
|
290
|
+
heartbeatState.status = "fail"
|
|
291
|
+
this.callbacks.close()
|
|
292
|
+
return
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
heartbeatState.responseTimer = setTimeout(() => {
|
|
296
|
+
if (heartbeatState.status !== "waiting") {
|
|
297
|
+
return
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
heartbeatState.responseTimer = undefined
|
|
301
|
+
heartbeatState.status = "fail"
|
|
302
|
+
const timeoutCount = this.countConsecutiveHeartbeatTimeouts(this.heartbeatStates)
|
|
303
|
+
if (timeoutCount >= this.options.maxHeartbeatLostCount) {
|
|
304
|
+
this.logger.error("Heartbeat timeout limit reached. Closing WebSocket connection.")
|
|
305
|
+
this.callbacks.close()
|
|
306
|
+
}
|
|
307
|
+
}, this.options.maxResponseTime)
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
handleMessage(
|
|
311
|
+
clientId: string,
|
|
312
|
+
message: Message,
|
|
313
|
+
): boolean {
|
|
314
|
+
const activeHandleResult = this.handleActiveHeartbeatMessage(
|
|
315
|
+
clientId, message
|
|
316
|
+
)
|
|
317
|
+
if (activeHandleResult === true) {
|
|
318
|
+
return true
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
const passiveHandleResult = this.handlePassiveHeartbeatMessage(
|
|
322
|
+
clientId, message
|
|
323
|
+
)
|
|
324
|
+
return passiveHandleResult
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
protected handleActiveHeartbeatMessage(
|
|
328
|
+
clientId: string,
|
|
329
|
+
message: Message,
|
|
330
|
+
): boolean {
|
|
331
|
+
if (this.options.enableActiveHeartbeat === false) {
|
|
332
|
+
return false
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
const heartbeatState = this.findMatchingWaitingHeartbeatState(clientId, message)
|
|
336
|
+
if (heartbeatState === undefined) {
|
|
337
|
+
return false
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
const messageString = JSON.stringify(message)
|
|
341
|
+
this.logger.info(`Active heartbeat pong message received: ${clientId}, ${messageString}`)
|
|
342
|
+
if (heartbeatState.responseTimer !== undefined) {
|
|
343
|
+
clearTimeout(heartbeatState.responseTimer)
|
|
344
|
+
heartbeatState.responseTimer = undefined
|
|
345
|
+
}
|
|
346
|
+
heartbeatState.pongMessageString = messageString
|
|
347
|
+
heartbeatState.status = "success"
|
|
348
|
+
this.cleanupHeartbeatStatesAfterSuccess(heartbeatState)
|
|
349
|
+
return true
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
protected handlePassiveHeartbeatMessage(
|
|
353
|
+
clientId: string,
|
|
354
|
+
message: Message,
|
|
355
|
+
): boolean {
|
|
356
|
+
if (this.options.enablePassiveHeartbeat === false) {
|
|
357
|
+
return false
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
const passiveHeartbeatMessageHandler = this.options.passiveHeartbeatMessageHandlerGenerator!()
|
|
361
|
+
if (passiveHeartbeatMessageHandler.verifyPingMessage(clientId, message) === false) {
|
|
362
|
+
return false
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
const messageString = JSON.stringify(message)
|
|
366
|
+
this.logger.info(`Passive heartbeat ping message received: ${clientId}, ${messageString}`)
|
|
367
|
+
const pongMessage = passiveHeartbeatMessageHandler.buildPongMessage(clientId, message)
|
|
368
|
+
const pongMessageString = JSON.stringify(pongMessage)
|
|
369
|
+
const sendResult = this.callbacks.sendMessage({
|
|
370
|
+
action: "Passive heartbeat pong send",
|
|
371
|
+
messageString: pongMessageString,
|
|
372
|
+
})
|
|
373
|
+
if (sendResult === true) {
|
|
374
|
+
this.logger.info(`Passive heartbeat pong message sent: ${clientId}, ${pongMessageString}`)
|
|
375
|
+
}
|
|
376
|
+
return true
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
protected findMatchingWaitingHeartbeatState(
|
|
380
|
+
clientId: string,
|
|
381
|
+
message: Message,
|
|
382
|
+
): SocketUnitHeartbeatState<Message> | undefined {
|
|
383
|
+
for (let index = this.heartbeatStates.length - 1; index >= 0; index = index - 1) {
|
|
384
|
+
const heartbeatState = this.heartbeatStates[index]!
|
|
385
|
+
|
|
386
|
+
if (heartbeatState.status !== "waiting") {
|
|
387
|
+
continue
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
if (heartbeatState.activeHeartbeatMessageHandler.verifyPongMessage(clientId, message) === true) {
|
|
391
|
+
return heartbeatState
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
return undefined
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
protected countConsecutiveHeartbeatTimeouts(
|
|
399
|
+
heartbeatStates: Array<SocketUnitHeartbeatState<Message>>,
|
|
400
|
+
): number {
|
|
401
|
+
let timeoutCount = 0
|
|
402
|
+
|
|
403
|
+
for (let index = heartbeatStates.length - 1; index >= 0; index = index - 1) {
|
|
404
|
+
const heartbeatState = heartbeatStates[index]!
|
|
405
|
+
|
|
406
|
+
if (heartbeatState.status === "fail") {
|
|
407
|
+
timeoutCount = timeoutCount + 1
|
|
408
|
+
continue
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
break
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
return timeoutCount
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
protected cleanupHeartbeatStatesAfterSuccess(
|
|
418
|
+
successState: SocketUnitHeartbeatState<Message>,
|
|
419
|
+
): void {
|
|
420
|
+
const successStateIndex = this.heartbeatStates.lastIndexOf(successState)
|
|
421
|
+
if (successStateIndex === -1) {
|
|
422
|
+
return
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
this.heartbeatStates.splice(0, successStateIndex + 1)
|
|
426
|
+
}
|
|
427
|
+
}
|