request-iframe 0.0.5 → 0.1.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.
Files changed (142) hide show
  1. package/README.CN.md +54 -7
  2. package/README.md +64 -11
  3. package/esm/api/client.js +79 -0
  4. package/esm/api/server.js +59 -0
  5. package/esm/constants/index.js +257 -0
  6. package/esm/constants/messages.js +155 -0
  7. package/esm/core/client-server.js +329 -0
  8. package/esm/core/client.js +873 -0
  9. package/esm/core/request.js +27 -0
  10. package/esm/core/response.js +451 -0
  11. package/esm/core/server.js +767 -0
  12. package/esm/index.js +21 -0
  13. package/esm/interceptors/index.js +122 -0
  14. package/esm/message/channel.js +181 -0
  15. package/esm/message/dispatcher.js +380 -0
  16. package/esm/message/index.js +2 -0
  17. package/esm/stream/file-stream.js +289 -0
  18. package/esm/stream/index.js +44 -0
  19. package/esm/stream/readable-stream.js +500 -0
  20. package/esm/stream/stream-core.js +91 -0
  21. package/esm/stream/types.js +1 -0
  22. package/esm/stream/writable-stream.js +582 -0
  23. package/esm/types/index.js +1 -0
  24. package/esm/utils/ack-meta.js +53 -0
  25. package/esm/utils/cache.js +147 -0
  26. package/esm/utils/cookie.js +352 -0
  27. package/esm/utils/debug.js +521 -0
  28. package/esm/utils/error.js +27 -0
  29. package/esm/utils/index.js +178 -0
  30. package/esm/utils/origin.js +28 -0
  31. package/esm/utils/path-match.js +148 -0
  32. package/esm/utils/protocol.js +157 -0
  33. package/library/api/client.d.ts.map +1 -1
  34. package/library/api/client.js +8 -1
  35. package/library/api/server.d.ts.map +1 -1
  36. package/library/api/server.js +4 -1
  37. package/library/constants/index.d.ts +25 -1
  38. package/library/constants/index.d.ts.map +1 -1
  39. package/library/constants/index.js +30 -5
  40. package/library/constants/messages.d.ts +5 -0
  41. package/library/constants/messages.d.ts.map +1 -1
  42. package/library/constants/messages.js +5 -0
  43. package/library/core/client-server.d.ts +3 -2
  44. package/library/core/client-server.d.ts.map +1 -1
  45. package/library/core/client-server.js +51 -4
  46. package/library/core/client.d.ts +4 -1
  47. package/library/core/client.d.ts.map +1 -1
  48. package/library/core/client.js +74 -31
  49. package/library/core/response.d.ts +21 -3
  50. package/library/core/response.d.ts.map +1 -1
  51. package/library/core/response.js +46 -6
  52. package/library/core/server.d.ts +28 -1
  53. package/library/core/server.d.ts.map +1 -1
  54. package/library/core/server.js +180 -19
  55. package/library/message/channel.d.ts +6 -0
  56. package/library/message/channel.d.ts.map +1 -1
  57. package/library/message/dispatcher.d.ts +22 -0
  58. package/library/message/dispatcher.d.ts.map +1 -1
  59. package/library/message/dispatcher.js +92 -0
  60. package/library/stream/file-stream.d.ts +4 -0
  61. package/library/stream/file-stream.d.ts.map +1 -1
  62. package/library/stream/file-stream.js +61 -33
  63. package/library/stream/index.d.ts.map +1 -1
  64. package/library/stream/index.js +2 -0
  65. package/library/stream/readable-stream.d.ts +30 -11
  66. package/library/stream/readable-stream.d.ts.map +1 -1
  67. package/library/stream/readable-stream.js +329 -73
  68. package/library/stream/stream-core.d.ts +44 -0
  69. package/library/stream/stream-core.d.ts.map +1 -0
  70. package/library/stream/stream-core.js +98 -0
  71. package/library/stream/types.d.ts +90 -3
  72. package/library/stream/types.d.ts.map +1 -1
  73. package/library/stream/writable-stream.d.ts +40 -12
  74. package/library/stream/writable-stream.d.ts.map +1 -1
  75. package/library/stream/writable-stream.js +391 -195
  76. package/library/types/index.d.ts +70 -3
  77. package/library/types/index.d.ts.map +1 -1
  78. package/library/utils/ack-meta.d.ts +2 -0
  79. package/library/utils/ack-meta.d.ts.map +1 -0
  80. package/library/utils/ack-meta.js +59 -0
  81. package/library/utils/index.d.ts +1 -0
  82. package/library/utils/index.d.ts.map +1 -1
  83. package/library/utils/index.js +16 -0
  84. package/library/utils/origin.d.ts +14 -0
  85. package/library/utils/origin.d.ts.map +1 -0
  86. package/library/utils/origin.js +34 -0
  87. package/package.json +31 -7
  88. package/react/README.md +16 -0
  89. package/react/esm/index.js +284 -0
  90. package/react/library/index.d.ts +1 -1
  91. package/react/library/index.d.ts.map +1 -1
  92. package/react/library/index.js +7 -4
  93. package/react/package.json +24 -2
  94. package/library/__tests__/channel.test.ts +0 -432
  95. package/library/__tests__/coverage-branches.test.ts +0 -356
  96. package/library/__tests__/debug.test.ts +0 -610
  97. package/library/__tests__/dispatcher.test.ts +0 -485
  98. package/library/__tests__/interceptors.test.ts +0 -146
  99. package/library/__tests__/requestIframe.test.ts +0 -5590
  100. package/library/__tests__/server.test.ts +0 -738
  101. package/library/__tests__/stream.test.ts +0 -726
  102. package/library/__tests__/utils.test.ts +0 -473
  103. package/library/api/client.d.js +0 -5
  104. package/library/api/server.d.js +0 -5
  105. package/library/constants/index.d.js +0 -36
  106. package/library/constants/messages.d.js +0 -5
  107. package/library/core/client.d.js +0 -5
  108. package/library/core/message-handler.d.ts +0 -110
  109. package/library/core/message-handler.d.ts.map +0 -1
  110. package/library/core/message-handler.js +0 -320
  111. package/library/core/request-response.d.ts +0 -59
  112. package/library/core/request-response.d.ts.map +0 -1
  113. package/library/core/request-response.js +0 -337
  114. package/library/core/request.d.js +0 -5
  115. package/library/core/response.d.js +0 -5
  116. package/library/core/server-base.d.ts +0 -86
  117. package/library/core/server-base.d.ts.map +0 -1
  118. package/library/core/server-base.js +0 -257
  119. package/library/core/server-client.d.js +0 -5
  120. package/library/core/server-client.d.ts +0 -101
  121. package/library/core/server-client.d.ts.map +0 -1
  122. package/library/core/server-client.js +0 -266
  123. package/library/core/server.d.js +0 -5
  124. package/library/interceptors/index.d.js +0 -5
  125. package/library/message/channel.d.js +0 -5
  126. package/library/message/dispatcher.d.js +0 -5
  127. package/library/message/index.d.js +0 -25
  128. package/library/stream/file-stream.d.js +0 -4
  129. package/library/stream/index.d.js +0 -58
  130. package/library/stream/readable-stream.d.js +0 -5
  131. package/library/stream/types.d.js +0 -5
  132. package/library/stream/writable-stream.d.js +0 -5
  133. package/library/types/index.d.js +0 -5
  134. package/library/utils/cache.d.js +0 -5
  135. package/library/utils/cookie.d.js +0 -5
  136. package/library/utils/debug.d.js +0 -5
  137. package/library/utils/index.d.js +0 -94
  138. package/library/utils/path-match.d.js +0 -5
  139. package/library/utils/protocol.d.js +0 -5
  140. package/react/library/__tests__/index.test.d.ts +0 -2
  141. package/react/library/__tests__/index.test.d.ts.map +0 -1
  142. package/react/library/__tests__/index.test.tsx +0 -792
