request-iframe 0.1.1 → 0.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/QUICKSTART.CN.md +4 -2
- package/QUICKSTART.md +4 -2
- package/README.CN.md +129 -33
- package/README.md +116 -15
- package/cdn/request-iframe-react.umd.js +3354 -0
- package/cdn/request-iframe-react.umd.js.map +1 -0
- package/cdn/request-iframe-react.umd.min.js +2 -0
- package/cdn/request-iframe-react.umd.min.js.map +1 -0
- package/cdn/request-iframe.umd.js +19761 -0
- package/cdn/request-iframe.umd.js.map +1 -0
- package/cdn/request-iframe.umd.min.js +4 -0
- package/cdn/request-iframe.umd.min.js.map +1 -0
- package/esm/api/client.js +29 -21
- package/esm/api/endpoint.js +229 -0
- package/esm/api/server.js +16 -8
- package/esm/constants/debug.js +17 -0
- package/esm/constants/index.js +84 -67
- package/esm/constants/log.js +11 -0
- package/esm/constants/messages.js +3 -0
- package/esm/constants/warn-once.js +15 -0
- package/esm/endpoint/facade.js +390 -0
- package/esm/endpoint/heartbeat/heartbeat.js +60 -0
- package/esm/endpoint/heartbeat/ping.js +20 -0
- package/esm/endpoint/index.js +13 -0
- package/esm/endpoint/infra/hub.js +316 -0
- package/esm/endpoint/infra/inbox.js +232 -0
- package/esm/endpoint/infra/outbox.js +408 -0
- package/esm/endpoint/stream/dispatcher.js +58 -0
- package/esm/endpoint/stream/errors.js +27 -0
- package/esm/endpoint/stream/factory.js +76 -0
- package/esm/endpoint/stream/file-auto-resolve.js +34 -0
- package/esm/endpoint/stream/file-writable.js +105 -0
- package/esm/endpoint/stream/handler.js +26 -0
- package/esm/{core → impl}/client.js +240 -317
- package/esm/{core → impl}/response.js +113 -155
- package/esm/impl/server.js +568 -0
- package/esm/index.js +13 -6
- package/esm/message/ack.js +27 -0
- package/esm/message/channel-cache.js +108 -0
- package/esm/message/channel.js +90 -4
- package/esm/message/dispatcher.js +115 -75
- package/esm/stream/error.js +22 -0
- package/esm/stream/index.js +3 -1
- package/esm/stream/readable-stream.js +45 -9
- package/esm/stream/stream-core.js +7 -2
- package/esm/stream/writable-stream.js +97 -26
- package/esm/utils/blob.js +16 -0
- package/esm/utils/cache.js +25 -76
- package/esm/utils/content-type.js +81 -0
- package/esm/utils/debug.js +156 -179
- package/esm/utils/hooks.js +130 -0
- package/esm/utils/id.js +14 -0
- package/esm/utils/iframe.js +20 -0
- package/esm/utils/index.js +11 -163
- package/esm/utils/is.js +3 -0
- package/esm/utils/logger.js +55 -0
- package/esm/utils/promise.js +3 -0
- package/esm/utils/window.js +31 -0
- package/library/api/client.d.ts.map +1 -1
- package/library/api/client.js +30 -22
- package/library/api/endpoint.d.ts +23 -0
- package/library/api/endpoint.d.ts.map +1 -0
- package/library/api/endpoint.js +235 -0
- package/library/api/server.d.ts +4 -1
- package/library/api/server.d.ts.map +1 -1
- package/library/api/server.js +16 -8
- package/library/constants/debug.d.ts +18 -0
- package/library/constants/debug.d.ts.map +1 -0
- package/library/constants/debug.js +23 -0
- package/library/constants/index.d.ts +22 -2
- package/library/constants/index.d.ts.map +1 -1
- package/library/constants/index.js +110 -67
- package/library/constants/log.d.ts +12 -0
- package/library/constants/log.d.ts.map +1 -0
- package/library/constants/log.js +17 -0
- package/library/constants/messages.d.ts +3 -0
- package/library/constants/messages.d.ts.map +1 -1
- package/library/constants/messages.js +3 -0
- package/library/constants/warn-once.d.ts +12 -0
- package/library/constants/warn-once.d.ts.map +1 -0
- package/library/constants/warn-once.js +22 -0
- package/library/endpoint/facade.d.ts +238 -0
- package/library/endpoint/facade.d.ts.map +1 -0
- package/library/endpoint/facade.js +398 -0
- package/library/endpoint/heartbeat/heartbeat.d.ts +34 -0
- package/library/endpoint/heartbeat/heartbeat.d.ts.map +1 -0
- package/library/endpoint/heartbeat/heartbeat.js +67 -0
- package/library/endpoint/heartbeat/ping.d.ts +18 -0
- package/library/endpoint/heartbeat/ping.d.ts.map +1 -0
- package/library/endpoint/heartbeat/ping.js +26 -0
- package/library/endpoint/index.d.ts +16 -0
- package/library/endpoint/index.d.ts.map +1 -0
- package/library/endpoint/index.js +114 -0
- package/library/endpoint/infra/hub.d.ts +170 -0
- package/library/endpoint/infra/hub.d.ts.map +1 -0
- package/library/endpoint/infra/hub.js +323 -0
- package/library/endpoint/infra/inbox.d.ts +73 -0
- package/library/endpoint/infra/inbox.d.ts.map +1 -0
- package/library/endpoint/infra/inbox.js +239 -0
- package/library/endpoint/infra/outbox.d.ts +149 -0
- package/library/endpoint/infra/outbox.d.ts.map +1 -0
- package/library/endpoint/infra/outbox.js +415 -0
- package/library/endpoint/stream/dispatcher.d.ts +33 -0
- package/library/endpoint/stream/dispatcher.d.ts.map +1 -0
- package/library/endpoint/stream/dispatcher.js +66 -0
- package/library/endpoint/stream/errors.d.ts +20 -0
- package/library/endpoint/stream/errors.d.ts.map +1 -0
- package/library/endpoint/stream/errors.js +32 -0
- package/library/endpoint/stream/factory.d.ts +44 -0
- package/library/endpoint/stream/factory.d.ts.map +1 -0
- package/library/endpoint/stream/factory.js +82 -0
- package/library/endpoint/stream/file-auto-resolve.d.ts +26 -0
- package/library/endpoint/stream/file-auto-resolve.d.ts.map +1 -0
- package/library/endpoint/stream/file-auto-resolve.js +41 -0
- package/library/endpoint/stream/file-writable.d.ts +33 -0
- package/library/endpoint/stream/file-writable.d.ts.map +1 -0
- package/library/endpoint/stream/file-writable.js +115 -0
- package/library/endpoint/stream/handler.d.ts +20 -0
- package/library/endpoint/stream/handler.d.ts.map +1 -0
- package/library/endpoint/stream/handler.js +32 -0
- package/library/{core → impl}/client.d.ts +16 -13
- package/library/impl/client.d.ts.map +1 -0
- package/library/{core → impl}/client.js +251 -330
- package/library/{core → impl}/request.d.ts.map +1 -1
- package/library/{core → impl}/response.d.ts +5 -10
- package/library/impl/response.d.ts.map +1 -0
- package/library/{core → impl}/response.js +113 -155
- package/library/{core → impl}/server.d.ts +22 -55
- package/library/impl/server.d.ts.map +1 -0
- package/library/impl/server.js +575 -0
- package/library/index.d.ts +13 -6
- package/library/index.d.ts.map +1 -1
- package/library/index.js +16 -16
- package/library/message/ack.d.ts +15 -0
- package/library/message/ack.d.ts.map +1 -0
- package/library/message/ack.js +33 -0
- package/library/message/channel-cache.d.ts +26 -0
- package/library/message/channel-cache.d.ts.map +1 -0
- package/library/message/channel-cache.js +115 -0
- package/library/message/channel.d.ts +53 -6
- package/library/message/channel.d.ts.map +1 -1
- package/library/message/channel.js +94 -8
- package/library/message/dispatcher.d.ts +7 -0
- package/library/message/dispatcher.d.ts.map +1 -1
- package/library/message/dispatcher.js +116 -76
- package/library/stream/error.d.ts +24 -0
- package/library/stream/error.d.ts.map +1 -0
- package/library/stream/error.js +29 -0
- package/library/stream/index.d.ts +4 -1
- package/library/stream/index.d.ts.map +1 -1
- package/library/stream/index.js +7 -4
- package/library/stream/readable-stream.d.ts.map +1 -1
- package/library/stream/readable-stream.js +46 -10
- package/library/stream/stream-core.d.ts.map +1 -1
- package/library/stream/stream-core.js +6 -1
- package/library/stream/writable-stream.d.ts.map +1 -1
- package/library/stream/writable-stream.js +99 -28
- package/library/types/index.d.ts +15 -19
- package/library/types/index.d.ts.map +1 -1
- package/library/utils/blob.d.ts +3 -0
- package/library/utils/blob.d.ts.map +1 -0
- package/library/utils/blob.js +22 -0
- package/library/utils/cache.d.ts +10 -20
- package/library/utils/cache.d.ts.map +1 -1
- package/library/utils/cache.js +25 -79
- package/library/utils/content-type.d.ts +13 -0
- package/library/utils/content-type.d.ts.map +1 -0
- package/library/utils/content-type.js +87 -0
- package/library/utils/debug.d.ts.map +1 -1
- package/library/utils/debug.js +155 -177
- package/library/utils/hooks.d.ts +30 -0
- package/library/utils/hooks.d.ts.map +1 -0
- package/library/utils/hooks.js +139 -0
- package/library/utils/id.d.ts +9 -0
- package/library/utils/id.d.ts.map +1 -0
- package/library/utils/id.js +21 -0
- package/library/utils/iframe.d.ts +5 -0
- package/library/utils/iframe.d.ts.map +1 -0
- package/library/utils/iframe.js +25 -0
- package/library/utils/index.d.ts +7 -34
- package/library/utils/index.d.ts.map +1 -1
- package/library/utils/index.js +58 -194
- package/library/utils/is.d.ts +2 -0
- package/library/utils/is.d.ts.map +1 -0
- package/library/utils/is.js +9 -0
- package/library/utils/logger.d.ts +13 -0
- package/library/utils/logger.d.ts.map +1 -0
- package/library/utils/logger.js +63 -0
- package/library/utils/promise.d.ts +2 -0
- package/library/utils/promise.d.ts.map +1 -0
- package/library/utils/promise.js +9 -0
- package/library/utils/window.d.ts +2 -0
- package/library/utils/window.d.ts.map +1 -0
- package/library/utils/window.js +38 -0
- package/package.json +49 -2
- package/react/package.json +2 -1
- package/esm/core/client-server.js +0 -294
- package/esm/core/server.js +0 -776
- package/library/core/client-server.d.ts +0 -97
- package/library/core/client-server.d.ts.map +0 -1
- package/library/core/client-server.js +0 -301
- package/library/core/client.d.ts.map +0 -1
- package/library/core/response.d.ts.map +0 -1
- package/library/core/server.d.ts.map +0 -1
- package/library/core/server.js +0 -781
- /package/esm/{core → impl}/request.js +0 -0
- /package/library/{core → impl}/request.d.ts +0 -0
- /package/library/{core → impl}/request.js +0 -0
|
@@ -0,0 +1,568 @@
|
|
|
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
|
+
import "core-js/modules/es.array.includes.js";
|
|
7
|
+
import "core-js/modules/es.symbol.description.js";
|
|
8
|
+
import "core-js/modules/es.array.from.js";
|
|
9
|
+
import "core-js/modules/es.array.iterator.js";
|
|
10
|
+
import "core-js/modules/es.array.slice.js";
|
|
11
|
+
import "core-js/modules/es.map.js";
|
|
12
|
+
import "core-js/modules/es.object.entries.js";
|
|
13
|
+
import "core-js/modules/es.promise.js";
|
|
14
|
+
import "core-js/modules/es.regexp.exec.js";
|
|
15
|
+
import "core-js/modules/es.string.includes.js";
|
|
16
|
+
import "core-js/modules/es.string.match.js";
|
|
17
|
+
import "core-js/modules/web.dom-collections.for-each.js";
|
|
18
|
+
import "core-js/modules/es.regexp.to-string.js";
|
|
19
|
+
import "core-js/modules/web.dom-collections.iterator.js";
|
|
20
|
+
import { matchPath, matchPathWithParams } from '../utils/path-match';
|
|
21
|
+
import { ServerRequestImpl } from './request';
|
|
22
|
+
import { ServerResponseImpl } from './response';
|
|
23
|
+
import { generateInstanceId } from '../utils/id';
|
|
24
|
+
import { RequestIframeEndpointFacade, buildStreamStartTimeoutErrorPayload, autoResolveIframeFileReadableStream } from '../endpoint';
|
|
25
|
+
import { MessageType, ErrorCode, HttpStatus, HttpStatusText, Messages, DefaultTimeout, ProtocolVersion, formatMessage, MessageRole, StreamType as StreamTypeConstant, WarnOnceKey, buildWarnOnceKey } from '../constants';
|
|
26
|
+
import { isPromise } from '../utils/promise';
|
|
27
|
+
import { isFunction } from '../utils/is';
|
|
28
|
+
import { requestIframeLog } from '../utils/logger';
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Middleware item (contains path matcher and middleware function)
|
|
32
|
+
*/
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Server configuration options
|
|
36
|
+
*/
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* RequestIframeServer implementation
|
|
40
|
+
* Uses shared MessageDispatcher (backed by MessageChannel) to listen for and send messages
|
|
41
|
+
*/
|
|
42
|
+
export class RequestIframeServerImpl {
|
|
43
|
+
constructor(options) {
|
|
44
|
+
var _options$ackTimeout, _options$maxConcurren;
|
|
45
|
+
_defineProperty(this, "handlers", new Map());
|
|
46
|
+
_defineProperty(this, "middlewares", []);
|
|
47
|
+
/** Use custom id if provided, otherwise generate one */
|
|
48
|
+
this.id = (options === null || options === void 0 ? void 0 : options.id) || generateInstanceId();
|
|
49
|
+
var endpoint = new RequestIframeEndpointFacade({
|
|
50
|
+
role: MessageRole.SERVER,
|
|
51
|
+
instanceId: this.id,
|
|
52
|
+
secretKey: options === null || options === void 0 ? void 0 : options.secretKey,
|
|
53
|
+
versionValidator: options === null || options === void 0 ? void 0 : options.versionValidator,
|
|
54
|
+
autoAckMaxMetaLength: options === null || options === void 0 ? void 0 : options.autoAckMaxMetaLength,
|
|
55
|
+
autoAckMaxIdLength: options === null || options === void 0 ? void 0 : options.autoAckMaxIdLength,
|
|
56
|
+
streamDispatcher: {
|
|
57
|
+
handledBy: this.id
|
|
58
|
+
},
|
|
59
|
+
heartbeat: {
|
|
60
|
+
pendingBucket: RequestIframeServerImpl.PENDING_PONGS,
|
|
61
|
+
handledBy: this.id,
|
|
62
|
+
isOriginAllowed: (d, ctx) => this.isOriginAllowed(d, ctx),
|
|
63
|
+
warnMissingPendingWhenClosed: d => {
|
|
64
|
+
this.hub.warnOnce(buildWarnOnceKey(WarnOnceKey.SERVER_MISSING_PENDING_WHEN_CLOSED, d.type, d.requestId), () => {
|
|
65
|
+
requestIframeLog('warn', formatMessage(Messages.SERVER_IGNORED_MESSAGE_WHEN_CLOSED, d.type, d.requestId));
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
},
|
|
69
|
+
originValidator: {
|
|
70
|
+
allowedOrigins: options === null || options === void 0 ? void 0 : options.allowedOrigins,
|
|
71
|
+
validateOrigin: options === null || options === void 0 ? void 0 : options.validateOrigin
|
|
72
|
+
}
|
|
73
|
+
});
|
|
74
|
+
this.endpoint = endpoint;
|
|
75
|
+
this.hub = endpoint.hub;
|
|
76
|
+
this.originValidator = endpoint.originValidator;
|
|
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.maxConcurrentRequestsPerClient = (_options$maxConcurren = options === null || options === void 0 ? void 0 : options.maxConcurrentRequestsPerClient) !== null && _options$maxConcurren !== void 0 ? _options$maxConcurren : Number.POSITIVE_INFINITY;
|
|
79
|
+
var warnMissingPendingWhenClosed = d => {
|
|
80
|
+
this.hub.warnOnce(buildWarnOnceKey(WarnOnceKey.SERVER_MISSING_PENDING_WHEN_CLOSED, d.type, d.requestId), () => {
|
|
81
|
+
requestIframeLog('warn', formatMessage(Messages.SERVER_IGNORED_MESSAGE_WHEN_CLOSED, d.type, d.requestId));
|
|
82
|
+
});
|
|
83
|
+
};
|
|
84
|
+
var handlerOptions = this.hub.createHandlerOptions(this.handleVersionError.bind(this));
|
|
85
|
+
|
|
86
|
+
// Server business entry: REQUEST handler
|
|
87
|
+
this.endpoint.onOpen(() => {
|
|
88
|
+
this.hub.registerHandler(MessageType.REQUEST, (data, context) => this.handleRequest(data, context), handlerOptions);
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
// Server base infra: ping/pong/ack/stream handlers (facade-managed)
|
|
92
|
+
this.endpoint.registerServerBaseHandlers({
|
|
93
|
+
handlerOptions,
|
|
94
|
+
handledBy: this.id,
|
|
95
|
+
includeTargetIdInPong: false,
|
|
96
|
+
isOriginAllowed: (d, ctx) => this.isOriginAllowed(d, ctx),
|
|
97
|
+
warnMissingPendingWhenClosed,
|
|
98
|
+
pendingAckBucket: RequestIframeServerImpl.PENDING_ACKS,
|
|
99
|
+
pendingStreamStartBucket: RequestIframeServerImpl.PENDING_STREAM_REQUESTS,
|
|
100
|
+
expectedStreamStartRole: MessageRole.CLIENT
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
/** Auto-open by default (unless explicitly set to false) */
|
|
104
|
+
if ((options === null || options === void 0 ? void 0 : options.autoOpen) !== false) {
|
|
105
|
+
this.open();
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
get dispatcher() {
|
|
109
|
+
return this.hub.messageDispatcher;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/** Message isolation key (read-only) */
|
|
113
|
+
get secretKey() {
|
|
114
|
+
return this.hub.secretKey;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/** Whether message handling is enabled */
|
|
118
|
+
get isOpen() {
|
|
119
|
+
return this.hub.isOpen;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/** Enable message handling (register message handler) */
|
|
123
|
+
open() {
|
|
124
|
+
this.hub.open();
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/** Disable message handling (unregister message handler, but don't release resources) */
|
|
128
|
+
close() {
|
|
129
|
+
this.hub.close();
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/** Get the underlying MessageDispatcher */
|
|
133
|
+
get messageDispatcher() {
|
|
134
|
+
return this.hub.messageDispatcher;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* Check whether an incoming message origin is allowed.
|
|
139
|
+
*/
|
|
140
|
+
isOriginAllowed(data, context) {
|
|
141
|
+
if (!this.originValidator) return true;
|
|
142
|
+
try {
|
|
143
|
+
return this.originValidator(context.origin, data, context);
|
|
144
|
+
} catch (_unused) {
|
|
145
|
+
return false;
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* Build a per-client key used for concurrency limiting.
|
|
151
|
+
* We intentionally include origin to prevent cross-origin collisions.
|
|
152
|
+
*/
|
|
153
|
+
getClientKey(origin, creatorId) {
|
|
154
|
+
return `${origin}::${creatorId || 'unknown'}`;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* Handle protocol version error
|
|
159
|
+
*/
|
|
160
|
+
handleVersionError(data, context, version) {
|
|
161
|
+
if (!context.source) return;
|
|
162
|
+
|
|
163
|
+
// Send protocol version incompatibility error
|
|
164
|
+
// Window check is handled in MessageDispatcher
|
|
165
|
+
this.dispatcher.sendMessage(context.source, context.origin, MessageType.ERROR, data.requestId, {
|
|
166
|
+
path: data.path,
|
|
167
|
+
status: HttpStatus.BAD_REQUEST,
|
|
168
|
+
statusText: Messages.PROTOCOL_VERSION_UNSUPPORTED,
|
|
169
|
+
error: {
|
|
170
|
+
message: formatMessage(Messages.PROTOCOL_VERSION_TOO_LOW, version, ProtocolVersion.MIN_SUPPORTED),
|
|
171
|
+
code: ErrorCode.PROTOCOL_UNSUPPORTED
|
|
172
|
+
}
|
|
173
|
+
});
|
|
174
|
+
}
|
|
175
|
+
handleRequestError(res, targetWindow, targetOrigin, data, err) {
|
|
176
|
+
if (!res._sent) {
|
|
177
|
+
res._markSent();
|
|
178
|
+
/**
|
|
179
|
+
* Use INTERNAL_SERVER_ERROR (500) for handler errors unless a different error status code was explicitly set.
|
|
180
|
+
* If statusCode is still the default OK (200), override it to INTERNAL_SERVER_ERROR.
|
|
181
|
+
*/
|
|
182
|
+
var errorStatus = res.statusCode === HttpStatus.OK ? HttpStatus.INTERNAL_SERVER_ERROR : res.statusCode;
|
|
183
|
+
|
|
184
|
+
// Window check is handled in MessageDispatcher
|
|
185
|
+
this.dispatcher.sendMessage(targetWindow, targetOrigin, MessageType.ERROR, data.requestId, {
|
|
186
|
+
path: data.path,
|
|
187
|
+
error: {
|
|
188
|
+
message: err && err.message || Messages.REQUEST_FAILED,
|
|
189
|
+
code: err && err.code || ErrorCode.REQUEST_ERROR
|
|
190
|
+
},
|
|
191
|
+
status: errorStatus,
|
|
192
|
+
statusText: HttpStatusText[errorStatus] || HttpStatusText[HttpStatus.INTERNAL_SERVER_ERROR],
|
|
193
|
+
headers: res.headers,
|
|
194
|
+
targetId: data.creatorId
|
|
195
|
+
});
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
handleRequestResult(res, targetWindow, targetOrigin, data, result) {
|
|
199
|
+
if (!res._sent && result !== undefined) {
|
|
200
|
+
res.send(result);
|
|
201
|
+
} else if (!res._sent) {
|
|
202
|
+
res._markSent();
|
|
203
|
+
this.dispatcher.sendMessage(targetWindow, targetOrigin, MessageType.ERROR, data.requestId, {
|
|
204
|
+
path: data.path,
|
|
205
|
+
error: {
|
|
206
|
+
message: Messages.NO_RESPONSE_SENT,
|
|
207
|
+
code: ErrorCode.NO_RESPONSE
|
|
208
|
+
},
|
|
209
|
+
status: HttpStatus.INTERNAL_SERVER_ERROR,
|
|
210
|
+
statusText: HttpStatusText[HttpStatus.INTERNAL_SERVER_ERROR],
|
|
211
|
+
headers: res.headers
|
|
212
|
+
});
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
/**
|
|
217
|
+
* Find matching handler and extract path parameters
|
|
218
|
+
* @param requestPath The actual request path
|
|
219
|
+
* @returns Handler function and extracted parameters, or null if not found
|
|
220
|
+
*/
|
|
221
|
+
findHandler(requestPath) {
|
|
222
|
+
var prefixedRequestPath = this.dispatcher.prefixPath(requestPath);
|
|
223
|
+
|
|
224
|
+
// First try exact match (for backward compatibility and performance)
|
|
225
|
+
var exactHandler = this.handlers.get(prefixedRequestPath);
|
|
226
|
+
if (exactHandler) {
|
|
227
|
+
return {
|
|
228
|
+
handler: exactHandler,
|
|
229
|
+
params: {}
|
|
230
|
+
};
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
// Then try parameter matching (e.g., '/api/users/:id' matches '/api/users/123')
|
|
234
|
+
var _iterator = _createForOfIteratorHelper(this.handlers.entries()),
|
|
235
|
+
_step;
|
|
236
|
+
try {
|
|
237
|
+
for (_iterator.s(); !(_step = _iterator.n()).done;) {
|
|
238
|
+
var _step$value = _slicedToArray(_step.value, 2),
|
|
239
|
+
registeredPath = _step$value[0],
|
|
240
|
+
handler = _step$value[1];
|
|
241
|
+
// Check if registered path contains parameters
|
|
242
|
+
if (registeredPath.includes(':')) {
|
|
243
|
+
var matchResult = matchPathWithParams(prefixedRequestPath, registeredPath);
|
|
244
|
+
if (matchResult.match) {
|
|
245
|
+
return {
|
|
246
|
+
handler,
|
|
247
|
+
params: matchResult.params
|
|
248
|
+
};
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
} catch (err) {
|
|
253
|
+
_iterator.e(err);
|
|
254
|
+
} finally {
|
|
255
|
+
_iterator.f();
|
|
256
|
+
}
|
|
257
|
+
return null;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
/**
|
|
261
|
+
* Handle request
|
|
262
|
+
*/
|
|
263
|
+
handleRequest(data, context) {
|
|
264
|
+
// If targetId is specified, only process if it matches this server's id
|
|
265
|
+
if (!data.path || data.targetId && data.targetId !== this.id) return;
|
|
266
|
+
if (!context.source) return;
|
|
267
|
+
if (!this.isOriginAllowed(data, context)) return;
|
|
268
|
+
|
|
269
|
+
// If message has already been handled by another server instance, skip processing
|
|
270
|
+
if (context.handledBy) {
|
|
271
|
+
return;
|
|
272
|
+
}
|
|
273
|
+
var targetWindow = context.source;
|
|
274
|
+
var targetOrigin = context.origin;
|
|
275
|
+
|
|
276
|
+
// Find matching handler and extract path parameters
|
|
277
|
+
var handlerMatch = this.findHandler(data.path);
|
|
278
|
+
if (!handlerMatch) {
|
|
279
|
+
// No handler found in this instance
|
|
280
|
+
// Mark as handled by this instance (using special marker) to prevent other instances from processing
|
|
281
|
+
// This ensures only one instance sends the error response
|
|
282
|
+
context.markHandledBy(this.id);
|
|
283
|
+
|
|
284
|
+
// Send METHOD_NOT_FOUND error
|
|
285
|
+
// Use request's creatorId as targetId to route back to the correct client
|
|
286
|
+
// Window check is handled in MessageDispatcher
|
|
287
|
+
this.dispatcher.sendMessage(targetWindow, targetOrigin, MessageType.ERROR, data.requestId, {
|
|
288
|
+
path: data.path,
|
|
289
|
+
error: {
|
|
290
|
+
message: Messages.METHOD_NOT_FOUND,
|
|
291
|
+
code: ErrorCode.METHOD_NOT_FOUND
|
|
292
|
+
},
|
|
293
|
+
status: HttpStatus.NOT_FOUND,
|
|
294
|
+
statusText: HttpStatusText[HttpStatus.NOT_FOUND],
|
|
295
|
+
targetId: data.creatorId
|
|
296
|
+
});
|
|
297
|
+
return;
|
|
298
|
+
}
|
|
299
|
+
var handlerFn = handlerMatch.handler,
|
|
300
|
+
params = handlerMatch.params;
|
|
301
|
+
var clientKey = this.getClientKey(targetOrigin, data.creatorId);
|
|
302
|
+
var acquired = this.hub.limiter.tryAcquire(RequestIframeServerImpl.LIMIT_IN_FLIGHT_BY_CLIENT, clientKey, this.maxConcurrentRequestsPerClient);
|
|
303
|
+
if (!acquired) {
|
|
304
|
+
// Prevent other server instances from also responding
|
|
305
|
+
context.markHandledBy(this.id);
|
|
306
|
+
this.dispatcher.sendMessage(targetWindow, targetOrigin, MessageType.ERROR, data.requestId, {
|
|
307
|
+
path: data.path,
|
|
308
|
+
error: {
|
|
309
|
+
message: formatMessage(Messages.TOO_MANY_REQUESTS, this.maxConcurrentRequestsPerClient),
|
|
310
|
+
code: ErrorCode.TOO_MANY_REQUESTS
|
|
311
|
+
},
|
|
312
|
+
status: HttpStatus.TOO_MANY_REQUESTS,
|
|
313
|
+
statusText: HttpStatusText[HttpStatus.TOO_MANY_REQUESTS],
|
|
314
|
+
requireAck: data.requireAck,
|
|
315
|
+
ack: data.ack,
|
|
316
|
+
targetId: data.creatorId
|
|
317
|
+
});
|
|
318
|
+
return;
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
// Mark as accepted so MessageDispatcher can auto-send ACK (delivery confirmation)
|
|
322
|
+
context.markAcceptedBy(this.id);
|
|
323
|
+
|
|
324
|
+
// Create response object with channel reference
|
|
325
|
+
// Pass request's creatorId as targetId so responses are routed back to the correct client
|
|
326
|
+
var peer = this.hub.createOutbox(targetWindow, targetOrigin, data.creatorId);
|
|
327
|
+
var res = new ServerResponseImpl(data.requestId, data.path || '', this.id, peer, {
|
|
328
|
+
registerStreamHandler: (streamId, handler) => {
|
|
329
|
+
this.endpoint.streamDispatcher.register(streamId, handler);
|
|
330
|
+
},
|
|
331
|
+
unregisterStreamHandler: streamId => {
|
|
332
|
+
this.endpoint.streamDispatcher.unregister(streamId);
|
|
333
|
+
},
|
|
334
|
+
heartbeat: () => this.endpoint.pingPeer(targetWindow, targetOrigin, this.ackTimeout, data.creatorId),
|
|
335
|
+
onSent: () => this.hub.limiter.release(RequestIframeServerImpl.LIMIT_IN_FLIGHT_BY_CLIENT, clientKey)
|
|
336
|
+
});
|
|
337
|
+
|
|
338
|
+
// Register callback waiting for client acknowledgment
|
|
339
|
+
this.endpoint.registerPendingAck({
|
|
340
|
+
requestId: data.requestId,
|
|
341
|
+
timeoutMs: this.ackTimeout,
|
|
342
|
+
pendingBucket: RequestIframeServerImpl.PENDING_ACKS,
|
|
343
|
+
resolve: (received, ack) => {
|
|
344
|
+
res._triggerAck(received, ack);
|
|
345
|
+
}
|
|
346
|
+
});
|
|
347
|
+
|
|
348
|
+
// Client sends body as stream: wait for stream_start, then create readable stream and call handler
|
|
349
|
+
// If streamId is present, this is a stream request
|
|
350
|
+
var streamId = data.streamId;
|
|
351
|
+
if (streamId) {
|
|
352
|
+
var streamStartTimeout = this.ackTimeout;
|
|
353
|
+
this.endpoint.registerIncomingStreamStartWaiter({
|
|
354
|
+
pendingBucket: RequestIframeServerImpl.PENDING_STREAM_REQUESTS,
|
|
355
|
+
requestId: data.requestId,
|
|
356
|
+
streamId,
|
|
357
|
+
timeoutMs: streamStartTimeout,
|
|
358
|
+
targetWindow,
|
|
359
|
+
targetOrigin,
|
|
360
|
+
onTimeout: () => {
|
|
361
|
+
if (!res._sent) {
|
|
362
|
+
res._markSent();
|
|
363
|
+
this.dispatcher.sendMessage(targetWindow, targetOrigin, MessageType.ERROR, data.requestId, buildStreamStartTimeoutErrorPayload({
|
|
364
|
+
path: data.path || '',
|
|
365
|
+
timeoutMs: streamStartTimeout,
|
|
366
|
+
requireAck: data.requireAck,
|
|
367
|
+
ack: data.ack,
|
|
368
|
+
targetId: data.creatorId
|
|
369
|
+
}));
|
|
370
|
+
}
|
|
371
|
+
},
|
|
372
|
+
continue: ({
|
|
373
|
+
stream,
|
|
374
|
+
info,
|
|
375
|
+
data: streamStartData
|
|
376
|
+
}) => {
|
|
377
|
+
// Create request object with path parameters
|
|
378
|
+
var req = new ServerRequestImpl(data, context, res, params);
|
|
379
|
+
|
|
380
|
+
// File stream: optionally auto-resolve to File/Blob before calling handler
|
|
381
|
+
if ((info === null || info === void 0 ? void 0 : info.type) === StreamTypeConstant.FILE) {
|
|
382
|
+
var fileStream = stream;
|
|
383
|
+
if (info !== null && info !== void 0 && info.autoResolve) {
|
|
384
|
+
autoResolveIframeFileReadableStream({
|
|
385
|
+
fileStream,
|
|
386
|
+
info
|
|
387
|
+
}).then(fileData => {
|
|
388
|
+
req.body = fileData;
|
|
389
|
+
req.stream = undefined;
|
|
390
|
+
this.runMiddlewares(req, res, () => {
|
|
391
|
+
try {
|
|
392
|
+
var result = handlerFn(req, res);
|
|
393
|
+
if (isPromise(result)) {
|
|
394
|
+
this.dispatcher.sendMessage(targetWindow, targetOrigin, MessageType.ASYNC, streamStartData.requestId, {
|
|
395
|
+
path: data.path,
|
|
396
|
+
targetId: streamStartData.creatorId
|
|
397
|
+
});
|
|
398
|
+
result.then(this.handleRequestResult.bind(this, res, targetWindow, targetOrigin, data)).catch(this.handleRequestError.bind(this, res, targetWindow, targetOrigin, data));
|
|
399
|
+
} else {
|
|
400
|
+
this.handleRequestResult(res, targetWindow, targetOrigin, data, result);
|
|
401
|
+
}
|
|
402
|
+
} catch (error) {
|
|
403
|
+
this.handleRequestError(res, targetWindow, targetOrigin, data, error);
|
|
404
|
+
}
|
|
405
|
+
});
|
|
406
|
+
}).catch(error => {
|
|
407
|
+
this.handleRequestError(res, targetWindow, targetOrigin, data, error);
|
|
408
|
+
});
|
|
409
|
+
return;
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
// Non-autoResolve: expose stream directly
|
|
413
|
+
req.body = fileStream;
|
|
414
|
+
req.stream = fileStream;
|
|
415
|
+
} else {
|
|
416
|
+
// Non-file stream
|
|
417
|
+
req.body = undefined;
|
|
418
|
+
req.stream = stream;
|
|
419
|
+
}
|
|
420
|
+
this.runMiddlewares(req, res, () => {
|
|
421
|
+
try {
|
|
422
|
+
var result = handlerFn(req, res);
|
|
423
|
+
if (isPromise(result)) {
|
|
424
|
+
this.dispatcher.sendMessage(targetWindow, targetOrigin, MessageType.ASYNC, streamStartData.requestId, {
|
|
425
|
+
path: data.path,
|
|
426
|
+
targetId: streamStartData.creatorId
|
|
427
|
+
});
|
|
428
|
+
result.then(this.handleRequestResult.bind(this, res, targetWindow, targetOrigin, data)).catch(this.handleRequestError.bind(this, res, targetWindow, targetOrigin, data));
|
|
429
|
+
} else {
|
|
430
|
+
this.handleRequestResult(res, targetWindow, targetOrigin, data, result);
|
|
431
|
+
}
|
|
432
|
+
} catch (error) {
|
|
433
|
+
this.handleRequestError(res, targetWindow, targetOrigin, data, error);
|
|
434
|
+
}
|
|
435
|
+
});
|
|
436
|
+
}
|
|
437
|
+
});
|
|
438
|
+
return;
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
// Create request object with path parameters
|
|
442
|
+
var req = new ServerRequestImpl(data, context, res, params);
|
|
443
|
+
|
|
444
|
+
// Execute middleware chain
|
|
445
|
+
this.runMiddlewares(req, res, () => {
|
|
446
|
+
try {
|
|
447
|
+
var result = handlerFn(req, res);
|
|
448
|
+
if (isPromise(result)) {
|
|
449
|
+
// Async task
|
|
450
|
+
// Window check is handled in MessageDispatcher
|
|
451
|
+
// Use request's creatorId as targetId to route back to the correct client
|
|
452
|
+
this.dispatcher.sendMessage(targetWindow, targetOrigin, MessageType.ASYNC, data.requestId, {
|
|
453
|
+
path: data.path,
|
|
454
|
+
targetId: data.creatorId
|
|
455
|
+
});
|
|
456
|
+
result.then(this.handleRequestResult.bind(this, res, targetWindow, targetOrigin, data)).catch(this.handleRequestError.bind(this, res, targetWindow, targetOrigin, data));
|
|
457
|
+
} else {
|
|
458
|
+
// Synchronous processing
|
|
459
|
+
this.handleRequestResult(res, targetWindow, targetOrigin, data, result);
|
|
460
|
+
}
|
|
461
|
+
} catch (error) {
|
|
462
|
+
this.handleRequestError(res, targetWindow, targetOrigin, data, error);
|
|
463
|
+
}
|
|
464
|
+
});
|
|
465
|
+
}
|
|
466
|
+
use(pathOrMiddleware, middleware) {
|
|
467
|
+
if (isFunction(pathOrMiddleware)) {
|
|
468
|
+
this.middlewares.push({
|
|
469
|
+
matcher: null,
|
|
470
|
+
middleware: pathOrMiddleware
|
|
471
|
+
});
|
|
472
|
+
} else if (middleware) {
|
|
473
|
+
this.middlewares.push({
|
|
474
|
+
matcher: pathOrMiddleware,
|
|
475
|
+
middleware
|
|
476
|
+
});
|
|
477
|
+
}
|
|
478
|
+
}
|
|
479
|
+
on(path, handler) {
|
|
480
|
+
var prefixedPath = this.dispatcher.prefixPath(path);
|
|
481
|
+
this.handlers.set(prefixedPath, handler);
|
|
482
|
+
|
|
483
|
+
// Return unregister function
|
|
484
|
+
return () => {
|
|
485
|
+
this.handlers.delete(prefixedPath);
|
|
486
|
+
};
|
|
487
|
+
}
|
|
488
|
+
runMiddlewares(req, res, finalHandler) {
|
|
489
|
+
var path = req.path;
|
|
490
|
+
var index = 0;
|
|
491
|
+
var next = () => {
|
|
492
|
+
if (res._sent) {
|
|
493
|
+
return;
|
|
494
|
+
}
|
|
495
|
+
while (index < this.middlewares.length) {
|
|
496
|
+
var item = this.middlewares[index++];
|
|
497
|
+
if (item.matcher === null || matchPath(path, item.matcher)) {
|
|
498
|
+
try {
|
|
499
|
+
var result = item.middleware(req, res, next);
|
|
500
|
+
if (result instanceof Promise) {
|
|
501
|
+
result.catch(err => {
|
|
502
|
+
if (!res._sent) {
|
|
503
|
+
res.status(HttpStatus.INTERNAL_SERVER_ERROR).send({
|
|
504
|
+
error: err.message || Messages.MIDDLEWARE_ERROR
|
|
505
|
+
});
|
|
506
|
+
}
|
|
507
|
+
});
|
|
508
|
+
}
|
|
509
|
+
return;
|
|
510
|
+
} catch (err) {
|
|
511
|
+
if (!res._sent) {
|
|
512
|
+
res.status(HttpStatus.INTERNAL_SERVER_ERROR).send({
|
|
513
|
+
error: (err === null || err === void 0 ? void 0 : err.message) || Messages.MIDDLEWARE_ERROR
|
|
514
|
+
});
|
|
515
|
+
}
|
|
516
|
+
return;
|
|
517
|
+
}
|
|
518
|
+
}
|
|
519
|
+
}
|
|
520
|
+
if (index >= this.middlewares.length) {
|
|
521
|
+
finalHandler();
|
|
522
|
+
}
|
|
523
|
+
};
|
|
524
|
+
next();
|
|
525
|
+
}
|
|
526
|
+
off(path) {
|
|
527
|
+
if (Array.isArray(path)) {
|
|
528
|
+
// Batch unregister
|
|
529
|
+
path.forEach(p => {
|
|
530
|
+
this.handlers.delete(this.dispatcher.prefixPath(p));
|
|
531
|
+
});
|
|
532
|
+
} else {
|
|
533
|
+
// Single unregister
|
|
534
|
+
this.handlers.delete(this.dispatcher.prefixPath(path));
|
|
535
|
+
}
|
|
536
|
+
}
|
|
537
|
+
map(handlers) {
|
|
538
|
+
var unregisterFns = [];
|
|
539
|
+
Object.entries(handlers).forEach(([path, h]) => {
|
|
540
|
+
unregisterFns.push(this.on(path, h));
|
|
541
|
+
});
|
|
542
|
+
return () => {
|
|
543
|
+
unregisterFns.forEach(fn => fn());
|
|
544
|
+
};
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
/**
|
|
548
|
+
* Cleanup before destroy
|
|
549
|
+
*/
|
|
550
|
+
cleanup() {
|
|
551
|
+
// Clean up handlers
|
|
552
|
+
this.handlers.clear();
|
|
553
|
+
this.middlewares.length = 0;
|
|
554
|
+
this.endpoint.streamDispatcher.clear();
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
/**
|
|
558
|
+
* Destroy server (close and release resources)
|
|
559
|
+
*/
|
|
560
|
+
destroy() {
|
|
561
|
+
this.cleanup();
|
|
562
|
+
this.hub.destroy();
|
|
563
|
+
}
|
|
564
|
+
}
|
|
565
|
+
_defineProperty(RequestIframeServerImpl, "PENDING_ACKS", 'server:pendingAcks');
|
|
566
|
+
_defineProperty(RequestIframeServerImpl, "PENDING_PONGS", 'server:pendingPongs');
|
|
567
|
+
_defineProperty(RequestIframeServerImpl, "PENDING_STREAM_REQUESTS", 'server:pendingStreamRequests');
|
|
568
|
+
_defineProperty(RequestIframeServerImpl, "LIMIT_IN_FLIGHT_BY_CLIENT", 'server:inFlightByClientKey');
|
package/esm/index.js
CHANGED
|
@@ -1,19 +1,26 @@
|
|
|
1
1
|
// API
|
|
2
2
|
export { requestIframeClient, clearRequestIframeClientCache } from './api/client';
|
|
3
3
|
export { requestIframeServer, clearRequestIframeServerCache } from './api/server';
|
|
4
|
+
export { requestIframeEndpoint } from './api/endpoint';
|
|
4
5
|
// Implementation classes
|
|
5
|
-
export { RequestIframeClientImpl } from './
|
|
6
|
-
export { RequestIframeServerImpl } from './
|
|
7
|
-
export { RequestIframeClientServer } from './core/client-server';
|
|
6
|
+
export { RequestIframeClientImpl } from './impl/client';
|
|
7
|
+
export { RequestIframeServerImpl } from './impl/server';
|
|
8
8
|
// MessageChannel and MessageDispatcher
|
|
9
9
|
export { MessageChannel, ChannelType, MessageDispatcher } from './message';
|
|
10
10
|
// Cache utilities
|
|
11
|
-
export { getOrCreateMessageChannel, releaseMessageChannel, clearMessageChannelCache } from './
|
|
12
|
-
export { ServerRequestImpl } from './
|
|
13
|
-
export { ServerResponseImpl } from './
|
|
11
|
+
export { getOrCreateMessageChannel, releaseMessageChannel, clearMessageChannelCache } from './message/channel-cache';
|
|
12
|
+
export { ServerRequestImpl } from './impl/request';
|
|
13
|
+
export { ServerResponseImpl } from './impl/response';
|
|
14
14
|
// Stream
|
|
15
15
|
export { IframeWritableStream, IframeReadableStream, IframeFileWritableStream, IframeFileReadableStream, isIframeReadableStream, isIframeFileReadableStream, isIframeFileWritableStream, isIframeWritableStream } from './stream';
|
|
16
16
|
// Types and utilities
|
|
17
|
+
/**
|
|
18
|
+
* NOTE:
|
|
19
|
+
* Root entry re-exports are kept for convenience, but for better tree-shaking and clearer dependencies,
|
|
20
|
+
* prefer subpath imports:
|
|
21
|
+
* - `request-iframe/types`
|
|
22
|
+
* - `request-iframe/constants`
|
|
23
|
+
*/
|
|
17
24
|
export * from './types';
|
|
18
25
|
export { detectContentType, blobToBase64, RequestIframeError } from './utils';
|
|
19
26
|
export { InterceptorManager, RequestInterceptorManager, ResponseInterceptorManager } from './interceptors';
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { generateRequestId } from '../utils/id';
|
|
2
|
+
import { isAckMatch } from '../utils/ack';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Build the expected ack payload for requireAck workflows.
|
|
6
|
+
*
|
|
7
|
+
* - If `ack` is provided, it wins.
|
|
8
|
+
* - If `requireAck` is true and ack not provided, generate `{ id }`.
|
|
9
|
+
* - Otherwise return undefined.
|
|
10
|
+
*/
|
|
11
|
+
export function buildExpectedAck(requireAck, ack) {
|
|
12
|
+
if (ack !== undefined) return ack;
|
|
13
|
+
if (!requireAck) return undefined;
|
|
14
|
+
return {
|
|
15
|
+
id: generateRequestId()
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Check whether received ack matches expected.
|
|
21
|
+
*
|
|
22
|
+
* If expected is undefined, treat as "no matching required" and return true.
|
|
23
|
+
*/
|
|
24
|
+
export function isExpectedAckMatch(expectedAck, receivedAck) {
|
|
25
|
+
if (expectedAck === undefined) return true;
|
|
26
|
+
return isAckMatch(expectedAck, receivedAck);
|
|
27
|
+
}
|