@planet-matrix/mobius-model 0.5.0 → 0.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +61 -0
- package/README.md +123 -36
- package/dist/index.js +715 -4
- package/dist/index.js.map +981 -13
- package/oxlint.config.ts +6 -0
- package/package.json +36 -18
- package/src/abort/README.md +92 -0
- package/src/abort/abort-manager.ts +278 -0
- package/src/abort/abort-signal-listener-manager.ts +81 -0
- package/src/abort/index.ts +2 -0
- 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 +72 -116
- package/src/basic/error.ts +19 -5
- package/src/basic/function.ts +83 -64
- package/src/basic/index.ts +1 -0
- package/src/basic/is.ts +152 -71
- package/src/basic/promise.ts +29 -8
- package/src/basic/schedule.ts +111 -0
- package/src/basic/stream.ts +135 -25
- package/src/basic/string.ts +2 -33
- package/src/color/README.md +105 -0
- package/src/color/index.ts +3 -0
- package/src/color/internal.ts +42 -0
- package/src/color/rgb/analyze.ts +236 -0
- package/src/color/rgb/construct.ts +130 -0
- package/src/color/rgb/convert.ts +227 -0
- package/src/color/rgb/derive.ts +303 -0
- package/src/color/rgb/index.ts +6 -0
- package/src/color/rgb/internal.ts +208 -0
- package/src/color/rgb/parse.ts +302 -0
- package/src/color/rgb/serialize.ts +144 -0
- package/src/color/types.ts +57 -0
- package/src/color/xyz/analyze.ts +80 -0
- package/src/color/xyz/construct.ts +19 -0
- package/src/color/xyz/convert.ts +71 -0
- package/src/color/xyz/index.ts +3 -0
- package/src/color/xyz/internal.ts +23 -0
- 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/css/README.md +93 -0
- package/src/css/class.ts +559 -0
- package/src/css/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 +200 -0
- package/src/email/README.md +1 -0
- package/src/email/index.ts +1 -0
- package/src/email/resend.ts +25 -0
- package/src/encoding/README.md +66 -79
- package/src/encoding/base64.ts +13 -4
- package/src/environment/README.md +97 -0
- package/src/environment/basic.ts +26 -0
- package/src/environment/device.ts +311 -0
- package/src/environment/feature.ts +285 -0
- package/src/environment/geo.ts +337 -0
- package/src/environment/index.ts +7 -0
- package/src/environment/runtime.ts +400 -0
- package/src/environment/snapshot.ts +60 -0
- package/src/environment/variable.ts +239 -0
- package/src/event/README.md +90 -0
- package/src/event/class-event-proxy.ts +229 -0
- package/src/event/common.ts +29 -0
- package/src/event/event-manager.ts +203 -0
- package/src/event/index.ts +4 -0
- package/src/event/instance-event-proxy.ts +187 -0
- package/src/event/internal.ts +24 -0
- package/src/exception/README.md +96 -0
- package/src/exception/browser.ts +219 -0
- package/src/exception/index.ts +4 -0
- package/src/exception/nodejs.ts +169 -0
- package/src/exception/normalize.ts +106 -0
- package/src/exception/types.ts +99 -0
- package/src/form/README.md +25 -0
- package/src/form/index.ts +1 -0
- package/src/form/inputor-controller/base.ts +874 -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 +181 -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 +37 -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 +297 -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/identifier/README.md +92 -0
- package/src/identifier/id.ts +119 -0
- package/src/identifier/index.ts +2 -0
- package/src/identifier/uuid.ts +187 -0
- package/src/index.ts +33 -1
- package/src/json/README.md +92 -0
- package/src/json/index.ts +1 -0
- package/src/json/repair.ts +18 -0
- package/src/log/README.md +79 -0
- package/src/log/index.ts +5 -0
- package/src/log/log-emitter.ts +72 -0
- package/src/log/log-record.ts +10 -0
- package/src/log/log-scheduler.ts +74 -0
- package/src/log/log-type.ts +8 -0
- package/src/log/logger.ts +554 -0
- package/src/openai/README.md +1 -0
- package/src/openai/index.ts +1 -0
- package/src/openai/openai.ts +510 -0
- package/src/orchestration/README.md +91 -0
- package/src/orchestration/coordination/barrier.ts +214 -0
- package/src/orchestration/coordination/count-down-latch.ts +215 -0
- package/src/orchestration/coordination/errors.ts +98 -0
- package/src/orchestration/coordination/index.ts +16 -0
- package/src/orchestration/coordination/internal/wait-constraints.ts +95 -0
- package/src/orchestration/coordination/internal/wait-queue.ts +109 -0
- package/src/orchestration/coordination/keyed-lock.ts +168 -0
- package/src/orchestration/coordination/mutex.ts +257 -0
- package/src/orchestration/coordination/permit.ts +127 -0
- package/src/orchestration/coordination/read-write-lock.ts +444 -0
- package/src/orchestration/coordination/semaphore.ts +280 -0
- 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 +3 -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 +56 -86
- 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/reactor/README.md +4 -0
- package/src/reactor/reactor-core/primitive.ts +9 -9
- package/src/reactor/reactor-core/reactive-system.ts +5 -5
- package/src/request/README.md +108 -0
- package/src/request/fetch/base.ts +108 -0
- package/src/request/fetch/browser.ts +285 -0
- package/src/request/fetch/general.ts +20 -0
- package/src/request/fetch/index.ts +4 -0
- package/src/request/fetch/nodejs.ts +285 -0
- package/src/request/index.ts +2 -0
- package/src/request/request/base.ts +250 -0
- package/src/request/request/general.ts +64 -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 +54 -0
- package/src/result/either.ts +193 -0
- package/src/result/index.ts +2 -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 +1642 -0
- package/src/route/uri/hash.ts +308 -0
- package/src/route/uri/index.ts +7 -0
- package/src/route/uri/pathname.ts +376 -0
- package/src/route/uri/search.ts +413 -0
- package/src/singleton/README.md +79 -0
- package/src/singleton/factory.ts +55 -0
- package/src/singleton/index.ts +2 -0
- package/src/singleton/manager.ts +204 -0
- package/src/socket/README.md +105 -0
- package/src/socket/client/index.ts +2 -0
- package/src/socket/client/socket-unit.ts +660 -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 +449 -0
- package/src/socket/server/socket.ts +264 -0
- package/src/storage/README.md +107 -0
- package/src/storage/index.ts +1 -0
- package/src/storage/table.ts +449 -0
- package/src/timer/README.md +86 -0
- package/src/timer/expiration/expiration-manager.ts +594 -0
- package/src/timer/expiration/index.ts +3 -0
- package/src/timer/expiration/min-heap.ts +208 -0
- package/src/timer/expiration/remaining-manager.ts +241 -0
- package/src/timer/index.ts +1 -0
- package/src/tube/README.md +99 -0
- package/src/tube/helper.ts +138 -0
- package/src/tube/index.ts +2 -0
- package/src/tube/tube.ts +880 -0
- package/src/type/README.md +54 -307
- package/src/type/class.ts +2 -2
- package/src/type/index.ts +14 -14
- package/src/type/is.ts +265 -2
- package/src/type/object.ts +37 -0
- package/src/type/string.ts +7 -2
- package/src/type/tuple.ts +6 -6
- package/src/type/union.ts +16 -0
- package/src/web/README.md +77 -0
- package/src/web/capture.ts +35 -0
- package/src/web/clipboard.ts +97 -0
- package/src/web/dom.ts +117 -0
- package/src/web/download.ts +16 -0
- package/src/web/event.ts +46 -0
- package/src/web/index.ts +10 -0
- package/src/web/local-storage.ts +113 -0
- package/src/web/location.ts +28 -0
- package/src/web/permission.ts +172 -0
- package/src/web/script-loader.ts +432 -0
- package/src/weixin/README.md +1 -0
- package/src/weixin/index.ts +2 -0
- package/src/weixin/official-account/authorization.ts +159 -0
- package/src/weixin/official-account/index.ts +2 -0
- package/src/weixin/official-account/js-api.ts +134 -0
- package/src/weixin/open/index.ts +1 -0
- package/src/weixin/open/oauth2.ts +133 -0
- package/tests/unit/abort/abort-manager.spec.ts +225 -0
- package/tests/unit/abort/abort-signal-listener-manager.spec.ts +62 -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 +147 -0
- package/tests/unit/aio/prompt.spec.ts +111 -0
- package/tests/unit/basic/array.spec.ts +1 -1
- package/tests/unit/basic/error.spec.ts +16 -4
- package/tests/unit/basic/schedule.spec.ts +74 -0
- package/tests/unit/basic/stream.spec.ts +91 -38
- package/tests/unit/basic/string.spec.ts +0 -9
- package/tests/unit/color/rgb/analyze.spec.ts +110 -0
- package/tests/unit/color/rgb/construct.spec.ts +56 -0
- package/tests/unit/color/rgb/convert.spec.ts +60 -0
- package/tests/unit/color/rgb/derive.spec.ts +103 -0
- package/tests/unit/color/rgb/parse.spec.ts +66 -0
- package/tests/unit/color/rgb/serialize.spec.ts +46 -0
- package/tests/unit/color/xyz/analyze.spec.ts +33 -0
- package/tests/unit/color/xyz/construct.spec.ts +10 -0
- package/tests/unit/color/xyz/convert.spec.ts +18 -0
- package/tests/unit/credential/api-key.spec.ts +37 -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 +41 -0
- package/tests/unit/cron/cron.spec.ts +84 -0
- package/tests/unit/css/class.spec.ts +157 -0
- package/tests/unit/environment/basic.spec.ts +20 -0
- package/tests/unit/environment/device.spec.ts +146 -0
- package/tests/unit/environment/feature.spec.ts +388 -0
- package/tests/unit/environment/geo.spec.ts +111 -0
- package/tests/unit/environment/runtime.spec.ts +364 -0
- package/tests/unit/environment/snapshot.spec.ts +4 -0
- package/tests/unit/environment/variable.spec.ts +190 -0
- package/tests/unit/event/class-event-proxy.spec.ts +225 -0
- package/tests/unit/event/event-manager.spec.ts +246 -0
- package/tests/unit/event/instance-event-proxy.spec.ts +187 -0
- package/tests/unit/exception/browser.spec.ts +213 -0
- package/tests/unit/exception/nodejs.spec.ts +144 -0
- package/tests/unit/exception/normalize.spec.ts +57 -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 +191 -0
- package/tests/unit/identifier/id.spec.ts +71 -0
- package/tests/unit/identifier/uuid.spec.ts +85 -0
- package/tests/unit/json/repair.spec.ts +11 -0
- package/tests/unit/log/log-emitter.spec.ts +33 -0
- package/tests/unit/log/log-scheduler.spec.ts +40 -0
- package/tests/unit/log/log-type.spec.ts +7 -0
- package/tests/unit/log/logger.spec.ts +237 -0
- package/tests/unit/openai/openai.spec.ts +64 -0
- package/tests/unit/orchestration/coordination/barrier.spec.ts +96 -0
- package/tests/unit/orchestration/coordination/count-down-latch.spec.ts +63 -0
- package/tests/unit/orchestration/coordination/errors.spec.ts +29 -0
- package/tests/unit/orchestration/coordination/keyed-lock.spec.ts +109 -0
- package/tests/unit/orchestration/coordination/mutex.spec.ts +132 -0
- package/tests/unit/orchestration/coordination/permit.spec.ts +43 -0
- package/tests/unit/orchestration/coordination/read-write-lock.spec.ts +154 -0
- package/tests/unit/orchestration/coordination/semaphore.spec.ts +135 -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/reactor/alien-signals-effect.spec.ts +11 -10
- package/tests/unit/reactor/preact-signal.spec.ts +1 -2
- 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 +385 -0
- package/tests/unit/request/request/general.spec.ts +161 -0
- package/tests/unit/route/router/route.spec.ts +431 -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 +147 -0
- package/tests/unit/route/uri/search.spec.ts +107 -0
- package/tests/unit/singleton/singleton.spec.ts +49 -0
- package/tests/unit/socket/client.spec.ts +208 -0
- package/tests/unit/socket/server.spec.ts +135 -0
- package/tests/unit/socket/socket-unit-heartbeat.spec.ts +214 -0
- package/tests/unit/storage/table.spec.ts +620 -0
- package/tests/unit/timer/expiration/expiration-manager.spec.ts +464 -0
- package/tests/unit/timer/expiration/min-heap.spec.ts +71 -0
- package/tests/unit/timer/expiration/remaining-manager.spec.ts +234 -0
- package/tests/unit/tube/helper.spec.ts +139 -0
- package/tests/unit/tube/tube.spec.ts +501 -0
- package/.oxlintrc.json +0 -5
- package/src/random/uuid.ts +0 -103
- package/tests/unit/random/uuid.spec.ts +0 -37
|
@@ -0,0 +1,660 @@
|
|
|
1
|
+
import type { LoggerFriendly, LoggerFriendlyOptions } from "#Source/log/index.ts"
|
|
2
|
+
import { Logger } from "#Source/log/index.ts"
|
|
3
|
+
import type { BuildEvents } from "#Source/event/index.ts"
|
|
4
|
+
import { EventManager } from "#Source/event/index.ts"
|
|
5
|
+
import type {
|
|
6
|
+
SocketUnitBaseSnapshot,
|
|
7
|
+
SocketUnitLifecycleState,
|
|
8
|
+
SocketUnitHeartbeatOptions,
|
|
9
|
+
SocketUnitHeartbeatSnapshot,
|
|
10
|
+
} from "../common/index.ts"
|
|
11
|
+
import {
|
|
12
|
+
SocketUnitHeartbeat,
|
|
13
|
+
} from "../common/index.ts"
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* 表示客户端 SocketUnit 的传输状态。
|
|
17
|
+
*/
|
|
18
|
+
export type SocketUnitStatus = "UNINSTANTIATED" | "CONNECTING" | "OPEN" | "CLOSING" | "CLOSED"
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* 表示客户端 SocketUnit 对外派发的事件表。
|
|
22
|
+
*/
|
|
23
|
+
export type SocketUnitEvents<Message> = BuildEvents<{
|
|
24
|
+
status: (status: SocketUnitStatus) => void
|
|
25
|
+
message: (message: Message) => void
|
|
26
|
+
}>
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* 表示客户端 SocketUnit 的诊断快照。
|
|
30
|
+
*/
|
|
31
|
+
export interface SocketUnitSnapshot extends SocketUnitBaseSnapshot<
|
|
32
|
+
string | undefined,
|
|
33
|
+
SocketUnitStatus,
|
|
34
|
+
SocketUnitHeartbeatSnapshot
|
|
35
|
+
> {
|
|
36
|
+
adapterState: {
|
|
37
|
+
kind: "client"
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* 表示客户端初始消息的构造器。
|
|
43
|
+
*/
|
|
44
|
+
export type InitialMessageBuilder<Message> = (clientId: string) => Message
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* 表示客户端 SocketUnit 的构造参数。
|
|
48
|
+
*/
|
|
49
|
+
export interface SocketUnitOptions<Message> extends LoggerFriendlyOptions {
|
|
50
|
+
url: string
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* 是否启用初始消息。
|
|
54
|
+
*
|
|
55
|
+
* 初始消息遵循与普通业务消息一致的 ready 语义:
|
|
56
|
+
* 只有在连接已打开且 clientId 已就绪后才会发送。
|
|
57
|
+
*
|
|
58
|
+
* @default false
|
|
59
|
+
*/
|
|
60
|
+
enableInitialMessage?: boolean | undefined
|
|
61
|
+
initialMessageBuilder?: InitialMessageBuilder<Message> | undefined
|
|
62
|
+
|
|
63
|
+
heartbeat?: SocketUnitHeartbeatOptions<Message> | undefined
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* 验证客户端 SocketUnit 配置是否合法。
|
|
68
|
+
*/
|
|
69
|
+
export const validateSocketUnitOptions = <Message>(options: SocketUnitOptions<Message>): void => {
|
|
70
|
+
if (
|
|
71
|
+
options.enableInitialMessage === true
|
|
72
|
+
&& options.initialMessageBuilder === undefined
|
|
73
|
+
) {
|
|
74
|
+
throw new Error("Initialize message builder is required when enable initial message.")
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* 表示补齐默认值后的客户端 SocketUnit 配置。
|
|
80
|
+
*/
|
|
81
|
+
export interface ResolvedSocketUnitOptions<Message> extends LoggerFriendlyOptions {
|
|
82
|
+
url: string
|
|
83
|
+
enableInitialMessage: boolean
|
|
84
|
+
initialMessageBuilder?: InitialMessageBuilder<Message> | undefined
|
|
85
|
+
heartbeat: SocketUnitHeartbeatOptions<Message>
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* 为客户端 SocketUnit 配置补齐默认值。
|
|
90
|
+
*/
|
|
91
|
+
export const resolveSocketUnitOptions = <Message>(options: SocketUnitOptions<Message>): ResolvedSocketUnitOptions<Message> => {
|
|
92
|
+
return {
|
|
93
|
+
...options,
|
|
94
|
+
url: options.url,
|
|
95
|
+
enableInitialMessage: options.enableInitialMessage ?? false,
|
|
96
|
+
initialMessageBuilder: options.initialMessageBuilder,
|
|
97
|
+
heartbeat: options.heartbeat ?? {},
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
interface Action {
|
|
102
|
+
action: () => void
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* 关于心跳:
|
|
107
|
+
* 心跳只会在正常连接时工作,目的是保持连接的活跃状态,避免被服务器断开。
|
|
108
|
+
* 连接断开之后,心跳会停止工作,直到连接重新建立。
|
|
109
|
+
*/
|
|
110
|
+
export class SocketUnit<Message> implements LoggerFriendly {
|
|
111
|
+
protected readonly options: ResolvedSocketUnitOptions<Message>
|
|
112
|
+
|
|
113
|
+
readonly logger: Logger
|
|
114
|
+
protected heartbeat: SocketUnitHeartbeat<Message>
|
|
115
|
+
protected clientId: string | undefined
|
|
116
|
+
protected webSocket: WebSocket | null
|
|
117
|
+
protected status: SocketUnitStatus
|
|
118
|
+
protected actionQueue: Action[]
|
|
119
|
+
protected runtimeWebSocketListenerCleanup: (() => void) | undefined
|
|
120
|
+
protected openSettlementListenerCleanup: (() => void) | undefined
|
|
121
|
+
|
|
122
|
+
eventManager: EventManager<SocketUnitEvents<Message>>
|
|
123
|
+
|
|
124
|
+
constructor(options: SocketUnitOptions<Message>) {
|
|
125
|
+
validateSocketUnitOptions(options)
|
|
126
|
+
|
|
127
|
+
this.options = resolveSocketUnitOptions(options)
|
|
128
|
+
|
|
129
|
+
this.logger = Logger.fromOptions(options).setDefaultName("SocketUnit")
|
|
130
|
+
this.heartbeat = new SocketUnitHeartbeat<Message>({
|
|
131
|
+
...this.options.heartbeat,
|
|
132
|
+
logger: Logger.derive(this.logger).setName("SocketUnitHeartbeat")
|
|
133
|
+
}, {
|
|
134
|
+
sendMessage: (message) => {
|
|
135
|
+
return this.safeSendMessage(message)
|
|
136
|
+
},
|
|
137
|
+
close: () => {
|
|
138
|
+
void this.close()
|
|
139
|
+
},
|
|
140
|
+
})
|
|
141
|
+
this.clientId = undefined
|
|
142
|
+
this.webSocket = null
|
|
143
|
+
this.status = "UNINSTANTIATED"
|
|
144
|
+
this.actionQueue = []
|
|
145
|
+
this.runtimeWebSocketListenerCleanup = undefined
|
|
146
|
+
this.openSettlementListenerCleanup = undefined
|
|
147
|
+
|
|
148
|
+
this.eventManager = new EventManager<SocketUnitEvents<Message>>()
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* 返回当前 SocketUnit 的状态。
|
|
153
|
+
*/
|
|
154
|
+
getStatus(): SocketUnitStatus {
|
|
155
|
+
return this.status
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* 返回当前 SocketUnit 的快照。
|
|
160
|
+
*/
|
|
161
|
+
getSnapshot(): SocketUnitSnapshot {
|
|
162
|
+
const heartbeatSnapshot: SocketUnitHeartbeatSnapshot = this.heartbeat.getSnapshot()
|
|
163
|
+
|
|
164
|
+
return {
|
|
165
|
+
status: this.status,
|
|
166
|
+
clientId: this.clientId,
|
|
167
|
+
lifecycleState: this.getLifecycleState(),
|
|
168
|
+
isReadyToWork: this.isReadyToWork(),
|
|
169
|
+
hasTransport: this.webSocket !== null,
|
|
170
|
+
pendingActionCount: this.actionQueue.length,
|
|
171
|
+
transportOwnership: "OWNED",
|
|
172
|
+
heartbeat: heartbeatSnapshot,
|
|
173
|
+
adapterState: {
|
|
174
|
+
kind: "client",
|
|
175
|
+
},
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
protected getLifecycleState(): SocketUnitLifecycleState {
|
|
180
|
+
if (this.status === "UNINSTANTIATED") {
|
|
181
|
+
return "IDLE"
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
if (this.status === "CLOSED") {
|
|
185
|
+
return "CLOSED"
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
return "RUNNING"
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
protected mutateRuntimeState(mutator: () => void): void {
|
|
192
|
+
const previousStatus = this.status
|
|
193
|
+
const wasReadyToWork = this.isReadyToWork()
|
|
194
|
+
|
|
195
|
+
mutator()
|
|
196
|
+
|
|
197
|
+
const nextStatus = this.status
|
|
198
|
+
const isReadyToWork = this.isReadyToWork()
|
|
199
|
+
this.handleRuntimeStateChange(previousStatus, nextStatus, wasReadyToWork, isReadyToWork)
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
protected handleRuntimeStateChange(
|
|
203
|
+
previousStatus: SocketUnitStatus,
|
|
204
|
+
nextStatus: SocketUnitStatus,
|
|
205
|
+
wasReadyToWork: boolean,
|
|
206
|
+
isReadyToWork: boolean,
|
|
207
|
+
): void {
|
|
208
|
+
if (
|
|
209
|
+
(previousStatus !== nextStatus && nextStatus !== "OPEN")
|
|
210
|
+
|| (wasReadyToWork === true && isReadyToWork === false)
|
|
211
|
+
) {
|
|
212
|
+
this.heartbeat.stop()
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
if (previousStatus !== nextStatus) {
|
|
216
|
+
this.eventManager.emit("status", nextStatus)
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
if (wasReadyToWork === false && isReadyToWork === true) {
|
|
220
|
+
this.triggerReadyToWork()
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
/**
|
|
225
|
+
* 从 WebSocket 实例计算状态。
|
|
226
|
+
*/
|
|
227
|
+
protected calculateStatus(): SocketUnitStatus {
|
|
228
|
+
if (this.webSocket === null) {
|
|
229
|
+
return "UNINSTANTIATED"
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
const readyStateToStatusMap: Record<number, SocketUnitStatus> = {
|
|
233
|
+
[WebSocket.CONNECTING]: "CONNECTING",
|
|
234
|
+
[WebSocket.OPEN]: "OPEN",
|
|
235
|
+
[WebSocket.CLOSING]: "CLOSING",
|
|
236
|
+
[WebSocket.CLOSED]: "CLOSED",
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
const status = readyStateToStatusMap[this.webSocket.readyState]
|
|
240
|
+
if (status === undefined) {
|
|
241
|
+
throw new Error(`Unexpected readyState: ${this.webSocket.readyState}`)
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
return status
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
/**
|
|
248
|
+
* [sugar method] 自动执行 {@link calculateStatus} 并使用计算结果设置状态。
|
|
249
|
+
*/
|
|
250
|
+
protected updateStatus(): SocketUnitStatus {
|
|
251
|
+
const status = this.calculateStatus()
|
|
252
|
+
this.mutateRuntimeState(() => {
|
|
253
|
+
this.status = status
|
|
254
|
+
})
|
|
255
|
+
return status
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
/**
|
|
259
|
+
* 设置当前 SocketUnit 绑定的 clientId。
|
|
260
|
+
*/
|
|
261
|
+
setClientId(clientId: string): void {
|
|
262
|
+
this.mutateRuntimeState(() => {
|
|
263
|
+
this.clientId = clientId
|
|
264
|
+
})
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
/**
|
|
268
|
+
* [sugar method] 设置 WebSocket 实例并调用 {@link updateStatus} 更新状态。
|
|
269
|
+
*/
|
|
270
|
+
protected setWebSocket(ws: WebSocket | null): void {
|
|
271
|
+
this.mutateRuntimeState(() => {
|
|
272
|
+
this.webSocket = ws
|
|
273
|
+
this.status = this.calculateStatus()
|
|
274
|
+
})
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
protected replaceRuntimeWebSocketListenerCleanup(cleanup: () => void): void {
|
|
278
|
+
this.removeRuntimeWebSocketListeners()
|
|
279
|
+
this.runtimeWebSocketListenerCleanup = cleanup
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
protected removeRuntimeWebSocketListeners(): void {
|
|
283
|
+
const cleanup = this.runtimeWebSocketListenerCleanup
|
|
284
|
+
this.runtimeWebSocketListenerCleanup = undefined
|
|
285
|
+
cleanup?.()
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
protected replaceOpenSettlementListenerCleanup(cleanup: () => void): void {
|
|
289
|
+
this.removeOpenSettlementListeners()
|
|
290
|
+
this.openSettlementListenerCleanup = cleanup
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
protected removeOpenSettlementListeners(): void {
|
|
294
|
+
const cleanup = this.openSettlementListenerCleanup
|
|
295
|
+
this.openSettlementListenerCleanup = undefined
|
|
296
|
+
cleanup?.()
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
protected removeWebSocketListeners(): void {
|
|
300
|
+
this.removeOpenSettlementListeners()
|
|
301
|
+
this.removeRuntimeWebSocketListeners()
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
protected clearPendingActions(): void {
|
|
305
|
+
this.actionQueue = []
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
protected pushToQueueOrExecute(action: Action): void {
|
|
309
|
+
if (this.isReadyToWork() === true) {
|
|
310
|
+
action.action()
|
|
311
|
+
}
|
|
312
|
+
else {
|
|
313
|
+
this.actionQueue.push(action)
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
protected executeActionQueue(): void {
|
|
318
|
+
[...this.actionQueue].forEach((action) => {
|
|
319
|
+
action.action()
|
|
320
|
+
})
|
|
321
|
+
this.actionQueue = []
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
/**
|
|
325
|
+
* NOTE: Ready 之后才可以发消息。
|
|
326
|
+
*/
|
|
327
|
+
protected isReadyToWork(): boolean {
|
|
328
|
+
return this.status === "OPEN" && this.clientId !== undefined
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
protected triggerReadyToWork(): void {
|
|
332
|
+
if (this.isReadyToWork() === true) {
|
|
333
|
+
this.heartbeat.start(this.clientId!)
|
|
334
|
+
this.executeActionQueue()
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
protected cleanupConnectionResources(): void {
|
|
339
|
+
this.heartbeat.stop()
|
|
340
|
+
this.removeWebSocketListeners()
|
|
341
|
+
|
|
342
|
+
if (this.webSocket !== null) {
|
|
343
|
+
this.setWebSocket(null)
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
protected resetRuntimeState(): void {
|
|
348
|
+
this.cleanupConnectionResources()
|
|
349
|
+
this.clearPendingActions()
|
|
350
|
+
|
|
351
|
+
this.mutateRuntimeState(() => {
|
|
352
|
+
this.clientId = undefined
|
|
353
|
+
this.status = "UNINSTANTIATED"
|
|
354
|
+
})
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
/**
|
|
358
|
+
* 发送一条业务消息;若尚未 ready,则先进入待发送队列。
|
|
359
|
+
*/
|
|
360
|
+
sendMessage(message: Message): void {
|
|
361
|
+
this.pushToQueueOrExecute({
|
|
362
|
+
action: (): void => {
|
|
363
|
+
this.safeSendMessage({
|
|
364
|
+
action: "Business message send",
|
|
365
|
+
messageString: JSON.stringify(message)
|
|
366
|
+
})
|
|
367
|
+
},
|
|
368
|
+
})
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
protected enqueueInitialMessageIfEnabled(): void {
|
|
372
|
+
if (this.options.enableInitialMessage === true) {
|
|
373
|
+
this.pushToQueueOrExecute({
|
|
374
|
+
action: () => {
|
|
375
|
+
const clientId = this.clientId!
|
|
376
|
+
const initialMessage = this.options.initialMessageBuilder!(clientId)
|
|
377
|
+
const initialMessageString = JSON.stringify(initialMessage)
|
|
378
|
+
const sendResult = this.safeSendMessage({
|
|
379
|
+
action: "Initial message send",
|
|
380
|
+
messageString: initialMessageString
|
|
381
|
+
})
|
|
382
|
+
if (sendResult === true) {
|
|
383
|
+
this.logger.log(`Initial message sent: ${clientId}, ${initialMessageString}`)
|
|
384
|
+
}
|
|
385
|
+
},
|
|
386
|
+
})
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
protected safeSendMessage(message: {
|
|
391
|
+
action: string,
|
|
392
|
+
messageString: string
|
|
393
|
+
}): boolean {
|
|
394
|
+
const webSocket = this.webSocket
|
|
395
|
+
if (webSocket === null) {
|
|
396
|
+
this.logger.warn(`${message.action} skipped because WebSocket is not initialized.`)
|
|
397
|
+
return false
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
if (webSocket.readyState !== WebSocket.OPEN) {
|
|
401
|
+
this.logger.warn(`${message.action} skipped because WebSocket is not open.`)
|
|
402
|
+
return false
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
try {
|
|
406
|
+
webSocket.send(message.messageString)
|
|
407
|
+
return true
|
|
408
|
+
}
|
|
409
|
+
catch (exception) {
|
|
410
|
+
this.logger.error(`${message.action} failed.`, exception)
|
|
411
|
+
void this.close()
|
|
412
|
+
return false
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
protected parseMessage(event: MessageEvent): Message | undefined {
|
|
417
|
+
try {
|
|
418
|
+
// oxlint-disable-next-line typescript/no-unsafe-type-assertion
|
|
419
|
+
const messageString = event.data as string
|
|
420
|
+
// oxlint-disable-next-line typescript/no-unsafe-type-assertion
|
|
421
|
+
const parsedMessage = JSON.parse(messageString) as Message
|
|
422
|
+
return parsedMessage
|
|
423
|
+
}
|
|
424
|
+
catch {
|
|
425
|
+
this.logger.error("Failed to parse message:", event.data)
|
|
426
|
+
return undefined
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
protected handleParsedMessage(parsedMessage: Message): void {
|
|
431
|
+
const clientId = this.clientId
|
|
432
|
+
if (clientId === undefined) {
|
|
433
|
+
this.logger.warn("Message skipped because clientId is not ready.")
|
|
434
|
+
return
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
const heartbeatHandleResult = this.heartbeat.handleMessage(
|
|
438
|
+
clientId, parsedMessage
|
|
439
|
+
)
|
|
440
|
+
if (heartbeatHandleResult === true) {
|
|
441
|
+
return
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
this.handleBusinessMessage(parsedMessage)
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
protected handleBusinessMessage(message: Message): void {
|
|
448
|
+
this.eventManager.emit("message", message)
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
protected handleMessageEvent(event: MessageEvent): void {
|
|
452
|
+
const parsedMessage = this.parseMessage(event)
|
|
453
|
+
if (parsedMessage === undefined) {
|
|
454
|
+
return
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
this.handleParsedMessage(parsedMessage)
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
protected readonly handleRuntimeOpen = (event: Event): void => {
|
|
461
|
+
this.logger.log("WebSocket open", event)
|
|
462
|
+
this.updateStatus()
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
protected readonly handleRuntimeMessage = (event: MessageEvent): void => {
|
|
466
|
+
this.updateStatus()
|
|
467
|
+
this.handleMessageEvent(event)
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
protected readonly handleRuntimeClose = (event: CloseEvent): void => {
|
|
471
|
+
this.logger.log("WebSocket close", event)
|
|
472
|
+
this.updateStatus()
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
protected readonly handleRuntimeError = (event: Event): void => {
|
|
476
|
+
this.logger.error("WebSocket error", event)
|
|
477
|
+
this.updateStatus()
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
protected attachRuntimeWebSocketListeners(webSocket: WebSocket): void {
|
|
481
|
+
webSocket.addEventListener("open", this.handleRuntimeOpen)
|
|
482
|
+
webSocket.addEventListener("message", this.handleRuntimeMessage)
|
|
483
|
+
webSocket.addEventListener("close", this.handleRuntimeClose)
|
|
484
|
+
webSocket.addEventListener("error", this.handleRuntimeError)
|
|
485
|
+
|
|
486
|
+
this.replaceRuntimeWebSocketListenerCleanup((): void => {
|
|
487
|
+
webSocket.removeEventListener("open", this.handleRuntimeOpen)
|
|
488
|
+
webSocket.removeEventListener("message", this.handleRuntimeMessage)
|
|
489
|
+
webSocket.removeEventListener("close", this.handleRuntimeClose)
|
|
490
|
+
webSocket.removeEventListener("error", this.handleRuntimeError)
|
|
491
|
+
})
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
protected attachOpenSettlementListeners(
|
|
495
|
+
webSocket: WebSocket,
|
|
496
|
+
resolve: () => void,
|
|
497
|
+
reject: (reason?: unknown) => void,
|
|
498
|
+
): void {
|
|
499
|
+
let isOpenSettled = false
|
|
500
|
+
|
|
501
|
+
const settleOpen = (callback: () => void): void => {
|
|
502
|
+
if (isOpenSettled === true) {
|
|
503
|
+
return
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
isOpenSettled = true
|
|
507
|
+
this.removeOpenSettlementListeners()
|
|
508
|
+
callback()
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
const openListener = (): void => {
|
|
512
|
+
this.updateStatus()
|
|
513
|
+
settleOpen(resolve)
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
const errorListener = (): void => {
|
|
517
|
+
this.updateStatus()
|
|
518
|
+
|
|
519
|
+
if (webSocket.readyState !== WebSocket.CLOSED) {
|
|
520
|
+
return
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
settleOpen(() => {
|
|
524
|
+
this.cleanupConnectionResources()
|
|
525
|
+
reject(new Error(`Failed to open WebSocket connection: ${this.options.url}`))
|
|
526
|
+
})
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
const closeListener = (event: CloseEvent): void => {
|
|
530
|
+
this.updateStatus()
|
|
531
|
+
|
|
532
|
+
const closeReason = event.reason.length !== 0
|
|
533
|
+
? ` (${event.code}: ${event.reason})`
|
|
534
|
+
: ` (${event.code})`
|
|
535
|
+
|
|
536
|
+
settleOpen(() => {
|
|
537
|
+
this.cleanupConnectionResources()
|
|
538
|
+
reject(new Error(`WebSocket closed before opening${closeReason}.`))
|
|
539
|
+
})
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
webSocket.addEventListener("open", openListener)
|
|
543
|
+
webSocket.addEventListener("error", errorListener)
|
|
544
|
+
webSocket.addEventListener("close", closeListener)
|
|
545
|
+
|
|
546
|
+
this.replaceOpenSettlementListenerCleanup((): void => {
|
|
547
|
+
webSocket.removeEventListener("open", openListener)
|
|
548
|
+
webSocket.removeEventListener("error", errorListener)
|
|
549
|
+
webSocket.removeEventListener("close", closeListener)
|
|
550
|
+
})
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
/**
|
|
554
|
+
* 如果没有创建 WebSocket 实例,则创建一个 WebSocket 实例,并注册事件监听器。
|
|
555
|
+
* 如果已经创建了 WebSocket 实例,则什么都不做。
|
|
556
|
+
* 如果你希望在已经创建了 WebSocket 实例的情况下报错,可以使用 {@link safeOpen} 方法。
|
|
557
|
+
*/
|
|
558
|
+
async open(): Promise<void> {
|
|
559
|
+
const status = this.updateStatus()
|
|
560
|
+
if (status === "UNINSTANTIATED") {
|
|
561
|
+
return await new Promise((resolve, reject) => {
|
|
562
|
+
const webSocket = new WebSocket(this.options.url)
|
|
563
|
+
this.setWebSocket(webSocket)
|
|
564
|
+
this.attachRuntimeWebSocketListeners(webSocket)
|
|
565
|
+
|
|
566
|
+
this.enqueueInitialMessageIfEnabled()
|
|
567
|
+
this.attachOpenSettlementListeners(webSocket, resolve, reject)
|
|
568
|
+
})
|
|
569
|
+
}
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
/**
|
|
573
|
+
* {@link open}
|
|
574
|
+
*/
|
|
575
|
+
async safeOpen(): Promise<void> {
|
|
576
|
+
const status = this.updateStatus()
|
|
577
|
+
if (status !== "UNINSTANTIATED") {
|
|
578
|
+
throw new Error("Socket is already open")
|
|
579
|
+
}
|
|
580
|
+
else {
|
|
581
|
+
await this.open()
|
|
582
|
+
}
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
/**
|
|
586
|
+
* 只负责关闭当前连接与释放连接级资源,保留 clientId 与待发送动作。
|
|
587
|
+
*/
|
|
588
|
+
async close(): Promise<void> {
|
|
589
|
+
const closingWebSocket = new Promise<void>((resolve, _reject) => {
|
|
590
|
+
const status = this.calculateStatus()
|
|
591
|
+
switch (status) {
|
|
592
|
+
case "UNINSTANTIATED": {
|
|
593
|
+
resolve()
|
|
594
|
+
break
|
|
595
|
+
}
|
|
596
|
+
case "CONNECTING": {
|
|
597
|
+
// 如果 WebSocket 处于连接中,等待连接建立后再关闭,否则会有如下报错:
|
|
598
|
+
// WebSocket connection to 'ws://localhost:3001/' failed: WebSocket is closed before the connection is established.
|
|
599
|
+
const webSocket = this.webSocket!
|
|
600
|
+
const closeListener = (): void => {
|
|
601
|
+
resolve()
|
|
602
|
+
webSocket.removeEventListener("open", openListener)
|
|
603
|
+
webSocket.removeEventListener("close", closeListener)
|
|
604
|
+
}
|
|
605
|
+
webSocket.addEventListener("close", closeListener)
|
|
606
|
+
const openListener = (): void => {
|
|
607
|
+
webSocket.close()
|
|
608
|
+
}
|
|
609
|
+
webSocket.addEventListener("open", openListener)
|
|
610
|
+
break
|
|
611
|
+
}
|
|
612
|
+
case "OPEN": {
|
|
613
|
+
const webSocket = this.webSocket!
|
|
614
|
+
const closeListener = (): void => {
|
|
615
|
+
resolve()
|
|
616
|
+
webSocket.removeEventListener("close", closeListener)
|
|
617
|
+
}
|
|
618
|
+
webSocket.addEventListener("close", closeListener)
|
|
619
|
+
webSocket.close()
|
|
620
|
+
break
|
|
621
|
+
}
|
|
622
|
+
case "CLOSING": {
|
|
623
|
+
const webSocket = this.webSocket!
|
|
624
|
+
const closeListener = (): void => {
|
|
625
|
+
resolve()
|
|
626
|
+
webSocket.removeEventListener("close", closeListener)
|
|
627
|
+
}
|
|
628
|
+
webSocket.addEventListener("close", closeListener)
|
|
629
|
+
break
|
|
630
|
+
}
|
|
631
|
+
case "CLOSED": {
|
|
632
|
+
resolve()
|
|
633
|
+
break
|
|
634
|
+
}
|
|
635
|
+
default: {
|
|
636
|
+
throw new Error("Unexpected status")
|
|
637
|
+
}
|
|
638
|
+
}
|
|
639
|
+
})
|
|
640
|
+
await closingWebSocket
|
|
641
|
+
this.cleanupConnectionResources()
|
|
642
|
+
}
|
|
643
|
+
|
|
644
|
+
/**
|
|
645
|
+
* 关闭当前连接后重建连接,保留 clientId 与待发送动作。
|
|
646
|
+
*/
|
|
647
|
+
async reopen(): Promise<void> {
|
|
648
|
+
await this.close()
|
|
649
|
+
await this.open()
|
|
650
|
+
}
|
|
651
|
+
|
|
652
|
+
/**
|
|
653
|
+
* 清空连接级资源与运行态,将实例恢复到接近构造后的状态。
|
|
654
|
+
*/
|
|
655
|
+
async reset(onReset?: () => void): Promise<void> {
|
|
656
|
+
await this.close()
|
|
657
|
+
this.resetRuntimeState()
|
|
658
|
+
onReset?.()
|
|
659
|
+
}
|
|
660
|
+
}
|