@xo-cash/utils 0.0.7 → 0.0.8
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/dist/index.d.mts +345 -1
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +485 -1
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/dist/index.mjs
CHANGED
|
@@ -265,6 +265,21 @@ var ExternallyAbortedExponentialBackoffInternalSignalAbortedError = class extend
|
|
|
265
265
|
//#endregion
|
|
266
266
|
//#region source/misc.ts
|
|
267
267
|
/**
|
|
268
|
+
* Recursively freezes an object by iterating over all properties and freezing them.
|
|
269
|
+
* @param obj - The object to freeze.
|
|
270
|
+
* @returns The frozen object.
|
|
271
|
+
*/
|
|
272
|
+
const deepFreeze = (value) => {
|
|
273
|
+
if (value !== null && (typeof value === "object" || typeof value === "function")) {
|
|
274
|
+
for (const key of Reflect.ownKeys(value)) {
|
|
275
|
+
const descriptor = Reflect.getOwnPropertyDescriptor(value, key);
|
|
276
|
+
if (descriptor && "value" in descriptor) deepFreeze(descriptor.value);
|
|
277
|
+
}
|
|
278
|
+
Object.freeze(value);
|
|
279
|
+
}
|
|
280
|
+
return value;
|
|
281
|
+
};
|
|
282
|
+
/**
|
|
268
283
|
* Validate the value is within the bounds, returning true if it is within the bounds, false otherwise
|
|
269
284
|
*
|
|
270
285
|
* @param value - The value to validate
|
|
@@ -277,6 +292,14 @@ const isWithinBounds = (value, min, max) => {
|
|
|
277
292
|
if (value < min || value > max) return false;
|
|
278
293
|
return true;
|
|
279
294
|
};
|
|
295
|
+
/**
|
|
296
|
+
* Converts a non-error to an error
|
|
297
|
+
* @param error - The error to convert
|
|
298
|
+
* @returns The error
|
|
299
|
+
*/
|
|
300
|
+
const normalizeError = (error) => {
|
|
301
|
+
return error instanceof Error ? error : /* @__PURE__ */ new Error(`${error}`);
|
|
302
|
+
};
|
|
280
303
|
|
|
281
304
|
//#endregion
|
|
282
305
|
//#region source/exponential-backoff/exponential-backoff.ts
|
|
@@ -728,6 +751,26 @@ const SSE_TRAILING_NEWLINE_REGEX = /\n$/;
|
|
|
728
751
|
* buffered partial lines between streamed chunks before the next parse call.
|
|
729
752
|
*/
|
|
730
753
|
const NEW_LINE = "\n";
|
|
754
|
+
/**
|
|
755
|
+
* Default exponential backoff options for the SSE session.
|
|
756
|
+
*/
|
|
757
|
+
const SSE_SESSION_EXPONENTIAL_BACKOFF_DEFAULTS = {
|
|
758
|
+
baseDelay: 250,
|
|
759
|
+
maxDelay: 1e4,
|
|
760
|
+
maxAttempts: 0,
|
|
761
|
+
growthRate: 1.3,
|
|
762
|
+
jitter: .3
|
|
763
|
+
};
|
|
764
|
+
/**
|
|
765
|
+
* Default attempt reconnect flag for the SSE session.
|
|
766
|
+
* @default true
|
|
767
|
+
*/
|
|
768
|
+
const SSE_SESSION_ATTEMPT_RECONNECT_DEFAULT = true;
|
|
769
|
+
/**
|
|
770
|
+
* Default persistent flag for the SSE session.
|
|
771
|
+
* @default false
|
|
772
|
+
*/
|
|
773
|
+
const SSE_SESSION_PERSISTENT_DEFAULT = false;
|
|
731
774
|
|
|
732
775
|
//#endregion
|
|
733
776
|
//#region source/sse-session/sse-event-parser.ts
|
|
@@ -880,6 +923,447 @@ var SSEEventParser = class {
|
|
|
880
923
|
}
|
|
881
924
|
};
|
|
882
925
|
|
|
926
|
+
//#endregion
|
|
927
|
+
//#region source/sse-session/errors.ts
|
|
928
|
+
/**
|
|
929
|
+
* Error thrown when a response body is null
|
|
930
|
+
*/
|
|
931
|
+
var ResponseBodyNullError = class extends Error {
|
|
932
|
+
constructor() {
|
|
933
|
+
super("Response body is null");
|
|
934
|
+
this.name = "ResponseBodyNullError";
|
|
935
|
+
}
|
|
936
|
+
};
|
|
937
|
+
/**
|
|
938
|
+
* Error thrown when an HTTP error occurs
|
|
939
|
+
*/
|
|
940
|
+
var HTTPError = class extends Error {
|
|
941
|
+
constructor(status, message) {
|
|
942
|
+
super(`HTTP error! Status: ${status} - ${message}`);
|
|
943
|
+
this.name = "HTTPError";
|
|
944
|
+
}
|
|
945
|
+
};
|
|
946
|
+
/**
|
|
947
|
+
* Error thrown when a plugin's unsubscribe function is not a function
|
|
948
|
+
*/
|
|
949
|
+
var SSESessionUnsubscribePluginNotAFunctionError = class extends Error {
|
|
950
|
+
constructor(pluginName) {
|
|
951
|
+
super(`${pluginName}'s unsubscribe function is not a function`);
|
|
952
|
+
this.name = "SSESessionUnsubscribePluginNotAFunctionError";
|
|
953
|
+
}
|
|
954
|
+
};
|
|
955
|
+
|
|
956
|
+
//#endregion
|
|
957
|
+
//#region source/sse-session/sse-session.ts
|
|
958
|
+
/**
|
|
959
|
+
* A fetch-based Server-Sent Events (SSE) client with reconnect and optional
|
|
960
|
+
* browser tab visibility handling.
|
|
961
|
+
*
|
|
962
|
+
* Each session maintains one HTTP streaming connection at a time. Incoming
|
|
963
|
+
* bytes are parsed into {@link SSEvent} objects and delivered through two
|
|
964
|
+
* surfaces:
|
|
965
|
+
*
|
|
966
|
+
* - **Events** — `"connected"`, `"message"`, `"disconnected"`, `"error"`,
|
|
967
|
+
* and `"closed"` on the session itself (extends {@link EventEmitter}).
|
|
968
|
+
* - **Messages** — {@link messages}, an async iterable for `for await...of`
|
|
969
|
+
* consumers.
|
|
970
|
+
*
|
|
971
|
+
* Typical usage:
|
|
972
|
+
*
|
|
973
|
+
* ```ts
|
|
974
|
+
* const session = await SSESession.create("/events");
|
|
975
|
+
*
|
|
976
|
+
* session.on("message", (event) => console.log(event.data));
|
|
977
|
+
*
|
|
978
|
+
* for await (const event of session.messages) {
|
|
979
|
+
* handle(event);
|
|
980
|
+
* }
|
|
981
|
+
* ```
|
|
982
|
+
*
|
|
983
|
+
* ## Lifecycle
|
|
984
|
+
*
|
|
985
|
+
* - {@link connect} opens (or reopens) the transport. It resolves once the
|
|
986
|
+
* HTTP stream is established; reading continues in the background.
|
|
987
|
+
* - {@link disconnect} stops the in-flight fetch without ending the session.
|
|
988
|
+
* Used internally for tab visibility. The {@link messages} iterator stays
|
|
989
|
+
* open so an existing consumer resumes when the tab becomes visible again.
|
|
990
|
+
* - {@link close} aborts the transport, closes {@link messages}, and emits
|
|
991
|
+
* `"closed"`.
|
|
992
|
+
*
|
|
993
|
+
* Automatic reconnect is controlled by {@link SSESessionOptions.persistent}
|
|
994
|
+
* (server closed the stream) and
|
|
995
|
+
* {@link SSESessionOptions.attemptReconnect} (transport error).
|
|
996
|
+
*
|
|
997
|
+
* ## Connection supersession
|
|
998
|
+
*
|
|
999
|
+
* Each {@link connect} will create a new controller if one does not exist. Otherwise,
|
|
1000
|
+
* it will return immediately.
|
|
1001
|
+
*/
|
|
1002
|
+
var SSESession = class SSESession extends EventEmitter {
|
|
1003
|
+
/**
|
|
1004
|
+
* Creates a session and waits until the first connection is established.
|
|
1005
|
+
*
|
|
1006
|
+
* @param url - The SSE endpoint URL.
|
|
1007
|
+
* @param options - Configuration merged with instance defaults.
|
|
1008
|
+
* @returns A connected session.
|
|
1009
|
+
* @throws When the initial connection cannot be established.
|
|
1010
|
+
*/
|
|
1011
|
+
static async create(url, options = {}) {
|
|
1012
|
+
const session = new SSESession(url, options);
|
|
1013
|
+
await session.connect();
|
|
1014
|
+
return session;
|
|
1015
|
+
}
|
|
1016
|
+
/**
|
|
1017
|
+
* Enables SSE resume semantics by sending `Last-Event-ID` on reconnect.
|
|
1018
|
+
*
|
|
1019
|
+
* Listens for incoming `"message"` events and remembers the most recent
|
|
1020
|
+
* {@link SSEvent.id}. On every subsequent connect or reconnect, the session's
|
|
1021
|
+
* {@link onRequest} hook is wrapped so that header is attached when an id is
|
|
1022
|
+
* known, allowing the server to replay only events the client has not yet
|
|
1023
|
+
* received.
|
|
1024
|
+
*
|
|
1025
|
+
* The existing {@link onRequest} callback is preserved and runs after the
|
|
1026
|
+
* header is applied, so auth or other header mutations continue to work.
|
|
1027
|
+
*
|
|
1028
|
+
* Since this relies on listening to the `"message"` event, this should ideally
|
|
1029
|
+
* be called before "{@link connect} is called so no messages (and no ids) are missed.
|
|
1030
|
+
*
|
|
1031
|
+
* ```ts
|
|
1032
|
+
* const session = new SSESession(url);
|
|
1033
|
+
* SSESession.addLastEventIdReconnect(session);
|
|
1034
|
+
* await session.connect();
|
|
1035
|
+
* // Reconnects send Last-Event-ID once an event with an id is received.
|
|
1036
|
+
* ```
|
|
1037
|
+
*
|
|
1038
|
+
* @param session - The session to instrument.
|
|
1039
|
+
* @returns The same session, for chaining.
|
|
1040
|
+
*/
|
|
1041
|
+
static addLastEventIdReconnect(session) {
|
|
1042
|
+
if (session.plugins.has(SSESession.addLastEventIdReconnect)) {
|
|
1043
|
+
const removeListener = session.plugins.get(SSESession.addLastEventIdReconnect);
|
|
1044
|
+
if (typeof removeListener !== "function") throw new SSESessionUnsubscribePluginNotAFunctionError("addLastEventIdReconnect");
|
|
1045
|
+
return {
|
|
1046
|
+
session,
|
|
1047
|
+
removeListener
|
|
1048
|
+
};
|
|
1049
|
+
}
|
|
1050
|
+
let lastEventId;
|
|
1051
|
+
const messageListener = (event) => {
|
|
1052
|
+
lastEventId = event.id ?? lastEventId;
|
|
1053
|
+
};
|
|
1054
|
+
session.on("message", messageListener);
|
|
1055
|
+
const originalOnRequest = session.options.onRequest;
|
|
1056
|
+
let enabled = true;
|
|
1057
|
+
session.options.onRequest = async (request) => {
|
|
1058
|
+
if (lastEventId && enabled) request.headers = {
|
|
1059
|
+
...request.headers,
|
|
1060
|
+
"Last-Event-ID": lastEventId
|
|
1061
|
+
};
|
|
1062
|
+
return originalOnRequest(request);
|
|
1063
|
+
};
|
|
1064
|
+
const removeListener = () => {
|
|
1065
|
+
enabled = false;
|
|
1066
|
+
session.off("message", messageListener);
|
|
1067
|
+
if (session.plugins.get(SSESession.addLastEventIdReconnect) === removeListener) session.plugins.delete(SSESession.addLastEventIdReconnect);
|
|
1068
|
+
};
|
|
1069
|
+
session.plugins.set(SSESession.addLastEventIdReconnect, removeListener);
|
|
1070
|
+
return {
|
|
1071
|
+
session,
|
|
1072
|
+
removeListener
|
|
1073
|
+
};
|
|
1074
|
+
}
|
|
1075
|
+
/**
|
|
1076
|
+
* Pauses and resumes a session based on browser tab visibility.
|
|
1077
|
+
*
|
|
1078
|
+
* Uses the Page Visibility API (`document.visibilitychange`):
|
|
1079
|
+
*
|
|
1080
|
+
* - **hidden** — {@link disconnect} stops the active fetch. {@link messages}
|
|
1081
|
+
* stays open; `"disconnected"` fires but `"closed"` does not.
|
|
1082
|
+
* - **visible** — {@link connect} re-establishes the stream if needed.
|
|
1083
|
+
*
|
|
1084
|
+
* This controller will not re-connect if the session was disconnected by something else.
|
|
1085
|
+
*
|
|
1086
|
+
* No-op in non-browser environments where `document` is undefined.
|
|
1087
|
+
*
|
|
1088
|
+
* @param session - The session to manage.
|
|
1089
|
+
*/
|
|
1090
|
+
static addBrowserVisibilityHandler(session) {
|
|
1091
|
+
if (typeof document === "undefined") return { session };
|
|
1092
|
+
if (session.plugins.has(SSESession.addBrowserVisibilityHandler)) {
|
|
1093
|
+
const removeListener = session.plugins.get(SSESession.addBrowserVisibilityHandler);
|
|
1094
|
+
if (typeof removeListener !== "function") throw new SSESessionUnsubscribePluginNotAFunctionError("addBrowserVisibilityHandler");
|
|
1095
|
+
return {
|
|
1096
|
+
session,
|
|
1097
|
+
removeListener
|
|
1098
|
+
};
|
|
1099
|
+
}
|
|
1100
|
+
const reconnectOnVisible = async () => {
|
|
1101
|
+
if (document.visibilityState === "visible") {
|
|
1102
|
+
document.removeEventListener("visibilitychange", reconnectOnVisible);
|
|
1103
|
+
await session.connect();
|
|
1104
|
+
}
|
|
1105
|
+
};
|
|
1106
|
+
const disableOnExternalDisconnect = ({ source }) => {
|
|
1107
|
+
if (source !== SSESession.addBrowserVisibilityHandler) document.removeEventListener("visibilitychange", reconnectOnVisible);
|
|
1108
|
+
};
|
|
1109
|
+
const handleVisibilityChange = async () => {
|
|
1110
|
+
if (!session.active) return;
|
|
1111
|
+
if (document.visibilityState === "hidden") {
|
|
1112
|
+
document.addEventListener("visibilitychange", reconnectOnVisible);
|
|
1113
|
+
session.disconnect({ source: SSESession.addBrowserVisibilityHandler });
|
|
1114
|
+
}
|
|
1115
|
+
};
|
|
1116
|
+
document.addEventListener("visibilitychange", handleVisibilityChange);
|
|
1117
|
+
session.on("beforeDisconnect", disableOnExternalDisconnect);
|
|
1118
|
+
const removeListener = () => {
|
|
1119
|
+
document.removeEventListener("visibilitychange", handleVisibilityChange);
|
|
1120
|
+
document.removeEventListener("visibilitychange", reconnectOnVisible);
|
|
1121
|
+
session.off("beforeDisconnect", disableOnExternalDisconnect);
|
|
1122
|
+
if (session.plugins.get(SSESession.addBrowserVisibilityHandler) === removeListener) session.plugins.delete(SSESession.addBrowserVisibilityHandler);
|
|
1123
|
+
};
|
|
1124
|
+
session.plugins.set(SSESession.addBrowserVisibilityHandler, removeListener);
|
|
1125
|
+
return {
|
|
1126
|
+
session,
|
|
1127
|
+
removeListener
|
|
1128
|
+
};
|
|
1129
|
+
}
|
|
1130
|
+
/** SSE endpoint URL for this session. */
|
|
1131
|
+
#url;
|
|
1132
|
+
/**
|
|
1133
|
+
* Per-instance configuration.
|
|
1134
|
+
*
|
|
1135
|
+
* Defaults live on the instance field (not a shared static) so each session
|
|
1136
|
+
* gets its own {@link SSEEventParser} and {@link ExponentialBackoff}.
|
|
1137
|
+
*/
|
|
1138
|
+
options = {
|
|
1139
|
+
fetch: (...args) => fetch(...args),
|
|
1140
|
+
method: "GET",
|
|
1141
|
+
headers: {
|
|
1142
|
+
Accept: "text/event-stream",
|
|
1143
|
+
"Cache-Control": "no-cache"
|
|
1144
|
+
},
|
|
1145
|
+
body: new FormData(),
|
|
1146
|
+
onRequest: (request) => Promise.resolve(request),
|
|
1147
|
+
retry: new ExponentialBackoff({ ...SSE_SESSION_EXPONENTIAL_BACKOFF_DEFAULTS }),
|
|
1148
|
+
attemptReconnect: SSE_SESSION_ATTEMPT_RECONNECT_DEFAULT,
|
|
1149
|
+
persistent: SSE_SESSION_PERSISTENT_DEFAULT,
|
|
1150
|
+
eventParser: new SSEEventParser()
|
|
1151
|
+
};
|
|
1152
|
+
/**
|
|
1153
|
+
* Registered plugins for the session.
|
|
1154
|
+
* This can be used to prevent duplicate plugins from being added to the session.
|
|
1155
|
+
* The value for a plugin is arbitrary, for example a function that removes the plugin.
|
|
1156
|
+
*/
|
|
1157
|
+
plugins = /* @__PURE__ */ new Map();
|
|
1158
|
+
/** AbortController for the currently active fetch, if any. */
|
|
1159
|
+
#connectionController = null;
|
|
1160
|
+
/** The server's requested retry interval in milliseconds (per SSE spec) if any */
|
|
1161
|
+
#retryInterval = null;
|
|
1162
|
+
/**
|
|
1163
|
+
* Asynchronous stream of parsed SSE events for the active connection.
|
|
1164
|
+
*
|
|
1165
|
+
* Stays open across {@link disconnect} and automatic reconnects so an existing
|
|
1166
|
+
* `for await` consumer keeps receiving events after visibility resumes.
|
|
1167
|
+
*
|
|
1168
|
+
* Closes when:
|
|
1169
|
+
* - the server ends the stream and {@link SSESessionOptions.persistent}
|
|
1170
|
+
* is false,
|
|
1171
|
+
* - {@link close} is called, or
|
|
1172
|
+
* - a transport error occurs with
|
|
1173
|
+
* {@link SSESessionOptions.attemptReconnect} disabled.
|
|
1174
|
+
*
|
|
1175
|
+
* A later {@link connect} replaces this with a new iterator when the
|
|
1176
|
+
* previous one was closed. Consumers should read from `session.messages`
|
|
1177
|
+
* rather than caching a reference across terminal disconnects.
|
|
1178
|
+
*/
|
|
1179
|
+
messages = new AsyncPushIterator();
|
|
1180
|
+
constructor(url, options = {}) {
|
|
1181
|
+
super();
|
|
1182
|
+
const { onConnected, onDisconnected, onError, ...restOptions } = options;
|
|
1183
|
+
this.#url = url;
|
|
1184
|
+
this.options = {
|
|
1185
|
+
...this.options,
|
|
1186
|
+
...restOptions,
|
|
1187
|
+
headers: {
|
|
1188
|
+
...this.options.headers,
|
|
1189
|
+
...options.headers
|
|
1190
|
+
}
|
|
1191
|
+
};
|
|
1192
|
+
if (onConnected) this.on("connected", onConnected);
|
|
1193
|
+
if (onDisconnected) this.on("disconnected", onDisconnected);
|
|
1194
|
+
if (onError) this.on("error", onError);
|
|
1195
|
+
}
|
|
1196
|
+
/**
|
|
1197
|
+
* Returns true if the session is active by checking if the controller is not null.
|
|
1198
|
+
*/
|
|
1199
|
+
get active() {
|
|
1200
|
+
return this.#connectionController !== null;
|
|
1201
|
+
}
|
|
1202
|
+
/**
|
|
1203
|
+
* Connects or reconnects to the SSE endpoint.
|
|
1204
|
+
*
|
|
1205
|
+
* Resolves once the HTTP stream is established and `"connected"` has been
|
|
1206
|
+
* emitted. Body reading continues asynchronously in the background via the
|
|
1207
|
+
* internal `#readStream` method.
|
|
1208
|
+
*
|
|
1209
|
+
* @throws When the fetch retry policy exhausts attempts or the connection
|
|
1210
|
+
* is superseded before the reader is handed off (in the latter case the
|
|
1211
|
+
* promise resolves without throwing).
|
|
1212
|
+
*/
|
|
1213
|
+
async connect() {
|
|
1214
|
+
if (this.#connectionController) return;
|
|
1215
|
+
this.#resetEventParser();
|
|
1216
|
+
this.#ensureMessageStreamOpen();
|
|
1217
|
+
const connectionController = new AbortController();
|
|
1218
|
+
this.#connectionController = connectionController;
|
|
1219
|
+
const { method, headers, body } = this.options;
|
|
1220
|
+
const fetchOptions = {
|
|
1221
|
+
method,
|
|
1222
|
+
headers: headers || {},
|
|
1223
|
+
signal: connectionController.signal,
|
|
1224
|
+
cache: "no-store"
|
|
1225
|
+
};
|
|
1226
|
+
if (method === "POST") fetchOptions.body = body || null;
|
|
1227
|
+
let reader;
|
|
1228
|
+
try {
|
|
1229
|
+
reader = await this.options.retry.run(() => this.#createReader(fetchOptions), { signal: connectionController.signal });
|
|
1230
|
+
} catch (error) {
|
|
1231
|
+
if (this.#connectionController !== connectionController) return;
|
|
1232
|
+
this.#connectionController = null;
|
|
1233
|
+
const normalizedError = normalizeError(error);
|
|
1234
|
+
this.emit("disconnected", {
|
|
1235
|
+
reason: "error",
|
|
1236
|
+
error: normalizedError
|
|
1237
|
+
});
|
|
1238
|
+
this.emit("error", normalizedError);
|
|
1239
|
+
this.#closeMessageStream();
|
|
1240
|
+
throw error;
|
|
1241
|
+
}
|
|
1242
|
+
if (this.#connectionController !== connectionController) {
|
|
1243
|
+
await reader.cancel();
|
|
1244
|
+
return;
|
|
1245
|
+
}
|
|
1246
|
+
this.emit("connected", void 0);
|
|
1247
|
+
this.#readStream(reader, connectionController).catch((error) => {
|
|
1248
|
+
this.emit("error", error);
|
|
1249
|
+
});
|
|
1250
|
+
}
|
|
1251
|
+
/**
|
|
1252
|
+
* Disconnects only the currently active transport.
|
|
1253
|
+
*
|
|
1254
|
+
* `beforeDisconnect` is emitted on every invocation, even when no
|
|
1255
|
+
* transport is currently active. This allows observers to react to
|
|
1256
|
+
* an explicit disconnect operation.
|
|
1257
|
+
*
|
|
1258
|
+
* @param context - Context describing the operation initiator.
|
|
1259
|
+
* @emits `beforeDisconnect` with the supplied operation context.
|
|
1260
|
+
* @emits `disconnected` with reason `"disconnect"` when an active
|
|
1261
|
+
* transport is actually disconnected.
|
|
1262
|
+
*/
|
|
1263
|
+
disconnect(context = {}) {
|
|
1264
|
+
this.emit("beforeDisconnect", context);
|
|
1265
|
+
if (this.#connectionController) {
|
|
1266
|
+
const connectionController = this.#connectionController;
|
|
1267
|
+
this.#connectionController = null;
|
|
1268
|
+
connectionController.abort();
|
|
1269
|
+
this.emit("disconnected", {
|
|
1270
|
+
reason: "disconnect",
|
|
1271
|
+
source: context.source
|
|
1272
|
+
});
|
|
1273
|
+
}
|
|
1274
|
+
this.#resetEventParser();
|
|
1275
|
+
}
|
|
1276
|
+
/**
|
|
1277
|
+
* Terminates the session and disables attached visibility handling until
|
|
1278
|
+
* the same instance is manually {@link connect connected} again.
|
|
1279
|
+
*
|
|
1280
|
+
* Closes {@link messages} and emits `"closed"`.
|
|
1281
|
+
*/
|
|
1282
|
+
close() {
|
|
1283
|
+
this.disconnect();
|
|
1284
|
+
this.#closeMessageStream();
|
|
1285
|
+
this.emit("closed", void 0);
|
|
1286
|
+
}
|
|
1287
|
+
/**
|
|
1288
|
+
* Performs the HTTP request and returns a reader for the response body.
|
|
1289
|
+
*
|
|
1290
|
+
* {@link SSESessionOptions.onRequest} may mutate headers (for example auth
|
|
1291
|
+
* tokens or `Last-Event-ID`) before the fetch runs.
|
|
1292
|
+
*/
|
|
1293
|
+
async #createReader(fetchOptions) {
|
|
1294
|
+
const requestOptions = await this.options.onRequest(fetchOptions);
|
|
1295
|
+
const response = await this.options.fetch(this.#url, requestOptions);
|
|
1296
|
+
if (!response.ok) {
|
|
1297
|
+
const responseCode = response.status;
|
|
1298
|
+
const error = new HTTPError(responseCode, await response.text());
|
|
1299
|
+
this.emit("error", error);
|
|
1300
|
+
throw error;
|
|
1301
|
+
}
|
|
1302
|
+
if (!response.body) {
|
|
1303
|
+
const error = new ResponseBodyNullError();
|
|
1304
|
+
this.emit("error", error);
|
|
1305
|
+
throw error;
|
|
1306
|
+
}
|
|
1307
|
+
return response.body.getReader();
|
|
1308
|
+
}
|
|
1309
|
+
/**
|
|
1310
|
+
* Reads bytes from an established stream until it ends, errors, or is
|
|
1311
|
+
* superseded by a newer connection.
|
|
1312
|
+
*/
|
|
1313
|
+
async #readStream(reader, connectionController) {
|
|
1314
|
+
try {
|
|
1315
|
+
while (this.#connectionController === connectionController) {
|
|
1316
|
+
const { done, value } = await reader.read();
|
|
1317
|
+
if (this.#connectionController !== connectionController) return;
|
|
1318
|
+
if (done) {
|
|
1319
|
+
this.#connectionController = null;
|
|
1320
|
+
this.emit("disconnected", { reason: "remote" });
|
|
1321
|
+
if (this.options.persistent) await this.connect();
|
|
1322
|
+
else this.#closeMessageStream();
|
|
1323
|
+
return;
|
|
1324
|
+
}
|
|
1325
|
+
if (!value) continue;
|
|
1326
|
+
for (const event of this.options.eventParser.parseEvents(value)) {
|
|
1327
|
+
if (event.retry) this.#retryInterval = event.retry;
|
|
1328
|
+
this.emit("message", event);
|
|
1329
|
+
this.messages.push(event);
|
|
1330
|
+
}
|
|
1331
|
+
}
|
|
1332
|
+
} catch (error) {
|
|
1333
|
+
if (connectionController !== this.#connectionController) return;
|
|
1334
|
+
this.#connectionController = null;
|
|
1335
|
+
const normalizedError = normalizeError(error);
|
|
1336
|
+
this.emit("disconnected", {
|
|
1337
|
+
reason: "error",
|
|
1338
|
+
error: normalizedError
|
|
1339
|
+
});
|
|
1340
|
+
if (connectionController.signal.aborted) return;
|
|
1341
|
+
this.emit("error", normalizedError);
|
|
1342
|
+
if (this.options.attemptReconnect) {
|
|
1343
|
+
if (this.#retryInterval) await new Promise((resolve) => setTimeout(resolve, this.#retryInterval));
|
|
1344
|
+
await this.connect();
|
|
1345
|
+
} else this.#closeMessageStream();
|
|
1346
|
+
}
|
|
1347
|
+
}
|
|
1348
|
+
/** Clears partial SSE frames left over from an abandoned transport. */
|
|
1349
|
+
#resetEventParser() {
|
|
1350
|
+
this.options.eventParser.reset();
|
|
1351
|
+
}
|
|
1352
|
+
/**
|
|
1353
|
+
* Creates a new {@link messages} iterator when the previous one was closed
|
|
1354
|
+
* by a terminal disconnect or server stream end.
|
|
1355
|
+
*/
|
|
1356
|
+
#ensureMessageStreamOpen() {
|
|
1357
|
+
if (!this.messages.closed) return;
|
|
1358
|
+
this.messages = new AsyncPushIterator();
|
|
1359
|
+
}
|
|
1360
|
+
/** Ends the message iteration loop for the current connection span. */
|
|
1361
|
+
#closeMessageStream() {
|
|
1362
|
+
if (this.messages.closed) return;
|
|
1363
|
+
this.messages.close();
|
|
1364
|
+
}
|
|
1365
|
+
};
|
|
1366
|
+
|
|
883
1367
|
//#endregion
|
|
884
1368
|
//#region source/template/errors.ts
|
|
885
1369
|
/**
|
|
@@ -2521,5 +3005,5 @@ const compileCashAssemblyString = (parameters) => {
|
|
|
2521
3005
|
};
|
|
2522
3006
|
|
|
2523
3007
|
//#endregion
|
|
2524
|
-
export { AsyncPushIterator, CashAssemblyBlockCommentUnclosedError, CashAssemblyCompilationFailedError, CashAssemblyEvaluationUnclosedError, CashAssemblyIdentifierCollisionError, CashAssemblyNumberNotSafeIntegerError, CashAssemblyPrimitiveMethodMissingError, CashAssemblyPrimitiveVariableMissingError, CashAssemblyQuotedLiteralUnclosedError, CashAssemblyRequiredVariableMissingError, CashAssemblyUnsupportedValueTypeError, CashAssemblyVariableTypeMismatchError, CashAssemblyVmNumberDecodeError, EventEmitter, ExponentialBackoff, ExponentialBackoffExternallyAbortable, ExponentialBackoffMaxRetriesHitError, ExponentialBackoffNonIntegerError, ExponentialBackoffNumberNotFiniteError, ExponentialBackoffNumberOutOfBoundsError, ExponentialBackoffNumberTooSmallError, ExponentialBackoffStoppedRetriesError, ExternallyAbortedExponentialBackoffExternalSignalAbortedError, ExternallyAbortedExponentialBackoffInternalSignalAbortedError, SSEEventParser, TemplateInvalidError, TemplateJsonMalformedError, TemplateSerializationFailedError, VIEW_PROPERTIES_DESCRIPTION_MAX_LENGTH, VIEW_PROPERTIES_ICON_MAX_LENGTH, VIEW_PROPERTIES_NAME_MAX_LENGTH, WaitForTimeoutError, bchVmVersionSchema, buildErrorDescription, collectVariablesUsingParseScript, compileCashAssemblyString, extendedJsonReplacer, extendedJsonReviver, extractCashAssemblyEvaluations, fromExtendedJson, generateTemplateIdentifier, isCashAssemblyExpression, parseTemplate, satoshisSchema, scriptToScriptHash, serializeTemplate, toExtendedJson, uint8ArraySchema, xoTemplateActionIntentSchema, xoTemplateActionRequirementsSchema, xoTemplateActionRoleRequirementsSchema, xoTemplateActionRoleSchema, xoTemplateActionSchema, xoTemplateAssetAmountsSchema, xoTemplateConstantSchema, xoTemplateDataSchema, xoTemplateDefaultsSchema, xoTemplateIconSchema, xoTemplateImportDefaultValueSchema, xoTemplateInputSchema, xoTemplateIntentSchema, xoTemplateLockingScriptIntentSchema, xoTemplateLockingScriptRoleSchema, xoTemplateLockingScriptSchema, xoTemplateLockingTypeSchema, xoTemplateNftCapabilitySchema, xoTemplateNonFungibleTokenDetailsSchema, xoTemplateOutputIntentSchema, xoTemplateOutputSchema, xoTemplatePrimitiveTypeSchema, xoTemplateResourceSchema, xoTemplateRoleSlotSchema, xoTemplateRoleSlotsRequirementsSchema, xoTemplateSchema, xoTemplateStateSchema, xoTemplateTokenSchema, xoTemplateTransactionInputSchema, xoTemplateTransactionOutputSchema, xoTemplateTransactionRoleDataSchema, xoTemplateTransactionSchema, xoTemplateVariableSchema, xoTemplateViewPropertiesSchema };
|
|
3008
|
+
export { AsyncPushIterator, CashAssemblyBlockCommentUnclosedError, CashAssemblyCompilationFailedError, CashAssemblyEvaluationUnclosedError, CashAssemblyIdentifierCollisionError, CashAssemblyNumberNotSafeIntegerError, CashAssemblyPrimitiveMethodMissingError, CashAssemblyPrimitiveVariableMissingError, CashAssemblyQuotedLiteralUnclosedError, CashAssemblyRequiredVariableMissingError, CashAssemblyUnsupportedValueTypeError, CashAssemblyVariableTypeMismatchError, CashAssemblyVmNumberDecodeError, EventEmitter, ExponentialBackoff, ExponentialBackoffExternallyAbortable, ExponentialBackoffMaxRetriesHitError, ExponentialBackoffNonIntegerError, ExponentialBackoffNumberNotFiniteError, ExponentialBackoffNumberOutOfBoundsError, ExponentialBackoffNumberTooSmallError, ExponentialBackoffStoppedRetriesError, ExternallyAbortedExponentialBackoffExternalSignalAbortedError, ExternallyAbortedExponentialBackoffInternalSignalAbortedError, SSEEventParser, SSESession, TemplateInvalidError, TemplateJsonMalformedError, TemplateSerializationFailedError, VIEW_PROPERTIES_DESCRIPTION_MAX_LENGTH, VIEW_PROPERTIES_ICON_MAX_LENGTH, VIEW_PROPERTIES_NAME_MAX_LENGTH, WaitForTimeoutError, bchVmVersionSchema, buildErrorDescription, collectVariablesUsingParseScript, compileCashAssemblyString, deepFreeze, extendedJsonReplacer, extendedJsonReviver, extractCashAssemblyEvaluations, fromExtendedJson, generateTemplateIdentifier, isCashAssemblyExpression, isWithinBounds, normalizeError, parseTemplate, satoshisSchema, scriptToScriptHash, serializeTemplate, toExtendedJson, uint8ArraySchema, xoTemplateActionIntentSchema, xoTemplateActionRequirementsSchema, xoTemplateActionRoleRequirementsSchema, xoTemplateActionRoleSchema, xoTemplateActionSchema, xoTemplateAssetAmountsSchema, xoTemplateConstantSchema, xoTemplateDataSchema, xoTemplateDefaultsSchema, xoTemplateIconSchema, xoTemplateImportDefaultValueSchema, xoTemplateInputSchema, xoTemplateIntentSchema, xoTemplateLockingScriptIntentSchema, xoTemplateLockingScriptRoleSchema, xoTemplateLockingScriptSchema, xoTemplateLockingTypeSchema, xoTemplateNftCapabilitySchema, xoTemplateNonFungibleTokenDetailsSchema, xoTemplateOutputIntentSchema, xoTemplateOutputSchema, xoTemplatePrimitiveTypeSchema, xoTemplateResourceSchema, xoTemplateRoleSlotSchema, xoTemplateRoleSlotsRequirementsSchema, xoTemplateSchema, xoTemplateStateSchema, xoTemplateTokenSchema, xoTemplateTransactionInputSchema, xoTemplateTransactionOutputSchema, xoTemplateTransactionRoleDataSchema, xoTemplateTransactionSchema, xoTemplateVariableSchema, xoTemplateViewPropertiesSchema };
|
|
2525
3009
|
//# sourceMappingURL=index.mjs.map
|