@@ -0,0 +1,767 @@
1
+ import _slicedToArray from "@babel/runtime/helpers/esm/slicedToArray";
2
+ import _defineProperty from "@babel/runtime/helpers/esm/defineProperty";
3
+ function _createForOfIteratorHelper(r, e) { var t = "undefined" != typeof Symbol && r[Symbol.iterator] || r["@@iterator"]; if (!t) { if (Array.isArray(r) || (t = _unsupportedIterableToArray(r)) || e && r && "number" == typeof r.length) { t && (r = t); var _n = 0, F = function F() {}; return { s: F, n: function n() { return _n >= r.length ? { done: !0 } : { done: !1, value: r[_n++] }; }, e: function e(r) { throw r; }, f: F }; } throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); } var o, a = !0, u = !1; return { s: function s() { t = t.call(r); }, n: function n() { var r = t.next(); return a = r.done, r; }, e: function e(r) { u = !0, o = r; }, f: function f() { try { a || null == t.return || t.return(); } finally { if (u) throw o; } } }; }
4
+ function _unsupportedIterableToArray(r, a) { if (r) { if ("string" == typeof r) return _arrayLikeToArray(r, a); var t = {}.toString.call(r).slice(8, -1); return "Object" === t && r.constructor && (t = r.constructor.name), "Map" === t || "Set" === t ? Array.from(r) : "Arguments" === t || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(t) ? _arrayLikeToArray(r, a) : void 0; } }
5
+ function _arrayLikeToArray(r, a) { (null == a || a > r.length) && (a = r.length); for (var e = 0, n = Array(a); e < a; e++) n[e] = r[e]; return n; }
6
+ function ownKeys(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbols) { var o = Object.getOwnPropertySymbols(e); r && (o = o.filter(function (r) { return Object.getOwnPropertyDescriptor(e, r).enumerable; })), t.push.apply(t, o); } return t; }
7
+ function _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? ownKeys(Object(t), !0).forEach(function (r) { _defineProperty(e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; }
8
+ import "core-js/modules/es.array.includes.js";
9
+ import "core-js/modules/es.symbol.description.js";
10
+ import "core-js/modules/es.array.filter.js";
11
+ import "core-js/modules/es.array.from.js";
12
+ import "core-js/modules/es.array.iterator.js";
13
+ import "core-js/modules/es.array.slice.js";
14
+ import "core-js/modules/es.map.js";
15
+ import "core-js/modules/es.object.entries.js";
16
+ import "core-js/modules/es.promise.js";
17
+ import "core-js/modules/es.object.get-own-property-descriptors.js";
18
+ import "core-js/modules/es.regexp.exec.js";
19
+ import "core-js/modules/es.string.includes.js";
20
+ import "core-js/modules/es.string.match.js";
21
+ import "core-js/modules/es.regexp.to-string.js";
22
+ import "core-js/modules/es.string.replace.js";
23
+ import "core-js/modules/es.string.starts-with.js";
24
+ import "core-js/modules/web.dom-collections.for-each.js";
25
+ import "core-js/modules/web.dom-collections.iterator.js";
26
+ import { isCompatibleVersion } from '../utils';
27
+ import { matchPath, matchPathWithParams } from '../utils/path-match';
28
+ import { matchOrigin } from '../utils/origin';
29
+ import { ServerRequestImpl } from './request';
30
+ import { ServerResponseImpl } from './response';
31
+ import { MessageDispatcher } from '../message';
32
+ import { getOrCreateMessageChannel, releaseMessageChannel } from '../utils/cache';
33
+ import { generateInstanceId, generateRequestId } from '../utils';
34
+ import { MessageType, ErrorCode, HttpStatus, HttpStatusText, Messages, DefaultTimeout, ProtocolVersion, formatMessage, MessageRole, StreamType as StreamTypeConstant } from '../constants';
35
+ import { isPromise } from '../utils';
36
+ import { IframeReadableStream, IframeFileReadableStream } from '../stream';
37
+
38
+ /**
39
+ * Middleware item (contains path matcher and middleware function)
40
+ */
41
+
42
+ /**
43
+ * Pending acknowledgment
44
+ */
45
+
46
+ /** Pending request waiting for client stream (streamId present) */
47
+
48
+ /**
49
+ * Server configuration options
50
+ */
51
+
52
+ /**
53
+ * RequestIframeServer implementation
54
+ * Uses shared MessageDispatcher (backed by MessageChannel) to listen for and send messages
55
+ */
56
+ export class RequestIframeServerImpl {
57
+ constructor(options) {
58
+ var _options$ackTimeout, _options$versionValid, _options$maxConcurren;
59
+ /** Unique instance ID */
60
+ _defineProperty(this, "handlers", new Map());
61
+ _defineProperty(this, "middlewares", []);
62
+ _defineProperty(this, "inFlightByClientKey", new Map());
63
+ /** Responses waiting for client acknowledgment */
64
+ _defineProperty(this, "pendingAcks", new Map());
65
+ /** Pending pings waiting for client PONG (server -> client heartbeat) */
66
+ _defineProperty(this, "pendingPongs", new Map());
67
+ /** Pending requests waiting for client stream_start (streamId present) */
68
+ _defineProperty(this, "pendingStreamRequests", new Map());
69
+ /** Stream message handlers (streamId -> handler) for client→server streams */
70
+ _defineProperty(this, "streamHandlers", new Map());
71
+ /** List of functions to unregister handlers */
72
+ _defineProperty(this, "unregisterFns", []);
73
+ /** Whether it is open */
74
+ _defineProperty(this, "_isOpen", false);
75
+ // Use custom id if provided, otherwise generate one
76
+ this.id = (options === null || options === void 0 ? void 0 : options.id) || generateInstanceId();
77
+ this.ackTimeout = (_options$ackTimeout = options === null || options === void 0 ? void 0 : options.ackTimeout) !== null && _options$ackTimeout !== void 0 ? _options$ackTimeout : DefaultTimeout.ACK;
78
+ this.versionValidator = (_options$versionValid = options === null || options === void 0 ? void 0 : options.versionValidator) !== null && _options$versionValid !== void 0 ? _options$versionValid : isCompatibleVersion;
79
+ this.maxConcurrentRequestsPerClient = (_options$maxConcurren = options === null || options === void 0 ? void 0 : options.maxConcurrentRequestsPerClient) !== null && _options$maxConcurren !== void 0 ? _options$maxConcurren : Number.POSITIVE_INFINITY;
80
+
81
+ // Build origin validator (incoming messages)
82
+ if (options !== null && options !== void 0 && options.validateOrigin) {
83
+ this.originValidator = (origin, data, context) => options.validateOrigin(origin, data, context);
84
+ } else if (options !== null && options !== void 0 && options.allowedOrigins) {
85
+ var matcher = options.allowedOrigins;
86
+ this.originValidator = origin => matchOrigin(origin, matcher);
87
+ }
88
+
89
+ // Get or create shared channel and create dispatcher
90
+ var channel = getOrCreateMessageChannel(options === null || options === void 0 ? void 0 : options.secretKey);
91
+ this.dispatcher = new MessageDispatcher(channel, MessageRole.SERVER, this.id);
92
+
93
+ // Auto-open by default (unless explicitly set to false)
94
+ if ((options === null || options === void 0 ? void 0 : options.autoOpen) !== false) {
95
+ this.open();
96
+ }
97
+ }
98
+
99
+ /**
100
+ * Check whether an incoming message origin is allowed.
101
+ */
102
+ isOriginAllowed(data, context) {
103
+ if (!this.originValidator) return true;
104
+ try {
105
+ return this.originValidator(context.origin, data, context);
106
+ } catch (_unused) {
107
+ return false;
108
+ }
109
+ }
110
+
111
+ /**
112
+ * Build a per-client key used for concurrency limiting.
113
+ * We intentionally include origin to prevent cross-origin collisions.
114
+ */
115
+ getClientKey(origin, creatorId) {
116
+ return `${origin}::${creatorId || 'unknown'}`;
117
+ }
118
+ incInFlight(clientKey) {
119
+ var current = this.inFlightByClientKey.get(clientKey) || 0;
120
+ this.inFlightByClientKey.set(clientKey, current + 1);
121
+ }
122
+ decInFlight(clientKey) {
123
+ var current = this.inFlightByClientKey.get(clientKey) || 0;
124
+ var next = current - 1;
125
+ if (next <= 0) {
126
+ this.inFlightByClientKey.delete(clientKey);
127
+ return;
128
+ }
129
+ this.inFlightByClientKey.set(clientKey, next);
130
+ }
131
+
132
+ /**
133
+ * Open message processing (register message handlers)
134
+ */
135
+ open() {
136
+ if (this._isOpen) return;
137
+ this._isOpen = true;
138
+ this.registerHandlers();
139
+ }
140
+
141
+ /**
142
+ * Close message processing (unregister message handlers, but don't release channel)
143
+ */
144
+ close() {
145
+ if (!this._isOpen) return;
146
+ this._isOpen = false;
147
+
148
+ // Unregister all handlers
149
+ this.unregisterFns.forEach(fn => fn());
150
+ this.unregisterFns.length = 0;
151
+ }
152
+
153
+ /**
154
+ * Whether it is open
155
+ */
156
+ get isOpen() {
157
+ return this._isOpen;
158
+ }
159
+
160
+ /**
161
+ * Register message handlers
162
+ */
163
+ registerHandlers() {
164
+ var handlerOptions = {
165
+ versionValidator: this.versionValidator,
166
+ onVersionError: this.handleVersionError.bind(this)
167
+ };
168
+
169
+ // Handle REQUEST messages
170
+ this.unregisterFns.push(this.dispatcher.registerHandler(MessageType.REQUEST, (data, context) => this.handleRequest(data, context), handlerOptions));
171
+
172
+ // Handle PING messages
173
+ this.unregisterFns.push(this.dispatcher.registerHandler(MessageType.PING, (data, context) => this.handlePing(data, context), handlerOptions));
174
+
175
+ // Handle PONG messages (server -> client heartbeat)
176
+ this.unregisterFns.push(this.dispatcher.registerHandler(MessageType.PONG, (data, context) => this.handlePong(data, context), handlerOptions));
177
+
178
+ // Handle RECEIVED messages (for confirming response delivery)
179
+ this.unregisterFns.push(this.dispatcher.registerHandler(MessageType.RECEIVED, (data, context) => this.handleReceived(data, context), handlerOptions));
180
+
181
+ // Handle stream_* messages (client→server stream)
182
+ this.unregisterFns.push(this.dispatcher.registerHandler(MessageType.STREAM_START, (data, ctx) => this.handleStreamStart(data, ctx), handlerOptions));
183
+ this.unregisterFns.push(this.dispatcher.registerHandler(type => type.startsWith('stream_') && type !== MessageType.STREAM_START, (data, context) => this.dispatchStreamMessage(data, context), handlerOptions));
184
+ }
185
+
186
+ /** Handle stream_start from client (stream request with streamId) */
187
+ handleStreamStart(data, context) {
188
+ var _body$chunked;
189
+ if (data.role !== MessageRole.CLIENT) return;
190
+ if (!this.isOriginAllowed(data, context)) return;
191
+ var body = data.body;
192
+ if (!(body !== null && body !== void 0 && body.streamId)) return;
193
+ var pending = this.pendingStreamRequests.get(data.requestId);
194
+ if (!pending || pending.streamId !== body.streamId) return;
195
+ clearTimeout(pending.timeoutId);
196
+ this.pendingStreamRequests.delete(data.requestId);
197
+ var targetWindow = pending.targetWindow,
198
+ targetOrigin = pending.targetOrigin,
199
+ res = pending.res,
200
+ reqData = pending.data,
201
+ reqContext = pending.context,
202
+ handlerFn = pending.handlerFn;
203
+ var streamHandler = {
204
+ registerStreamHandler: (streamId, handler) => {
205
+ this.streamHandlers.set(streamId, handler);
206
+ },
207
+ unregisterStreamHandler: streamId => {
208
+ this.streamHandlers.delete(streamId);
209
+ },
210
+ postMessage: message => {
211
+ this.dispatcher.send(targetWindow, message, targetOrigin);
212
+ }
213
+ };
214
+ var streamType = body.type || StreamTypeConstant.DATA;
215
+ var streamMode = body.mode;
216
+ var streamChunked = (_body$chunked = body.chunked) !== null && _body$chunked !== void 0 ? _body$chunked : true;
217
+ var streamMetadata = body.metadata;
218
+ var req = new ServerRequestImpl(reqData, reqContext, res, pending.params);
219
+
220
+ // File stream: optionally auto-resolve to File/Blob before calling handler
221
+ if (streamType === StreamTypeConstant.FILE) {
222
+ var _body$autoResolve;
223
+ var fileStream = new IframeFileReadableStream(body.streamId, data.requestId, streamHandler, {
224
+ chunked: streamChunked,
225
+ metadata: streamMetadata,
226
+ secretKey: data.secretKey,
227
+ mode: streamMode,
228
+ filename: streamMetadata === null || streamMetadata === void 0 ? void 0 : streamMetadata.filename,
229
+ mimeType: streamMetadata === null || streamMetadata === void 0 ? void 0 : streamMetadata.mimeType,
230
+ size: streamMetadata === null || streamMetadata === void 0 ? void 0 : streamMetadata.size
231
+ });
232
+ var autoResolve = (_body$autoResolve = body.autoResolve) !== null && _body$autoResolve !== void 0 ? _body$autoResolve : false;
233
+ if (autoResolve) {
234
+ var name = fileStream.filename || (streamMetadata === null || streamMetadata === void 0 ? void 0 : streamMetadata.filename);
235
+ var promise = name ? fileStream.readAsFile(name) : fileStream.readAsBlob();
236
+ promise.then(fileData => {
237
+ req.body = fileData;
238
+ req.stream = undefined;
239
+ this.runMiddlewares(req, res, () => {
240
+ try {
241
+ var result = handlerFn(req, res);
242
+ if (isPromise(result)) {
243
+ // Window check is handled in MessageDispatcher
244
+ this.dispatcher.sendMessage(targetWindow, targetOrigin, MessageType.ASYNC, data.requestId, {
245
+ path: reqData.path,
246
+ targetId: data.creatorId
247
+ });
248
+ result.then(this.handleRequestResult.bind(this, res, targetWindow, targetOrigin, reqData)).catch(this.handleRequestError.bind(this, res, targetWindow, targetOrigin, reqData));
249
+ } else {
250
+ this.handleRequestResult(res, targetWindow, targetOrigin, reqData, result);
251
+ }
252
+ } catch (error) {
253
+ this.handleRequestError(res, targetWindow, targetOrigin, reqData, error);
254
+ }
255
+ });
256
+ }).catch(error => {
257
+ this.handleRequestError(res, targetWindow, targetOrigin, reqData, error);
258
+ });
259
+ return;
260
+ }
261
+
262
+ // Non-autoResolve: expose stream directly
263
+ req.body = fileStream;
264
+ req.stream = fileStream;
265
+ } else {
266
+ // Non-file stream
267
+ var readableStream = new IframeReadableStream(body.streamId, data.requestId, streamHandler, {
268
+ type: streamType,
269
+ mode: streamMode,
270
+ chunked: streamChunked,
271
+ metadata: streamMetadata,
272
+ secretKey: data.secretKey
273
+ });
274
+ req.body = undefined;
275
+ req.stream = readableStream;
276
+ }
277
+ this.runMiddlewares(req, res, () => {
278
+ try {
279
+ var result = handlerFn(req, res);
280
+ if (isPromise(result)) {
281
+ // Window check is handled in MessageDispatcher
282
+ this.dispatcher.sendMessage(targetWindow, targetOrigin, MessageType.ASYNC, data.requestId, {
283
+ path: reqData.path,
284
+ targetId: data.creatorId
285
+ });
286
+ result.then(this.handleRequestResult.bind(this, res, targetWindow, targetOrigin, reqData)).catch(this.handleRequestError.bind(this, res, targetWindow, targetOrigin, reqData));
287
+ } else {
288
+ this.handleRequestResult(res, targetWindow, targetOrigin, reqData, result);
289
+ }
290
+ } catch (error) {
291
+ this.handleRequestError(res, targetWindow, targetOrigin, reqData, error);
292
+ }
293
+ });
294
+ }
295
+ dispatchStreamMessage(data, context) {
296
+ if (!this.isOriginAllowed(data, context)) return;
297
+ var body = data.body;
298
+ if (!(body !== null && body !== void 0 && body.streamId)) return;
299
+ var handler = this.streamHandlers.get(body.streamId);
300
+ if (handler) {
301
+ var messageType = data.type.replace('stream_', '');
302
+ handler(_objectSpread(_objectSpread({}, body), {}, {
303
+ type: messageType
304
+ }));
305
+ }
306
+ }
307
+
308
+ /**
309
+ * Handle protocol version error
310
+ */
311
+ handleVersionError(data, context, version) {
312
+ if (!context.source) return;
313
+
314
+ // Send protocol version incompatibility error
315
+ // Window check is handled in MessageDispatcher
316
+ this.dispatcher.sendMessage(context.source, context.origin, MessageType.ERROR, data.requestId, {
317
+ path: data.path,
318
+ status: HttpStatus.BAD_REQUEST,
319
+ statusText: Messages.PROTOCOL_VERSION_UNSUPPORTED,
320
+ error: {
321
+ message: formatMessage(Messages.PROTOCOL_VERSION_TOO_LOW, version, ProtocolVersion.MIN_SUPPORTED),
322
+ code: ErrorCode.PROTOCOL_UNSUPPORTED
323
+ }
324
+ });
325
+ }
326
+
327
+ /**
328
+ * Handle ping message
329
+ */
330
+ handlePing(data, context) {
331
+ if (!context.source) return;
332
+ if (!this.isOriginAllowed(data, context)) return;
333
+
334
+ /**
335
+ * Only allow one server instance to respond.
336
+ * This is important when multiple server instances share the same channel.
337
+ */
338
+ if (!context.handledBy) {
339
+ // Mark as accepted so MessageDispatcher can auto-send ACK when requireAck === true
340
+ context.accepted = true;
341
+ context.handledBy = this.id;
342
+ }
343
+
344
+ // Window check is handled in MessageDispatcher
345
+ this.dispatcher.sendMessage(context.source, context.origin, MessageType.PONG, data.requestId);
346
+ }
347
+ handlePong(data, context) {
348
+ if (!this.isOriginAllowed(data, context)) return;
349
+ var pending = this.pendingPongs.get(data.requestId);
350
+ if (pending) {
351
+ if (!context.handledBy) {
352
+ context.accepted = true;
353
+ context.handledBy = this.id;
354
+ }
355
+ clearTimeout(pending.timeoutId);
356
+ this.pendingPongs.delete(data.requestId);
357
+ pending.resolve(true);
358
+ }
359
+ }
360
+ pingClient(targetWindow, targetOrigin, targetClientId) {
361
+ var requestId = generateRequestId();
362
+ return new Promise(resolve => {
363
+ var timeoutId = setTimeout(() => {
364
+ this.pendingPongs.delete(requestId);
365
+ resolve(false);
366
+ }, this.ackTimeout);
367
+ this.pendingPongs.set(requestId, {
368
+ resolve,
369
+ timeoutId
370
+ });
371
+ // Window check is handled in MessageDispatcher
372
+ this.dispatcher.sendMessage(targetWindow, targetOrigin, MessageType.PING, requestId, {
373
+ requireAck: true,
374
+ targetId: targetClientId
375
+ });
376
+ });
377
+ }
378
+
379
+ /**
380
+ * Handle received acknowledgment
381
+ */
382
+ handleReceived(data, context) {
383
+ if (!this.isOriginAllowed(data, context)) return;
384
+ var pending = this.pendingAcks.get(data.requestId);
385
+ if (pending) {
386
+ // Best-effort: prevent other server instances from also resolving
387
+ if (!context.handledBy) {
388
+ context.handledBy = this.id;
389
+ }
390
+ clearTimeout(pending.timeoutId);
391
+ this.pendingAcks.delete(data.requestId);
392
+ pending.resolve(true, data.ackMeta);
393
+ }
394
+ }
395
+
396
+ /** Get secretKey */
397
+ get secretKey() {
398
+ return this.dispatcher.secretKey;
399
+ }
400
+
401
+ /** Get the underlying MessageDispatcher */
402
+ get messageDispatcher() {
403
+ return this.dispatcher;
404
+ }
405
+ handleRequestError(res, targetWindow, targetOrigin, data, err) {
406
+ if (!res._sent) {
407
+ res._markSent();
408
+ /**
409
+ * Use INTERNAL_SERVER_ERROR (500) for handler errors unless a different error status code was explicitly set.
410
+ * If statusCode is still the default OK (200), override it to INTERNAL_SERVER_ERROR.
411
+ */
412
+ var errorStatus = res.statusCode === HttpStatus.OK ? HttpStatus.INTERNAL_SERVER_ERROR : res.statusCode;
413
+
414
+ // Window check is handled in MessageDispatcher
415
+ this.dispatcher.sendMessage(targetWindow, targetOrigin, MessageType.ERROR, data.requestId, {
416
+ path: data.path,
417
+ error: {
418
+ message: err && err.message || Messages.REQUEST_FAILED,
419
+ code: err && err.code || ErrorCode.REQUEST_ERROR
420
+ },
421
+ status: errorStatus,
422
+ statusText: HttpStatusText[errorStatus] || HttpStatusText[HttpStatus.INTERNAL_SERVER_ERROR],
423
+ headers: res.headers,
424
+ targetId: data.creatorId
425
+ });
426
+ }
427
+ }
428
+ handleRequestResult(res, targetWindow, targetOrigin, data, result) {
429
+ if (!res._sent && result !== undefined) {
430
+ res.send(result);
431
+ } else if (!res._sent) {
432
+ res._markSent();
433
+ this.dispatcher.sendMessage(targetWindow, targetOrigin, MessageType.ERROR, data.requestId, {
434
+ path: data.path,
435
+ error: {
436
+ message: Messages.NO_RESPONSE_SENT,
437
+ code: ErrorCode.NO_RESPONSE
438
+ },
439
+ status: HttpStatus.INTERNAL_SERVER_ERROR,
440
+ statusText: HttpStatusText[HttpStatus.INTERNAL_SERVER_ERROR],
441
+ headers: res.headers
442
+ });
443
+ }
444
+ }
445
+
446
+ /**
447
+ * Find matching handler and extract path parameters
448
+ * @param requestPath The actual request path
449
+ * @returns Handler function and extracted parameters, or null if not found
450
+ */
451
+ findHandler(requestPath) {
452
+ var prefixedRequestPath = this.dispatcher.prefixPath(requestPath);
453
+
454
+ // First try exact match (for backward compatibility and performance)
455
+ var exactHandler = this.handlers.get(prefixedRequestPath);
456
+ if (exactHandler) {
457
+ return {
458
+ handler: exactHandler,
459
+ params: {}
460
+ };
461
+ }
462
+
463
+ // Then try parameter matching (e.g., '/api/users/:id' matches '/api/users/123')
464
+ var _iterator = _createForOfIteratorHelper(this.handlers.entries()),
465
+ _step;
466
+ try {
467
+ for (_iterator.s(); !(_step = _iterator.n()).done;) {
468
+ var _step$value = _slicedToArray(_step.value, 2),
469
+ registeredPath = _step$value[0],
470
+ handler = _step$value[1];
471
+ // Check if registered path contains parameters
472
+ if (registeredPath.includes(':')) {
473
+ var matchResult = matchPathWithParams(prefixedRequestPath, registeredPath);
474
+ if (matchResult.match) {
475
+ return {
476
+ handler,
477
+ params: matchResult.params
478
+ };
479
+ }
480
+ }
481
+ }
482
+ } catch (err) {
483
+ _iterator.e(err);
484
+ } finally {
485
+ _iterator.f();
486
+ }
487
+ return null;
488
+ }
489
+
490
+ /**
491
+ * Handle request
492
+ */
493
+ handleRequest(data, context) {
494
+ // If targetId is specified, only process if it matches this server's id
495
+ if (!data.path || data.targetId && data.targetId !== this.id) return;
496
+ if (!context.source) return;
497
+ if (!this.isOriginAllowed(data, context)) return;
498
+
499
+ // If message has already been handled by another server instance, skip processing
500
+ if (context.handledBy) {
501
+ return;
502
+ }
503
+ var targetWindow = context.source;
504
+ var targetOrigin = context.origin;
505
+
506
+ // Find matching handler and extract path parameters
507
+ var handlerMatch = this.findHandler(data.path);
508
+ if (!handlerMatch) {
509
+ // No handler found in this instance
510
+ // Mark as handled by this instance (using special marker) to prevent other instances from processing
511
+ // This ensures only one instance sends the error response
512
+ context.handledBy = this.id;
513
+
514
+ // Send METHOD_NOT_FOUND error
515
+ // Use request's creatorId as targetId to route back to the correct client
516
+ // Window check is handled in MessageDispatcher
517
+ this.dispatcher.sendMessage(targetWindow, targetOrigin, MessageType.ERROR, data.requestId, {
518
+ path: data.path,
519
+ error: {
520
+ message: Messages.METHOD_NOT_FOUND,
521
+ code: ErrorCode.METHOD_NOT_FOUND
522
+ },
523
+ status: HttpStatus.NOT_FOUND,
524
+ statusText: HttpStatusText[HttpStatus.NOT_FOUND],
525
+ targetId: data.creatorId
526
+ });
527
+ return;
528
+ }
529
+ var handlerFn = handlerMatch.handler,
530
+ params = handlerMatch.params;
531
+ var clientKey = this.getClientKey(targetOrigin, data.creatorId);
532
+ if (Number.isFinite(this.maxConcurrentRequestsPerClient)) {
533
+ var inFlight = this.inFlightByClientKey.get(clientKey) || 0;
534
+ if (inFlight >= this.maxConcurrentRequestsPerClient) {
535
+ // Prevent other server instances from also responding
536
+ context.handledBy = this.id;
537
+ this.dispatcher.sendMessage(targetWindow, targetOrigin, MessageType.ERROR, data.requestId, {
538
+ path: data.path,
539
+ error: {
540
+ message: formatMessage(Messages.TOO_MANY_REQUESTS, this.maxConcurrentRequestsPerClient),
541
+ code: ErrorCode.TOO_MANY_REQUESTS
542
+ },
543
+ status: HttpStatus.TOO_MANY_REQUESTS,
544
+ statusText: HttpStatusText[HttpStatus.TOO_MANY_REQUESTS],
545
+ requireAck: data.requireAck,
546
+ ackMeta: data.ackMeta,
547
+ targetId: data.creatorId
548
+ });
549
+ return;
550
+ }
551
+ }
552
+ this.incInFlight(clientKey);
553
+
554
+ // Mark as accepted so MessageDispatcher can auto-send ACK (delivery confirmation)
555
+ context.accepted = true;
556
+
557
+ // Mark message as handled by this server instance to prevent other server instances from processing it
558
+ context.handledBy = this.id;
559
+
560
+ // Create response object with channel reference
561
+ // Pass request's creatorId as targetId so responses are routed back to the correct client
562
+ var res = new ServerResponseImpl(data.requestId, data.path || '', data.secretKey, targetWindow, targetOrigin, this.dispatcher.getChannel(), this.id, data.creatorId, {
563
+ registerStreamHandler: (streamId, handler) => {
564
+ this.streamHandlers.set(streamId, handler);
565
+ },
566
+ unregisterStreamHandler: streamId => {
567
+ this.streamHandlers.delete(streamId);
568
+ },
569
+ heartbeat: () => this.pingClient(targetWindow, targetOrigin, data.creatorId),
570
+ onSent: () => this.decInFlight(clientKey)
571
+ });
572
+
573
+ // Register callback waiting for client acknowledgment
574
+ this.registerPendingAck(data.requestId, (received, ackMeta) => {
575
+ res._triggerAck(received, ackMeta);
576
+ }, () => {
577
+ res._triggerAck(false);
578
+ });
579
+
580
+ // Client sends body as stream: wait for stream_start, then create readable stream and call handler
581
+ // If streamId is present, this is a stream request
582
+ var streamId = data.streamId;
583
+ if (streamId) {
584
+ var streamStartTimeout = this.ackTimeout;
585
+ var timeoutId = setTimeout(() => {
586
+ var pending = this.pendingStreamRequests.get(data.requestId);
587
+ if (!pending) return;
588
+ this.pendingStreamRequests.delete(data.requestId);
589
+ if (!pending.res._sent) {
590
+ pending.res._markSent();
591
+ this.dispatcher.sendMessage(pending.targetWindow, pending.targetOrigin, MessageType.ERROR, pending.requestId, {
592
+ path: pending.path,
593
+ error: {
594
+ message: formatMessage(Messages.STREAM_START_TIMEOUT, streamStartTimeout),
595
+ code: ErrorCode.STREAM_START_TIMEOUT
596
+ },
597
+ status: HttpStatus.REQUEST_TIMEOUT,
598
+ statusText: HttpStatusText[HttpStatus.REQUEST_TIMEOUT],
599
+ requireAck: pending.data.requireAck,
600
+ ackMeta: pending.data.ackMeta,
601
+ targetId: pending.data.creatorId
602
+ });
603
+ }
604
+ }, streamStartTimeout);
605
+ this.pendingStreamRequests.set(data.requestId, {
606
+ path: data.path || '',
607
+ requestId: data.requestId,
608
+ streamId,
609
+ timeoutId,
610
+ handlerFn,
611
+ targetWindow,
612
+ targetOrigin,
613
+ res,
614
+ data,
615
+ context,
616
+ params
617
+ });
618
+ return;
619
+ }
620
+
621
+ // Create request object with path parameters
622
+ var req = new ServerRequestImpl(data, context, res, params);
623
+
624
+ // Execute middleware chain
625
+ this.runMiddlewares(req, res, () => {
626
+ try {
627
+ var result = handlerFn(req, res);
628
+ if (isPromise(result)) {
629
+ // Async task
630
+ // Window check is handled in MessageDispatcher
631
+ // Use request's creatorId as targetId to route back to the correct client
632
+ this.dispatcher.sendMessage(targetWindow, targetOrigin, MessageType.ASYNC, data.requestId, {
633
+ path: data.path,
634
+ targetId: data.creatorId
635
+ });
636
+ result.then(this.handleRequestResult.bind(this, res, targetWindow, targetOrigin, data)).catch(this.handleRequestError.bind(this, res, targetWindow, targetOrigin, data));
637
+ } else {
638
+ // Synchronous processing
639
+ this.handleRequestResult(res, targetWindow, targetOrigin, data, result);
640
+ }
641
+ } catch (error) {
642
+ this.handleRequestError(res, targetWindow, targetOrigin, data, error);
643
+ }
644
+ });
645
+ }
646
+
647
+ /**
648
+ * Register pending acknowledgment response
649
+ */
650
+ registerPendingAck(requestId, resolve, reject) {
651
+ var timeoutId = setTimeout(() => {
652
+ this.pendingAcks.delete(requestId);
653
+ resolve(false);
654
+ }, this.ackTimeout);
655
+ this.pendingAcks.set(requestId, {
656
+ resolve,
657
+ reject,
658
+ timeoutId
659
+ });
660
+ }
661
+ use(pathOrMiddleware, middleware) {
662
+ if (typeof pathOrMiddleware === 'function') {
663
+ this.middlewares.push({
664
+ matcher: null,
665
+ middleware: pathOrMiddleware
666
+ });
667
+ } else if (middleware) {
668
+ this.middlewares.push({
669
+ matcher: pathOrMiddleware,
670
+ middleware
671
+ });
672
+ }
673
+ }
674
+ on(path, handler) {
675
+ var prefixedPath = this.dispatcher.prefixPath(path);
676
+ this.handlers.set(prefixedPath, handler);
677
+
678
+ // Return unregister function
679
+ return () => {
680
+ this.handlers.delete(prefixedPath);
681
+ };
682
+ }
683
+ runMiddlewares(req, res, finalHandler) {
684
+ var path = req.path;
685
+ var index = 0;
686
+ var next = () => {
687
+ if (res._sent) {
688
+ return;
689
+ }
690
+ while (index < this.middlewares.length) {
691
+ var item = this.middlewares[index++];
692
+ if (item.matcher === null || matchPath(path, item.matcher)) {
693
+ try {
694
+ var result = item.middleware(req, res, next);
695
+ if (result instanceof Promise) {
696
+ result.catch(err => {
697
+ if (!res._sent) {
698
+ res.status(HttpStatus.INTERNAL_SERVER_ERROR).send({
699
+ error: err.message || Messages.MIDDLEWARE_ERROR
700
+ });
701
+ }
702
+ });
703
+ }
704
+ return;
705
+ } catch (err) {
706
+ if (!res._sent) {
707
+ res.status(HttpStatus.INTERNAL_SERVER_ERROR).send({
708
+ error: (err === null || err === void 0 ? void 0 : err.message) || Messages.MIDDLEWARE_ERROR
709
+ });
710
+ }
711
+ return;
712
+ }
713
+ }
714
+ }
715
+ if (index >= this.middlewares.length) {
716
+ finalHandler();
717
+ }
718
+ };
719
+ next();
720
+ }
721
+ off(path) {
722
+ if (Array.isArray(path)) {
723
+ // Batch unregister
724
+ path.forEach(p => {
725
+ this.handlers.delete(this.dispatcher.prefixPath(p));
726
+ });
727
+ } else {
728
+ // Single unregister
729
+ this.handlers.delete(this.dispatcher.prefixPath(path));
730
+ }
731
+ }
732
+ map(handlers) {
733
+ var unregisterFns = [];
734
+ Object.entries(handlers).forEach(([path, h]) => {
735
+ unregisterFns.push(this.on(path, h));
736
+ });
737
+ return () => {
738
+ unregisterFns.forEach(fn => fn());
739
+ };
740
+ }
741
+
742
+ /**
743
+ * Destroy (close and release channel reference)
744
+ */
745
+ destroy() {
746
+ // Close first
747
+ this.close();
748
+
749
+ // Clean up pending
750
+ this.pendingAcks.forEach(pending => clearTimeout(pending.timeoutId));
751
+ this.pendingAcks.clear();
752
+ this.pendingPongs.forEach(pending => clearTimeout(pending.timeoutId));
753
+ this.pendingPongs.clear();
754
+ this.pendingStreamRequests.forEach(pending => clearTimeout(pending.timeoutId));
755
+ this.pendingStreamRequests.clear();
756
+ this.inFlightByClientKey.clear();
757
+
758
+ // Clean up handlers
759
+ this.handlers.clear();
760
+ this.middlewares.length = 0;
761
+ this.streamHandlers.clear();
762
+
763
+ // Destroy dispatcher and release channel reference
764
+ this.dispatcher.destroy();
765
+ releaseMessageChannel(this.dispatcher.getChannel());
766
+ }
767
+ }