@moqtap/codec 0.1.0 → 0.2.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/README.md +99 -95
- package/dist/chunk-4RIXXEII.js +1275 -0
- package/dist/chunk-4XYGE53S.cjs +698 -0
- package/dist/chunk-4YJANAXU.cjs +1109 -0
- package/dist/chunk-6AEHWULA.cjs +641 -0
- package/dist/chunk-7DUBLRXC.js +680 -0
- package/dist/{chunk-WNTXF3DE.cjs → chunk-7IVGHMKJ.cjs} +164 -62
- package/dist/{chunk-YBSEOSSP.js → chunk-A27S7HW7.js} +5 -1
- package/dist/chunk-BISI45MN.cjs +680 -0
- package/dist/{chunk-3BSZ55L3.cjs → chunk-BPNL5YFQ.cjs} +158 -24
- package/dist/chunk-CXDHOMHG.js +1097 -0
- package/dist/chunk-DUBCL3WF.cjs +749 -0
- package/dist/chunk-DWK5ZQZ4.js +642 -0
- package/dist/chunk-E6E3NQYU.js +680 -0
- package/dist/chunk-EFM5T7OM.js +698 -0
- package/dist/chunk-ENURAVHI.cjs +680 -0
- package/dist/{chunk-5WFXFLL4.cjs → chunk-FUFTMAQD.cjs} +96 -63
- package/dist/{chunk-2NARXGVA.cjs → chunk-FWISIR26.cjs} +5 -1
- package/dist/{chunk-YPXLV5YK.js → chunk-FXZ2MYKJ.js} +376 -38
- package/dist/chunk-G26SJ6XS.cjs +1341 -0
- package/dist/chunk-G7GI7LJA.js +737 -0
- package/dist/chunk-GXEW4COZ.cjs +737 -0
- package/dist/chunk-HSVYF6XX.cjs +1361 -0
- package/dist/chunk-IBVM4DMJ.cjs +1097 -0
- package/dist/chunk-IV2H5CFI.cjs +1275 -0
- package/dist/chunk-IV2HRJVT.js +1198 -0
- package/dist/chunk-JSQM2MG3.js +680 -0
- package/dist/chunk-K4OLITS2.cjs +1055 -0
- package/dist/{chunk-UOBWHJA5.js → chunk-KFTCU2P6.js} +2 -3
- package/dist/chunk-LH4NTURO.js +1361 -0
- package/dist/{chunk-DC4L6ZIT.js → chunk-MFAP7R6L.js} +154 -20
- package/dist/chunk-NGVE2RZT.js +1097 -0
- package/dist/chunk-NUX5BHWO.js +1341 -0
- package/dist/chunk-PJRA2TQ5.js +1055 -0
- package/dist/chunk-RVJAGE4S.cjs +1198 -0
- package/dist/{chunk-QYG6KGOV.cjs → chunk-RWQ43Z4F.cjs} +2 -3
- package/dist/chunk-RZHAPEXO.js +749 -0
- package/dist/chunk-ST24APEO.js +1109 -0
- package/dist/chunk-SYHW3FLI.cjs +642 -0
- package/dist/chunk-TLYNOOQP.cjs +432 -0
- package/dist/{chunk-23YG7F46.js → chunk-TMNGRIPL.js} +153 -51
- package/dist/{chunk-IQPDRQVC.js → chunk-U2B3B42P.js} +62 -29
- package/dist/chunk-UNS34PTA.cjs +680 -0
- package/dist/chunk-UR6JKS56.js +432 -0
- package/dist/{chunk-GDRGWFEK.cjs → chunk-UYXTY6ZQ.cjs} +376 -38
- package/dist/chunk-XUUCOLWU.cjs +1097 -0
- package/dist/chunk-YG5KJESI.js +641 -0
- package/dist/chunk-ZBKE2QRQ.js +1401 -0
- package/dist/chunk-ZSPO2GF2.cjs +1401 -0
- package/dist/codec-95k8CAu5.d.cts +35 -0
- package/dist/codec-AFuOxfsO.d.ts +60 -0
- package/dist/codec-B-UJ5Iow.d.cts +75 -0
- package/dist/codec-BC5jfvMb.d.ts +35 -0
- package/dist/codec-BECYPfY8.d.ts +35 -0
- package/dist/codec-BsPU1vNC.d.ts +39 -0
- package/dist/codec-BvpuF-6u.d.cts +39 -0
- package/dist/codec-C8jZI5Cx.d.cts +39 -0
- package/dist/codec-CAevkgf5.d.cts +33 -0
- package/dist/codec-CSUqCrRs.d.ts +39 -0
- package/dist/codec-C_HMXNK_.d.ts +33 -0
- package/dist/{codec-CTvFtQQI.d.cts → codec-CpuvYTSV.d.cts} +5 -5
- package/dist/codec-D0x8-SCw.d.cts +35 -0
- package/dist/codec-D7ARhpG1.d.ts +75 -0
- package/dist/codec-DNAUGshO.d.cts +60 -0
- package/dist/codec-DPx_QNn0.d.ts +31 -0
- package/dist/{codec-qPzfmLNu.d.ts → codec-DRhCx_hw.d.ts} +5 -5
- package/dist/codec-Db7YPe3l.d.ts +31 -0
- package/dist/codec-axkJpb7D.d.cts +31 -0
- package/dist/codec-ujAbFaep.d.cts +31 -0
- package/dist/draft10-session.cjs +6 -0
- package/dist/draft10-session.d.cts +8 -0
- package/dist/draft10-session.d.ts +8 -0
- package/dist/draft10-session.js +6 -0
- package/dist/draft10.cjs +115 -0
- package/dist/draft10.d.cts +95 -0
- package/dist/draft10.d.ts +95 -0
- package/dist/draft10.js +115 -0
- package/dist/draft11-session.cjs +6 -0
- package/dist/draft11-session.d.cts +8 -0
- package/dist/draft11-session.d.ts +8 -0
- package/dist/draft11-session.js +6 -0
- package/dist/draft11.cjs +109 -0
- package/dist/draft11.d.cts +99 -0
- package/dist/draft11.d.ts +99 -0
- package/dist/draft11.js +109 -0
- package/dist/draft12-session.cjs +6 -0
- package/dist/draft12-session.d.cts +8 -0
- package/dist/draft12-session.d.ts +8 -0
- package/dist/draft12-session.js +6 -0
- package/dist/draft12.cjs +117 -0
- package/dist/draft12.d.cts +106 -0
- package/dist/draft12.d.ts +106 -0
- package/dist/draft12.js +117 -0
- package/dist/draft13-session.cjs +6 -0
- package/dist/draft13-session.d.cts +8 -0
- package/dist/draft13-session.d.ts +8 -0
- package/dist/draft13-session.js +6 -0
- package/dist/draft13.cjs +119 -0
- package/dist/draft13.d.cts +108 -0
- package/dist/draft13.d.ts +108 -0
- package/dist/draft13.js +119 -0
- package/dist/draft14-session.cjs +2 -2
- package/dist/draft14-session.d.cts +4 -4
- package/dist/draft14-session.d.ts +4 -4
- package/dist/draft14-session.js +1 -1
- package/dist/draft14.cjs +4 -4
- package/dist/draft14.d.cts +27 -15
- package/dist/draft14.d.ts +27 -15
- package/dist/draft14.js +3 -3
- package/dist/draft15-session.cjs +6 -0
- package/dist/draft15-session.d.cts +8 -0
- package/dist/draft15-session.d.ts +8 -0
- package/dist/draft15-session.js +6 -0
- package/dist/draft15.cjs +111 -0
- package/dist/draft15.d.cts +93 -0
- package/dist/draft15.d.ts +93 -0
- package/dist/draft15.js +111 -0
- package/dist/draft16-session.cjs +6 -0
- package/dist/draft16-session.d.cts +8 -0
- package/dist/draft16-session.d.ts +8 -0
- package/dist/draft16-session.js +6 -0
- package/dist/draft16.cjs +113 -0
- package/dist/draft16.d.cts +94 -0
- package/dist/draft16.d.ts +94 -0
- package/dist/draft16.js +113 -0
- package/dist/draft17-session.cjs +8 -0
- package/dist/draft17-session.d.cts +51 -0
- package/dist/draft17-session.d.ts +51 -0
- package/dist/draft17-session.js +8 -0
- package/dist/draft17.cjs +99 -0
- package/dist/draft17.d.cts +40 -0
- package/dist/draft17.d.ts +40 -0
- package/dist/draft17.js +99 -0
- package/dist/draft7-session.cjs +3 -3
- package/dist/draft7-session.d.cts +3 -3
- package/dist/draft7-session.d.ts +3 -3
- package/dist/draft7-session.js +2 -2
- package/dist/draft7.cjs +6 -6
- package/dist/draft7.d.cts +10 -10
- package/dist/draft7.d.ts +10 -10
- package/dist/draft7.js +3 -3
- package/dist/draft8-session.cjs +6 -0
- package/dist/draft8-session.d.cts +8 -0
- package/dist/draft8-session.d.ts +8 -0
- package/dist/draft8-session.js +6 -0
- package/dist/draft8.cjs +115 -0
- package/dist/draft8.d.cts +95 -0
- package/dist/draft8.d.ts +95 -0
- package/dist/draft8.js +115 -0
- package/dist/draft9-session.cjs +6 -0
- package/dist/draft9-session.d.cts +8 -0
- package/dist/draft9-session.d.ts +8 -0
- package/dist/draft9-session.js +6 -0
- package/dist/draft9.cjs +115 -0
- package/dist/draft9.d.cts +95 -0
- package/dist/draft9.d.ts +95 -0
- package/dist/draft9.js +115 -0
- package/dist/index.cjs +79 -7
- package/dist/index.d.cts +71 -8
- package/dist/index.d.ts +71 -8
- package/dist/index.js +77 -5
- package/dist/{session-types-B9NIf7_F.d.ts → session-types-CJIFbTPd.d.ts} +20 -20
- package/dist/{session-types-CCo-oA-d.d.cts → session-types-Cbq8IGCP.d.cts} +20 -20
- package/dist/session.cjs +5 -5
- package/dist/session.d.cts +3 -3
- package/dist/session.d.ts +3 -3
- package/dist/session.js +5 -5
- package/dist/types-4VxSL2Ho.d.cts +261 -0
- package/dist/types-4VxSL2Ho.d.ts +261 -0
- package/dist/types-B2afJZM-.d.cts +236 -0
- package/dist/types-B2afJZM-.d.ts +236 -0
- package/dist/{types-CIk5W10V.d.ts → types-BTFeKYCb.d.cts} +37 -37
- package/dist/{types-CIk5W10V.d.cts → types-BTFeKYCb.d.ts} +37 -37
- package/dist/types-Bg6QYNVt.d.cts +290 -0
- package/dist/types-Bg6QYNVt.d.ts +290 -0
- package/dist/types-C_1HrqBl.d.cts +306 -0
- package/dist/types-C_1HrqBl.d.ts +306 -0
- package/dist/types-Cw4WE9dh.d.cts +261 -0
- package/dist/types-Cw4WE9dh.d.ts +261 -0
- package/dist/types-D5gNQiDj.d.cts +261 -0
- package/dist/types-D5gNQiDj.d.ts +261 -0
- package/dist/types-DqCDFqgB.d.cts +230 -0
- package/dist/types-DqCDFqgB.d.ts +230 -0
- package/dist/types-ERexTpT8.d.cts +217 -0
- package/dist/types-ERexTpT8.d.ts +217 -0
- package/dist/{types-ClXELFGN.d.cts → types-QNXoxC9Y.d.cts} +36 -41
- package/dist/{types-ClXELFGN.d.ts → types-QNXoxC9Y.d.ts} +36 -41
- package/dist/types-r-CasCf1.d.cts +262 -0
- package/dist/types-r-CasCf1.d.ts +262 -0
- package/package.json +116 -8
- package/src/core/buffer-reader.ts +16 -9
- package/src/core/buffer-writer.ts +2 -2
- package/src/core/errors.ts +1 -1
- package/src/core/session-types.ts +28 -41
- package/src/core/types.ts +92 -75
- package/src/drafts/draft07/announce-fsm.ts +1 -1
- package/src/drafts/draft07/codec.ts +235 -118
- package/src/drafts/draft07/index.ts +43 -44
- package/src/drafts/draft07/messages.ts +1 -1
- package/src/drafts/draft07/parameters.ts +2 -2
- package/src/drafts/draft07/rules.ts +67 -38
- package/src/drafts/draft07/session-fsm.ts +330 -117
- package/src/drafts/draft07/session.ts +10 -10
- package/src/drafts/draft07/subscription-fsm.ts +1 -1
- package/src/drafts/draft07/varint.ts +4 -4
- package/src/drafts/draft08/codec.ts +1254 -0
- package/src/drafts/draft08/index.ts +125 -0
- package/src/drafts/draft08/messages.ts +72 -0
- package/src/drafts/draft08/rules.ts +91 -0
- package/src/drafts/draft08/session-fsm.ts +718 -0
- package/src/drafts/draft08/session.ts +26 -0
- package/src/drafts/draft08/types.ts +377 -0
- package/src/drafts/draft09/codec.ts +1235 -0
- package/src/drafts/draft09/index.ts +125 -0
- package/src/drafts/draft09/messages.ts +72 -0
- package/src/drafts/draft09/rules.ts +91 -0
- package/src/drafts/draft09/session-fsm.ts +718 -0
- package/src/drafts/draft09/session.ts +26 -0
- package/src/drafts/draft09/types.ts +376 -0
- package/src/drafts/draft10/codec.ts +1235 -0
- package/src/drafts/draft10/index.ts +125 -0
- package/src/drafts/draft10/messages.ts +72 -0
- package/src/drafts/draft10/rules.ts +91 -0
- package/src/drafts/draft10/session-fsm.ts +718 -0
- package/src/drafts/draft10/session.ts +26 -0
- package/src/drafts/draft10/types.ts +376 -0
- package/src/drafts/draft11/codec.ts +1198 -0
- package/src/drafts/draft11/index.ts +123 -0
- package/src/drafts/draft11/messages.ts +71 -0
- package/src/drafts/draft11/rules.ts +100 -0
- package/src/drafts/draft11/session-fsm.ts +758 -0
- package/src/drafts/draft11/session.ts +26 -0
- package/src/drafts/draft11/types.ts +375 -0
- package/src/drafts/draft12/codec.ts +1354 -0
- package/src/drafts/draft12/index.ts +130 -0
- package/src/drafts/draft12/messages.ts +84 -0
- package/src/drafts/draft12/rules.ts +106 -0
- package/src/drafts/draft12/session-fsm.ts +805 -0
- package/src/drafts/draft12/session.ts +26 -0
- package/src/drafts/draft12/types.ts +414 -0
- package/src/drafts/draft13/codec.ts +1438 -0
- package/src/drafts/draft13/index.ts +132 -0
- package/src/drafts/draft13/messages.ts +86 -0
- package/src/drafts/draft13/rules.ts +108 -0
- package/src/drafts/draft13/session-fsm.ts +819 -0
- package/src/drafts/draft13/session.ts +26 -0
- package/src/drafts/draft13/types.ts +433 -0
- package/src/drafts/draft14/codec.ts +339 -189
- package/src/drafts/draft14/index.ts +103 -108
- package/src/drafts/draft14/messages.ts +61 -61
- package/src/drafts/draft14/rules.ts +77 -34
- package/src/drafts/draft14/session-fsm.ts +640 -147
- package/src/drafts/draft14/session.ts +13 -13
- package/src/drafts/draft14/types.ts +68 -68
- package/src/drafts/draft15/codec.ts +1661 -0
- package/src/drafts/draft15/index.ts +121 -0
- package/src/drafts/draft15/messages.ts +64 -0
- package/src/drafts/draft15/rules.ts +95 -0
- package/src/drafts/draft15/session-fsm.ts +687 -0
- package/src/drafts/draft15/session.ts +26 -0
- package/src/drafts/draft15/types.ts +336 -0
- package/src/drafts/draft16/codec.ts +1623 -0
- package/src/drafts/draft16/index.ts +123 -0
- package/src/drafts/draft16/messages.ts +67 -0
- package/src/drafts/draft16/rules.ts +96 -0
- package/src/drafts/draft16/session-fsm.ts +682 -0
- package/src/drafts/draft16/session.ts +26 -0
- package/src/drafts/draft16/types.ts +354 -0
- package/src/drafts/draft17/codec.ts +1621 -0
- package/src/drafts/draft17/index.ts +105 -0
- package/src/drafts/draft17/messages.ts +53 -0
- package/src/drafts/draft17/rules.ts +85 -0
- package/src/drafts/draft17/session-fsm.ts +437 -0
- package/src/drafts/draft17/session.ts +15 -0
- package/src/drafts/draft17/types.ts +310 -0
- package/src/index.ts +283 -33
- package/src/session.ts +20 -20
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
// Draft-17 codec entry point
|
|
2
|
+
|
|
3
|
+
export type { Draft17Codec } from "./codec.js";
|
|
4
|
+
export {
|
|
5
|
+
createDataStreamDecoder,
|
|
6
|
+
createDraft17Codec,
|
|
7
|
+
createFetchStreamDecoder,
|
|
8
|
+
createStreamDecoder,
|
|
9
|
+
createSubgroupStreamDecoder,
|
|
10
|
+
decodeDatagram,
|
|
11
|
+
decodeDataStream,
|
|
12
|
+
decodeFetchStream,
|
|
13
|
+
decodeMessage,
|
|
14
|
+
decodeSubgroupStream,
|
|
15
|
+
encodeDatagram,
|
|
16
|
+
encodeFetchStream,
|
|
17
|
+
encodeMessage,
|
|
18
|
+
encodeSubgroupStream,
|
|
19
|
+
} from "./codec.js";
|
|
20
|
+
|
|
21
|
+
export {
|
|
22
|
+
MESSAGE_ID_MAP,
|
|
23
|
+
MESSAGE_TYPE_MAP,
|
|
24
|
+
MSG_FETCH,
|
|
25
|
+
MSG_FETCH_OK,
|
|
26
|
+
MSG_GOAWAY,
|
|
27
|
+
MSG_NAMESPACE,
|
|
28
|
+
MSG_NAMESPACE_DONE,
|
|
29
|
+
MSG_PUBLISH,
|
|
30
|
+
MSG_PUBLISH_BLOCKED,
|
|
31
|
+
MSG_PUBLISH_DONE,
|
|
32
|
+
MSG_PUBLISH_NAMESPACE,
|
|
33
|
+
MSG_PUBLISH_OK,
|
|
34
|
+
MSG_REQUEST_ERROR,
|
|
35
|
+
MSG_REQUEST_OK,
|
|
36
|
+
MSG_REQUEST_UPDATE,
|
|
37
|
+
MSG_SETUP,
|
|
38
|
+
MSG_SUBSCRIBE,
|
|
39
|
+
MSG_SUBSCRIBE_NAMESPACE,
|
|
40
|
+
MSG_SUBSCRIBE_OK,
|
|
41
|
+
MSG_TRACK_STATUS,
|
|
42
|
+
SETUP_OPT_AUTHORITY,
|
|
43
|
+
SETUP_OPT_MAX_AUTH_TOKEN_CACHE_SIZE,
|
|
44
|
+
SETUP_OPT_MOQT_IMPLEMENTATION,
|
|
45
|
+
SETUP_OPT_PATH,
|
|
46
|
+
} from "./messages.js";
|
|
47
|
+
|
|
48
|
+
export {
|
|
49
|
+
BIDIRECTIONAL_MESSAGES,
|
|
50
|
+
CLIENT_ONLY_MESSAGES,
|
|
51
|
+
CONTROL_MESSAGES,
|
|
52
|
+
getLegalIncoming,
|
|
53
|
+
getLegalOutgoing,
|
|
54
|
+
SERVER_ONLY_MESSAGES,
|
|
55
|
+
} from "./rules.js";
|
|
56
|
+
export type {
|
|
57
|
+
ProtocolViolation,
|
|
58
|
+
SessionPhase,
|
|
59
|
+
SideEffect,
|
|
60
|
+
TransitionResult,
|
|
61
|
+
ValidationResult,
|
|
62
|
+
} from "./session.js";
|
|
63
|
+
export { createDraft17SessionState, Draft17SessionFSM } from "./session.js";
|
|
64
|
+
|
|
65
|
+
export type {
|
|
66
|
+
DatagramObject,
|
|
67
|
+
DataStreamEvent,
|
|
68
|
+
DataStreamHeader,
|
|
69
|
+
Draft17BaseMessage,
|
|
70
|
+
Draft17DataStream,
|
|
71
|
+
Draft17Fetch,
|
|
72
|
+
Draft17FetchOk,
|
|
73
|
+
Draft17GoAway,
|
|
74
|
+
Draft17Message,
|
|
75
|
+
Draft17MessageType,
|
|
76
|
+
Draft17Namespace,
|
|
77
|
+
Draft17NamespaceDone,
|
|
78
|
+
Draft17Params,
|
|
79
|
+
Draft17Publish,
|
|
80
|
+
Draft17PublishBlocked,
|
|
81
|
+
Draft17PublishDone,
|
|
82
|
+
Draft17PublishNamespace,
|
|
83
|
+
Draft17PublishOk,
|
|
84
|
+
Draft17RequestError,
|
|
85
|
+
Draft17RequestOk,
|
|
86
|
+
Draft17RequestUpdate,
|
|
87
|
+
Draft17Setup,
|
|
88
|
+
Draft17SetupOptions,
|
|
89
|
+
Draft17Subscribe,
|
|
90
|
+
Draft17SubscribeNamespace,
|
|
91
|
+
Draft17SubscribeOk,
|
|
92
|
+
Draft17TrackProperties,
|
|
93
|
+
Draft17TrackStatus,
|
|
94
|
+
FetchObjectPayload,
|
|
95
|
+
FetchStream,
|
|
96
|
+
FetchStreamHeader,
|
|
97
|
+
JoiningFetch,
|
|
98
|
+
LargestObject,
|
|
99
|
+
ObjectPayload,
|
|
100
|
+
StandaloneFetch,
|
|
101
|
+
SubgroupStream,
|
|
102
|
+
SubgroupStreamHeader,
|
|
103
|
+
SubscriptionFilter,
|
|
104
|
+
UnknownParam,
|
|
105
|
+
} from "./types.js";
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
// Draft-17 message type wire IDs
|
|
2
|
+
|
|
3
|
+
export const MSG_REQUEST_UPDATE = 0x02n;
|
|
4
|
+
export const MSG_SUBSCRIBE = 0x03n;
|
|
5
|
+
export const MSG_SUBSCRIBE_OK = 0x04n;
|
|
6
|
+
export const MSG_REQUEST_ERROR = 0x05n;
|
|
7
|
+
export const MSG_PUBLISH_NAMESPACE = 0x06n;
|
|
8
|
+
export const MSG_REQUEST_OK = 0x07n;
|
|
9
|
+
export const MSG_NAMESPACE = 0x08n;
|
|
10
|
+
export const MSG_PUBLISH_DONE = 0x0bn;
|
|
11
|
+
export const MSG_TRACK_STATUS = 0x0dn;
|
|
12
|
+
export const MSG_NAMESPACE_DONE = 0x0en;
|
|
13
|
+
export const MSG_PUBLISH_BLOCKED = 0x0fn;
|
|
14
|
+
export const MSG_GOAWAY = 0x10n;
|
|
15
|
+
export const MSG_SUBSCRIBE_NAMESPACE = 0x11n;
|
|
16
|
+
export const MSG_FETCH = 0x16n;
|
|
17
|
+
export const MSG_FETCH_OK = 0x18n;
|
|
18
|
+
export const MSG_PUBLISH = 0x1dn;
|
|
19
|
+
export const MSG_PUBLISH_OK = 0x1en;
|
|
20
|
+
export const MSG_SETUP = 0x2f00n;
|
|
21
|
+
|
|
22
|
+
// Setup option type IDs (KVP encoding, constant across versions)
|
|
23
|
+
export const SETUP_OPT_PATH = 0x01n;
|
|
24
|
+
export const SETUP_OPT_MAX_AUTH_TOKEN_CACHE_SIZE = 0x04n;
|
|
25
|
+
export const SETUP_OPT_AUTHORITY = 0x05n;
|
|
26
|
+
export const SETUP_OPT_MOQT_IMPLEMENTATION = 0x07n;
|
|
27
|
+
|
|
28
|
+
// Map from wire ID to message type name
|
|
29
|
+
export const MESSAGE_TYPE_MAP: ReadonlyMap<bigint, string> = new Map([
|
|
30
|
+
[MSG_SETUP, "setup"],
|
|
31
|
+
[MSG_SUBSCRIBE, "subscribe"],
|
|
32
|
+
[MSG_SUBSCRIBE_OK, "subscribe_ok"],
|
|
33
|
+
[MSG_REQUEST_UPDATE, "request_update"],
|
|
34
|
+
[MSG_PUBLISH, "publish"],
|
|
35
|
+
[MSG_PUBLISH_OK, "publish_ok"],
|
|
36
|
+
[MSG_PUBLISH_DONE, "publish_done"],
|
|
37
|
+
[MSG_PUBLISH_NAMESPACE, "publish_namespace"],
|
|
38
|
+
[MSG_NAMESPACE, "namespace"],
|
|
39
|
+
[MSG_NAMESPACE_DONE, "namespace_done"],
|
|
40
|
+
[MSG_SUBSCRIBE_NAMESPACE, "subscribe_namespace"],
|
|
41
|
+
[MSG_PUBLISH_BLOCKED, "publish_blocked"],
|
|
42
|
+
[MSG_FETCH, "fetch"],
|
|
43
|
+
[MSG_FETCH_OK, "fetch_ok"],
|
|
44
|
+
[MSG_TRACK_STATUS, "track_status"],
|
|
45
|
+
[MSG_REQUEST_OK, "request_ok"],
|
|
46
|
+
[MSG_REQUEST_ERROR, "request_error"],
|
|
47
|
+
[MSG_GOAWAY, "goaway"],
|
|
48
|
+
]);
|
|
49
|
+
|
|
50
|
+
// Reverse map from message type name to wire ID
|
|
51
|
+
export const MESSAGE_ID_MAP: ReadonlyMap<string, bigint> = new Map(
|
|
52
|
+
[...MESSAGE_TYPE_MAP.entries()].map(([id, name]) => [name, id]),
|
|
53
|
+
);
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import type { Draft17MessageType } from "./types.js";
|
|
2
|
+
|
|
3
|
+
// All draft-17 control messages
|
|
4
|
+
export const CONTROL_MESSAGES: ReadonlySet<Draft17MessageType> = new Set([
|
|
5
|
+
"setup",
|
|
6
|
+
"subscribe",
|
|
7
|
+
"subscribe_ok",
|
|
8
|
+
"request_update",
|
|
9
|
+
"publish",
|
|
10
|
+
"publish_ok",
|
|
11
|
+
"publish_done",
|
|
12
|
+
"publish_namespace",
|
|
13
|
+
"namespace",
|
|
14
|
+
"namespace_done",
|
|
15
|
+
"subscribe_namespace",
|
|
16
|
+
"publish_blocked",
|
|
17
|
+
"fetch",
|
|
18
|
+
"fetch_ok",
|
|
19
|
+
"track_status",
|
|
20
|
+
"request_ok",
|
|
21
|
+
"request_error",
|
|
22
|
+
"goaway",
|
|
23
|
+
]);
|
|
24
|
+
|
|
25
|
+
// Draft-17 has a single SETUP message — both roles can send it
|
|
26
|
+
export const CLIENT_ONLY_MESSAGES: ReadonlySet<Draft17MessageType> = new Set<Draft17MessageType>();
|
|
27
|
+
|
|
28
|
+
export const SERVER_ONLY_MESSAGES: ReadonlySet<Draft17MessageType> = new Set<Draft17MessageType>();
|
|
29
|
+
|
|
30
|
+
// Messages that are bidirectional (both client and server can send)
|
|
31
|
+
export const BIDIRECTIONAL_MESSAGES: ReadonlySet<Draft17MessageType> = new Set([
|
|
32
|
+
"setup",
|
|
33
|
+
"subscribe",
|
|
34
|
+
"subscribe_ok",
|
|
35
|
+
"request_update",
|
|
36
|
+
"publish",
|
|
37
|
+
"publish_ok",
|
|
38
|
+
"publish_done",
|
|
39
|
+
"publish_namespace",
|
|
40
|
+
"namespace",
|
|
41
|
+
"namespace_done",
|
|
42
|
+
"subscribe_namespace",
|
|
43
|
+
"publish_blocked",
|
|
44
|
+
"fetch",
|
|
45
|
+
"fetch_ok",
|
|
46
|
+
"track_status",
|
|
47
|
+
"request_ok",
|
|
48
|
+
"request_error",
|
|
49
|
+
"goaway",
|
|
50
|
+
]);
|
|
51
|
+
|
|
52
|
+
// Messages legal in each session phase
|
|
53
|
+
export function getLegalOutgoing(
|
|
54
|
+
phase: string,
|
|
55
|
+
_role: "client" | "server",
|
|
56
|
+
): Set<Draft17MessageType> {
|
|
57
|
+
const legal = new Set<Draft17MessageType>();
|
|
58
|
+
|
|
59
|
+
switch (phase) {
|
|
60
|
+
case "idle":
|
|
61
|
+
legal.add("setup");
|
|
62
|
+
break;
|
|
63
|
+
case "setup":
|
|
64
|
+
legal.add("setup");
|
|
65
|
+
break;
|
|
66
|
+
case "ready": {
|
|
67
|
+
for (const msg of BIDIRECTIONAL_MESSAGES) {
|
|
68
|
+
legal.add(msg);
|
|
69
|
+
}
|
|
70
|
+
break;
|
|
71
|
+
}
|
|
72
|
+
case "draining":
|
|
73
|
+
break;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
return legal;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export function getLegalIncoming(
|
|
80
|
+
phase: string,
|
|
81
|
+
role: "client" | "server",
|
|
82
|
+
): Set<Draft17MessageType> {
|
|
83
|
+
const remoteRole = role === "client" ? "server" : "client";
|
|
84
|
+
return getLegalOutgoing(phase, remoteRole);
|
|
85
|
+
}
|
|
@@ -0,0 +1,437 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
AnnounceState,
|
|
3
|
+
FetchState,
|
|
4
|
+
ProtocolViolation,
|
|
5
|
+
PublishState,
|
|
6
|
+
SessionPhase,
|
|
7
|
+
SideEffect,
|
|
8
|
+
SubscriptionState,
|
|
9
|
+
TransitionResult,
|
|
10
|
+
ValidationResult,
|
|
11
|
+
} from "../../core/session-types.js";
|
|
12
|
+
import { getLegalIncoming, getLegalOutgoing } from "./rules.js";
|
|
13
|
+
import type { Draft17Message, Draft17MessageType } from "./types.js";
|
|
14
|
+
|
|
15
|
+
function violation(
|
|
16
|
+
code: ProtocolViolation<Draft17MessageType>["code"],
|
|
17
|
+
message: string,
|
|
18
|
+
currentPhase: SessionPhase,
|
|
19
|
+
offendingMessage: Draft17MessageType,
|
|
20
|
+
): ProtocolViolation<Draft17MessageType> {
|
|
21
|
+
return { code, message, currentPhase, offendingMessage };
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export class Draft17SessionFSM {
|
|
25
|
+
private _phase: SessionPhase = "idle";
|
|
26
|
+
private _role: "client" | "server";
|
|
27
|
+
private _setupDirection: "inbound" | "outbound" | null = null;
|
|
28
|
+
private _subscriptions = new Map<bigint, SubscriptionState>();
|
|
29
|
+
private _publishes = new Map<bigint, PublishState>();
|
|
30
|
+
private _fetches = new Map<bigint, FetchState>();
|
|
31
|
+
private _requestIds = new Set<bigint>();
|
|
32
|
+
private _pendingSubscribes: bigint[] = [];
|
|
33
|
+
private _pendingPublishes: bigint[] = [];
|
|
34
|
+
private _pendingFetches: bigint[] = [];
|
|
35
|
+
|
|
36
|
+
constructor(role: "client" | "server") {
|
|
37
|
+
this._role = role;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
get phase(): SessionPhase {
|
|
41
|
+
return this._phase;
|
|
42
|
+
}
|
|
43
|
+
get role(): "client" | "server" {
|
|
44
|
+
return this._role;
|
|
45
|
+
}
|
|
46
|
+
get subscriptions(): ReadonlyMap<bigint, SubscriptionState> {
|
|
47
|
+
return this._subscriptions;
|
|
48
|
+
}
|
|
49
|
+
get announces(): ReadonlyMap<string, AnnounceState> {
|
|
50
|
+
return new Map();
|
|
51
|
+
}
|
|
52
|
+
get publishes(): ReadonlyMap<bigint, PublishState> {
|
|
53
|
+
return this._publishes;
|
|
54
|
+
}
|
|
55
|
+
get fetches(): ReadonlyMap<bigint, FetchState> {
|
|
56
|
+
return this._fetches;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
get legalOutgoing(): ReadonlySet<Draft17MessageType> {
|
|
60
|
+
return getLegalOutgoing(this._phase, this._role);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
get legalIncoming(): ReadonlySet<Draft17MessageType> {
|
|
64
|
+
return getLegalIncoming(this._phase, this._role);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
private checkDuplicateRequestId(
|
|
68
|
+
requestId: bigint,
|
|
69
|
+
msgType: Draft17MessageType,
|
|
70
|
+
): ProtocolViolation<Draft17MessageType> | null {
|
|
71
|
+
if (this._requestIds.has(requestId)) {
|
|
72
|
+
return violation(
|
|
73
|
+
"DUPLICATE_REQUEST_ID",
|
|
74
|
+
`Request ID ${requestId} already in use`,
|
|
75
|
+
this._phase,
|
|
76
|
+
msgType,
|
|
77
|
+
);
|
|
78
|
+
}
|
|
79
|
+
return null;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
validateOutgoing(message: Draft17Message): ValidationResult<Draft17MessageType> {
|
|
83
|
+
if (!this.legalOutgoing.has(message.type)) {
|
|
84
|
+
return {
|
|
85
|
+
ok: false,
|
|
86
|
+
violation: violation(
|
|
87
|
+
this._phase === "idle" || this._phase === "setup"
|
|
88
|
+
? "MESSAGE_BEFORE_SETUP"
|
|
89
|
+
: "UNEXPECTED_MESSAGE",
|
|
90
|
+
`Cannot send ${message.type} in phase ${this._phase}`,
|
|
91
|
+
this._phase,
|
|
92
|
+
message.type,
|
|
93
|
+
),
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
return { ok: true };
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
receive(message: Draft17Message): TransitionResult<Draft17MessageType> {
|
|
100
|
+
return this.applyTransition(message, "inbound");
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
send(message: Draft17Message): TransitionResult<Draft17MessageType> {
|
|
104
|
+
return this.applyTransition(message, "outbound");
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
private applyTransition(
|
|
108
|
+
message: Draft17Message,
|
|
109
|
+
direction: "inbound" | "outbound",
|
|
110
|
+
): TransitionResult<Draft17MessageType> {
|
|
111
|
+
const sideEffects: SideEffect[] = [];
|
|
112
|
+
|
|
113
|
+
switch (message.type) {
|
|
114
|
+
case "setup":
|
|
115
|
+
return this.handleSetup(direction);
|
|
116
|
+
case "goaway":
|
|
117
|
+
return this.handleGoAway(message, sideEffects);
|
|
118
|
+
|
|
119
|
+
case "subscribe":
|
|
120
|
+
return this.handleSubscribe(message, sideEffects);
|
|
121
|
+
case "subscribe_ok":
|
|
122
|
+
return this.handleSubscribeOk(message, sideEffects);
|
|
123
|
+
case "request_update":
|
|
124
|
+
return this.handleRequestUpdate(message, sideEffects);
|
|
125
|
+
|
|
126
|
+
case "publish":
|
|
127
|
+
return this.handlePublish(message, sideEffects);
|
|
128
|
+
case "publish_ok":
|
|
129
|
+
return this.handlePublishOk(sideEffects);
|
|
130
|
+
case "publish_done":
|
|
131
|
+
return this.handlePublishDone(message, sideEffects);
|
|
132
|
+
|
|
133
|
+
case "fetch":
|
|
134
|
+
return this.handleFetch(message, sideEffects);
|
|
135
|
+
case "fetch_ok":
|
|
136
|
+
return this.handleFetchOk(sideEffects);
|
|
137
|
+
|
|
138
|
+
case "request_ok":
|
|
139
|
+
return this.handleRequestOk(sideEffects);
|
|
140
|
+
case "request_error":
|
|
141
|
+
return this.handleRequestError(sideEffects);
|
|
142
|
+
|
|
143
|
+
default:
|
|
144
|
+
return this.handleReadyPhaseMessage(message);
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
private handleSetup(direction: "inbound" | "outbound"): TransitionResult<Draft17MessageType> {
|
|
149
|
+
if (this._phase === "idle") {
|
|
150
|
+
this._setupDirection = direction;
|
|
151
|
+
this._phase = "setup";
|
|
152
|
+
return { ok: true, phase: this._phase, sideEffects: [] };
|
|
153
|
+
}
|
|
154
|
+
if (this._phase === "setup") {
|
|
155
|
+
if (direction === this._setupDirection) {
|
|
156
|
+
return {
|
|
157
|
+
ok: false,
|
|
158
|
+
violation: violation(
|
|
159
|
+
"SETUP_VIOLATION",
|
|
160
|
+
`Second SETUP must be ${this._setupDirection === "inbound" ? "outbound" : "inbound"}, got ${direction}`,
|
|
161
|
+
this._phase,
|
|
162
|
+
"setup",
|
|
163
|
+
),
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
this._phase = "ready";
|
|
167
|
+
return { ok: true, phase: this._phase, sideEffects: [{ type: "session-ready" }] };
|
|
168
|
+
}
|
|
169
|
+
return {
|
|
170
|
+
ok: false,
|
|
171
|
+
violation: violation(
|
|
172
|
+
"SETUP_VIOLATION",
|
|
173
|
+
"SETUP not valid in current phase",
|
|
174
|
+
this._phase,
|
|
175
|
+
"setup",
|
|
176
|
+
),
|
|
177
|
+
};
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
private handleGoAway(
|
|
181
|
+
message: Draft17Message,
|
|
182
|
+
sideEffects: SideEffect[],
|
|
183
|
+
): TransitionResult<Draft17MessageType> {
|
|
184
|
+
if (this._phase !== "ready" && this._phase !== "draining") {
|
|
185
|
+
return {
|
|
186
|
+
ok: false,
|
|
187
|
+
violation: violation(
|
|
188
|
+
"UNEXPECTED_MESSAGE",
|
|
189
|
+
`GOAWAY not valid in phase ${this._phase}`,
|
|
190
|
+
this._phase,
|
|
191
|
+
"goaway",
|
|
192
|
+
),
|
|
193
|
+
};
|
|
194
|
+
}
|
|
195
|
+
this._phase = "draining";
|
|
196
|
+
const goaway = message as import("./types.js").Draft17GoAway;
|
|
197
|
+
sideEffects.push({ type: "session-draining", goAwayUri: goaway.new_session_uri });
|
|
198
|
+
return { ok: true, phase: this._phase, sideEffects };
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
private requireReady(msgType: Draft17MessageType): ProtocolViolation<Draft17MessageType> | null {
|
|
202
|
+
if (this._phase !== "ready" && this._phase !== "draining") {
|
|
203
|
+
return violation(
|
|
204
|
+
this._phase === "idle" || this._phase === "setup"
|
|
205
|
+
? "MESSAGE_BEFORE_SETUP"
|
|
206
|
+
: "UNEXPECTED_MESSAGE",
|
|
207
|
+
`${msgType} requires ready phase, current: ${this._phase}`,
|
|
208
|
+
this._phase,
|
|
209
|
+
msgType,
|
|
210
|
+
);
|
|
211
|
+
}
|
|
212
|
+
return null;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
private handleSubscribe(
|
|
216
|
+
message: Draft17Message,
|
|
217
|
+
sideEffects: SideEffect[],
|
|
218
|
+
): TransitionResult<Draft17MessageType> {
|
|
219
|
+
const err = this.requireReady(message.type);
|
|
220
|
+
if (err) return { ok: false, violation: err };
|
|
221
|
+
const sub = message as import("./types.js").Draft17Subscribe;
|
|
222
|
+
const dupErr = this.checkDuplicateRequestId(sub.request_id, message.type);
|
|
223
|
+
if (dupErr) return { ok: false, violation: dupErr };
|
|
224
|
+
this._requestIds.add(sub.request_id);
|
|
225
|
+
this._subscriptions.set(sub.request_id, {
|
|
226
|
+
subscribeId: sub.request_id,
|
|
227
|
+
phase: "pending",
|
|
228
|
+
trackNamespace: sub.track_namespace,
|
|
229
|
+
trackName: sub.track_name,
|
|
230
|
+
});
|
|
231
|
+
this._pendingSubscribes.push(sub.request_id);
|
|
232
|
+
return { ok: true, phase: this._phase, sideEffects };
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
private handleSubscribeOk(
|
|
236
|
+
_message: Draft17Message,
|
|
237
|
+
sideEffects: SideEffect[],
|
|
238
|
+
): TransitionResult<Draft17MessageType> {
|
|
239
|
+
const err = this.requireReady("subscribe_ok");
|
|
240
|
+
if (err) return { ok: false, violation: err };
|
|
241
|
+
const requestId = this._pendingSubscribes.shift();
|
|
242
|
+
if (requestId === undefined) {
|
|
243
|
+
return {
|
|
244
|
+
ok: false,
|
|
245
|
+
violation: violation(
|
|
246
|
+
"UNEXPECTED_MESSAGE",
|
|
247
|
+
"SUBSCRIBE_OK with no pending subscribe",
|
|
248
|
+
this._phase,
|
|
249
|
+
"subscribe_ok",
|
|
250
|
+
),
|
|
251
|
+
};
|
|
252
|
+
}
|
|
253
|
+
const existing = this._subscriptions.get(requestId);
|
|
254
|
+
if (existing && existing.phase === "pending") {
|
|
255
|
+
this._subscriptions.set(requestId, { ...existing, phase: "active" });
|
|
256
|
+
sideEffects.push({ type: "subscription-activated", subscribeId: requestId });
|
|
257
|
+
}
|
|
258
|
+
return { ok: true, phase: this._phase, sideEffects };
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
private handleRequestUpdate(
|
|
262
|
+
message: Draft17Message,
|
|
263
|
+
sideEffects: SideEffect[],
|
|
264
|
+
): TransitionResult<Draft17MessageType> {
|
|
265
|
+
const err = this.requireReady(message.type);
|
|
266
|
+
if (err) return { ok: false, violation: err };
|
|
267
|
+
const update = message as import("./types.js").Draft17RequestUpdate;
|
|
268
|
+
const dupErr = this.checkDuplicateRequestId(update.request_id, message.type);
|
|
269
|
+
if (dupErr) return { ok: false, violation: dupErr };
|
|
270
|
+
this._requestIds.add(update.request_id);
|
|
271
|
+
return { ok: true, phase: this._phase, sideEffects };
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
private handlePublish(
|
|
275
|
+
message: Draft17Message,
|
|
276
|
+
sideEffects: SideEffect[],
|
|
277
|
+
): TransitionResult<Draft17MessageType> {
|
|
278
|
+
const err = this.requireReady(message.type);
|
|
279
|
+
if (err) return { ok: false, violation: err };
|
|
280
|
+
const pub = message as import("./types.js").Draft17Publish;
|
|
281
|
+
const dupErr = this.checkDuplicateRequestId(pub.request_id, message.type);
|
|
282
|
+
if (dupErr) return { ok: false, violation: dupErr };
|
|
283
|
+
this._requestIds.add(pub.request_id);
|
|
284
|
+
this._publishes.set(pub.request_id, { requestId: pub.request_id, phase: "pending" });
|
|
285
|
+
this._pendingPublishes.push(pub.request_id);
|
|
286
|
+
return { ok: true, phase: this._phase, sideEffects };
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
private handlePublishOk(sideEffects: SideEffect[]): TransitionResult<Draft17MessageType> {
|
|
290
|
+
const err = this.requireReady("publish_ok");
|
|
291
|
+
if (err) return { ok: false, violation: err };
|
|
292
|
+
const requestId = this._pendingPublishes.shift();
|
|
293
|
+
if (requestId === undefined) {
|
|
294
|
+
return {
|
|
295
|
+
ok: false,
|
|
296
|
+
violation: violation(
|
|
297
|
+
"UNEXPECTED_MESSAGE",
|
|
298
|
+
"PUBLISH_OK with no pending publish",
|
|
299
|
+
this._phase,
|
|
300
|
+
"publish_ok",
|
|
301
|
+
),
|
|
302
|
+
};
|
|
303
|
+
}
|
|
304
|
+
const existing = this._publishes.get(requestId);
|
|
305
|
+
if (existing && existing.phase === "pending") {
|
|
306
|
+
this._publishes.set(requestId, { ...existing, phase: "active" });
|
|
307
|
+
sideEffects.push({ type: "publish-activated", requestId });
|
|
308
|
+
}
|
|
309
|
+
return { ok: true, phase: this._phase, sideEffects };
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
private handlePublishDone(
|
|
313
|
+
_message: Draft17Message,
|
|
314
|
+
sideEffects: SideEffect[],
|
|
315
|
+
): TransitionResult<Draft17MessageType> {
|
|
316
|
+
const err = this.requireReady("publish_done");
|
|
317
|
+
if (err) return { ok: false, violation: err };
|
|
318
|
+
// PUBLISH_DONE has no request_id in draft-17; dequeue oldest active publish
|
|
319
|
+
for (const [reqId, pub] of this._publishes) {
|
|
320
|
+
if (pub.phase === "active") {
|
|
321
|
+
this._publishes.set(reqId, { ...pub, phase: "done" });
|
|
322
|
+
sideEffects.push({ type: "publish-ended", requestId: reqId, reason: "done" });
|
|
323
|
+
break;
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
return { ok: true, phase: this._phase, sideEffects };
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
private handleFetch(
|
|
330
|
+
message: Draft17Message,
|
|
331
|
+
sideEffects: SideEffect[],
|
|
332
|
+
): TransitionResult<Draft17MessageType> {
|
|
333
|
+
const err = this.requireReady(message.type);
|
|
334
|
+
if (err) return { ok: false, violation: err };
|
|
335
|
+
const fetch = message as import("./types.js").Draft17Fetch;
|
|
336
|
+
const dupErr = this.checkDuplicateRequestId(fetch.request_id, message.type);
|
|
337
|
+
if (dupErr) return { ok: false, violation: dupErr };
|
|
338
|
+
this._requestIds.add(fetch.request_id);
|
|
339
|
+
this._fetches.set(fetch.request_id, { requestId: fetch.request_id, phase: "pending" });
|
|
340
|
+
this._pendingFetches.push(fetch.request_id);
|
|
341
|
+
return { ok: true, phase: this._phase, sideEffects };
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
private handleFetchOk(sideEffects: SideEffect[]): TransitionResult<Draft17MessageType> {
|
|
345
|
+
const err = this.requireReady("fetch_ok");
|
|
346
|
+
if (err) return { ok: false, violation: err };
|
|
347
|
+
const requestId = this._pendingFetches.shift();
|
|
348
|
+
if (requestId === undefined) {
|
|
349
|
+
return {
|
|
350
|
+
ok: false,
|
|
351
|
+
violation: violation(
|
|
352
|
+
"UNEXPECTED_MESSAGE",
|
|
353
|
+
"FETCH_OK with no pending fetch",
|
|
354
|
+
this._phase,
|
|
355
|
+
"fetch_ok",
|
|
356
|
+
),
|
|
357
|
+
};
|
|
358
|
+
}
|
|
359
|
+
const existing = this._fetches.get(requestId);
|
|
360
|
+
if (existing && existing.phase === "pending") {
|
|
361
|
+
this._fetches.set(requestId, { ...existing, phase: "active" });
|
|
362
|
+
sideEffects.push({ type: "fetch-activated", requestId });
|
|
363
|
+
}
|
|
364
|
+
return { ok: true, phase: this._phase, sideEffects };
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
private handleRequestError(sideEffects: SideEffect[]): TransitionResult<Draft17MessageType> {
|
|
368
|
+
const err = this.requireReady("request_error");
|
|
369
|
+
if (err) return { ok: false, violation: err };
|
|
370
|
+
// REQUEST_ERROR can target any pending request — try FIFO dequeue from each queue
|
|
371
|
+
const subId = this.dequeuePending(this._pendingSubscribes, this._subscriptions);
|
|
372
|
+
if (subId !== undefined) {
|
|
373
|
+
const sub = this._subscriptions.get(subId)!;
|
|
374
|
+
this._subscriptions.set(subId, { ...sub, phase: "error" });
|
|
375
|
+
sideEffects.push({ type: "subscription-ended", subscribeId: subId, reason: "request_error" });
|
|
376
|
+
return { ok: true, phase: this._phase, sideEffects };
|
|
377
|
+
}
|
|
378
|
+
const pubId = this.dequeuePending(this._pendingPublishes, this._publishes);
|
|
379
|
+
if (pubId !== undefined) {
|
|
380
|
+
const pub = this._publishes.get(pubId)!;
|
|
381
|
+
this._publishes.set(pubId, { ...pub, phase: "error" });
|
|
382
|
+
sideEffects.push({ type: "publish-ended", requestId: pubId, reason: "request_error" });
|
|
383
|
+
return { ok: true, phase: this._phase, sideEffects };
|
|
384
|
+
}
|
|
385
|
+
const fetchId = this.dequeuePending(this._pendingFetches, this._fetches);
|
|
386
|
+
if (fetchId !== undefined) {
|
|
387
|
+
const f = this._fetches.get(fetchId)!;
|
|
388
|
+
this._fetches.set(fetchId, { ...f, phase: "error" });
|
|
389
|
+
sideEffects.push({ type: "fetch-ended", requestId: fetchId, reason: "request_error" });
|
|
390
|
+
return { ok: true, phase: this._phase, sideEffects };
|
|
391
|
+
}
|
|
392
|
+
// Could be for subscribe_namespace, publish_namespace, track_status — allow through
|
|
393
|
+
return { ok: true, phase: this._phase, sideEffects };
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
private handleRequestOk(sideEffects: SideEffect[]): TransitionResult<Draft17MessageType> {
|
|
397
|
+
const err = this.requireReady("request_ok");
|
|
398
|
+
if (err) return { ok: false, violation: err };
|
|
399
|
+
// REQUEST_OK is for subscribe_namespace, publish_namespace, track_status — allow through
|
|
400
|
+
return { ok: true, phase: this._phase, sideEffects };
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
private dequeuePending(
|
|
404
|
+
queue: bigint[],
|
|
405
|
+
stateMap: ReadonlyMap<bigint, { phase: string }>,
|
|
406
|
+
): bigint | undefined {
|
|
407
|
+
while (queue.length > 0) {
|
|
408
|
+
const id = queue[0]!;
|
|
409
|
+
const state = stateMap.get(id);
|
|
410
|
+
if (state && state.phase === "pending") {
|
|
411
|
+
queue.shift();
|
|
412
|
+
return id;
|
|
413
|
+
}
|
|
414
|
+
// Skip non-pending entries (already resolved by type-specific handler)
|
|
415
|
+
queue.shift();
|
|
416
|
+
}
|
|
417
|
+
return undefined;
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
private handleReadyPhaseMessage(message: Draft17Message): TransitionResult<Draft17MessageType> {
|
|
421
|
+
const err = this.requireReady(message.type);
|
|
422
|
+
if (err) return { ok: false, violation: err };
|
|
423
|
+
return { ok: true, phase: this._phase, sideEffects: [] };
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
reset(): void {
|
|
427
|
+
this._phase = "idle";
|
|
428
|
+
this._setupDirection = null;
|
|
429
|
+
this._subscriptions.clear();
|
|
430
|
+
this._publishes.clear();
|
|
431
|
+
this._fetches.clear();
|
|
432
|
+
this._requestIds.clear();
|
|
433
|
+
this._pendingSubscribes.length = 0;
|
|
434
|
+
this._pendingPublishes.length = 0;
|
|
435
|
+
this._pendingFetches.length = 0;
|
|
436
|
+
}
|
|
437
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { Draft17SessionFSM } from "./session-fsm.js";
|
|
2
|
+
|
|
3
|
+
export type {
|
|
4
|
+
ProtocolViolation,
|
|
5
|
+
SessionPhase,
|
|
6
|
+
SideEffect,
|
|
7
|
+
TransitionResult,
|
|
8
|
+
ValidationResult,
|
|
9
|
+
} from "../../core/session-types.js";
|
|
10
|
+
|
|
11
|
+
export function createDraft17SessionState(role: "client" | "server"): Draft17SessionFSM {
|
|
12
|
+
return new Draft17SessionFSM(role);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export { Draft17SessionFSM };
|