@dvina/sdk 4.0.212 → 4.0.215
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/adapters/angular/index.cjs +4 -4
- package/dist/adapters/angular/index.d.cts +2 -2
- package/dist/adapters/angular/index.d.ts +2 -2
- package/dist/adapters/angular/index.js +2 -2
- package/dist/billing/index.cjs +5 -5
- package/dist/billing/index.d.cts +2 -12
- package/dist/billing/index.d.ts +2 -12
- package/dist/billing/index.js +1 -1
- package/dist/{chunk-CW5W3LEG.js → chunk-BJDU4UX4.js} +290 -408
- package/dist/chunk-BJDU4UX4.js.map +1 -0
- package/dist/{chunk-5TAY5I34.js → chunk-Q5XGHH7D.js} +471 -209
- package/dist/chunk-Q5XGHH7D.js.map +1 -0
- package/dist/{chunk-4EESBP3A.cjs → chunk-XK5PW7IX.cjs} +311 -429
- package/dist/chunk-XK5PW7IX.cjs.map +1 -0
- package/dist/{chunk-BL3GFOZR.cjs → chunk-Y4OD5SDF.cjs} +473 -208
- package/dist/chunk-Y4OD5SDF.cjs.map +1 -0
- package/dist/{client-mOcqT66b.d.ts → client-Bn-0nJ8y.d.ts} +9 -2
- package/dist/{client-DRS_ycOv.d.cts → client-Ca2wlmC4.d.cts} +9 -2
- package/dist/{index-BKyI_YQS.d.cts → index-Ct2Mh0zl.d.cts} +1 -1
- package/dist/{index-BrsbEcIx.d.ts → index-D50r0Gl0.d.ts} +1 -1
- package/dist/index.cjs +387 -387
- package/dist/index.d.cts +9 -7
- package/dist/index.d.ts +9 -7
- package/dist/index.js +2 -2
- package/dist/pagination/index.d.cts +2 -2
- package/dist/pagination/index.d.ts +2 -2
- package/dist/{sync-engine-DN0tSqtg.d.cts → sync-engine-21OlgS5_.d.cts} +5 -16
- package/dist/{sync-engine-BmE8-rhX.d.ts → sync-engine-D35aV8Zc.d.ts} +5 -16
- package/package.json +1 -1
- package/dist/chunk-4EESBP3A.cjs.map +0 -1
- package/dist/chunk-5TAY5I34.js.map +0 -1
- package/dist/chunk-BL3GFOZR.cjs.map +0 -1
- package/dist/chunk-CW5W3LEG.js.map +0 -1
|
@@ -852,6 +852,26 @@ function hasMultilineItems(maybeArray) {
|
|
|
852
852
|
return (_maybeArray$some = maybeArray === null || maybeArray === void 0 ? void 0 : maybeArray.some((str) => str.includes("\n"))) !== null && _maybeArray$some !== void 0 ? _maybeArray$some : false;
|
|
853
853
|
}
|
|
854
854
|
|
|
855
|
+
// src/transport/core/token-refresh.ts
|
|
856
|
+
var inFlightRefreshes = /* @__PURE__ */ new WeakMap();
|
|
857
|
+
async function refreshTokenSingleFlight(getToken, failedToken) {
|
|
858
|
+
let inFlight = inFlightRefreshes.get(getToken);
|
|
859
|
+
if (!inFlight) {
|
|
860
|
+
const refreshPromise = getToken({ forceRefresh: true }).finally(() => {
|
|
861
|
+
if (inFlightRefreshes.get(getToken) === refreshPromise) {
|
|
862
|
+
inFlightRefreshes.delete(getToken);
|
|
863
|
+
}
|
|
864
|
+
});
|
|
865
|
+
inFlightRefreshes.set(getToken, refreshPromise);
|
|
866
|
+
inFlight = refreshPromise;
|
|
867
|
+
}
|
|
868
|
+
const refreshedToken = await inFlight;
|
|
869
|
+
if (failedToken && refreshedToken === failedToken) {
|
|
870
|
+
throw new chunkKV5SP7RP_cjs.DvinaAuthenticationError("Token refresh did not produce a new token", failedToken);
|
|
871
|
+
}
|
|
872
|
+
return refreshedToken;
|
|
873
|
+
}
|
|
874
|
+
|
|
855
875
|
// src/transport/http.ts
|
|
856
876
|
var printCache = /* @__PURE__ */ new WeakMap();
|
|
857
877
|
function printDocument(document) {
|
|
@@ -902,11 +922,22 @@ async function doFetch(options, document, variables) {
|
|
|
902
922
|
query: printDocument(document),
|
|
903
923
|
variables
|
|
904
924
|
});
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
925
|
+
let response;
|
|
926
|
+
try {
|
|
927
|
+
response = await fetch(options.url, {
|
|
928
|
+
method: "POST",
|
|
929
|
+
headers,
|
|
930
|
+
body
|
|
931
|
+
});
|
|
932
|
+
} catch (error) {
|
|
933
|
+
if (error instanceof Error && error.name === "AbortError") {
|
|
934
|
+
throw error;
|
|
935
|
+
}
|
|
936
|
+
throw new chunkKV5SP7RP_cjs.DvinaNetworkError(
|
|
937
|
+
error instanceof Error ? error.message : "Network request failed before reaching the server",
|
|
938
|
+
0
|
|
939
|
+
);
|
|
940
|
+
}
|
|
910
941
|
if (!response.ok) {
|
|
911
942
|
if (response.status === 401 || response.status === 403) {
|
|
912
943
|
throw new chunkKV5SP7RP_cjs.DvinaAuthenticationError(`Authentication failed with status ${response.status}`, token);
|
|
@@ -965,10 +996,7 @@ async function handleAuthRefresh(document, variables, refreshState, options, fai
|
|
|
965
996
|
if (!refreshState.isRefreshing) {
|
|
966
997
|
refreshState.isRefreshing = true;
|
|
967
998
|
try {
|
|
968
|
-
|
|
969
|
-
if (failedToken && newToken === failedToken) {
|
|
970
|
-
throw new chunkKV5SP7RP_cjs.DvinaAuthenticationError("Token refresh did not produce a new token");
|
|
971
|
-
}
|
|
999
|
+
await refreshTokenSingleFlight(options.getToken, failedToken);
|
|
972
1000
|
refreshState.pendingRequests.forEach((req) => req.resolve());
|
|
973
1001
|
refreshState.pendingRequests = [];
|
|
974
1002
|
refreshState.isRefreshing = false;
|
|
@@ -1005,15 +1033,430 @@ function sleep(ms) {
|
|
|
1005
1033
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
1006
1034
|
}
|
|
1007
1035
|
|
|
1036
|
+
// src/transport/core/connection-supervisor.ts
|
|
1037
|
+
var DEFAULT_AUTH_RETRY_DELAY_MS = 1e3;
|
|
1038
|
+
var DEFAULT_BASE_RECONNECT_DELAY_MS = 1e3;
|
|
1039
|
+
var DEFAULT_MAX_CONSECUTIVE_AUTH_FAILURES = 3;
|
|
1040
|
+
var DEFAULT_MAX_RECONNECT_DELAY_MS = 3e4;
|
|
1041
|
+
function isAbortError(error) {
|
|
1042
|
+
return error instanceof Error && error.name === "AbortError";
|
|
1043
|
+
}
|
|
1044
|
+
function now() {
|
|
1045
|
+
return Date.now();
|
|
1046
|
+
}
|
|
1047
|
+
var ConnectionSupervisor = class {
|
|
1048
|
+
constructor(options) {
|
|
1049
|
+
this.options = options;
|
|
1050
|
+
}
|
|
1051
|
+
abortController;
|
|
1052
|
+
abortReason;
|
|
1053
|
+
attemptPromise;
|
|
1054
|
+
closed = false;
|
|
1055
|
+
consecutiveAuthFailures = 0;
|
|
1056
|
+
forceRefreshNext = false;
|
|
1057
|
+
lastActivityAt = null;
|
|
1058
|
+
lastConnectedAt = null;
|
|
1059
|
+
lastError;
|
|
1060
|
+
reconnectAttempt = 0;
|
|
1061
|
+
reconnectTimer;
|
|
1062
|
+
restartAfterCurrentAttempt = false;
|
|
1063
|
+
state = "idle";
|
|
1064
|
+
staleTimer;
|
|
1065
|
+
shouldRun = false;
|
|
1066
|
+
get snapshot() {
|
|
1067
|
+
return {
|
|
1068
|
+
consecutiveAuthFailures: this.consecutiveAuthFailures,
|
|
1069
|
+
id: this.options.id,
|
|
1070
|
+
kind: this.options.kind,
|
|
1071
|
+
lastActivityAt: this.lastActivityAt,
|
|
1072
|
+
lastConnectedAt: this.lastConnectedAt,
|
|
1073
|
+
lastError: this.lastError,
|
|
1074
|
+
reconnectAttempt: this.reconnectAttempt,
|
|
1075
|
+
state: this.state
|
|
1076
|
+
};
|
|
1077
|
+
}
|
|
1078
|
+
async start() {
|
|
1079
|
+
if (this.closed) {
|
|
1080
|
+
throw new Error(`Connection '${this.options.kind}:${this.options.id}' is closed`);
|
|
1081
|
+
}
|
|
1082
|
+
this.shouldRun = true;
|
|
1083
|
+
this.clearReconnectTimer();
|
|
1084
|
+
if (this.attemptPromise && this.abortReason) {
|
|
1085
|
+
this.restartAfterCurrentAttempt = true;
|
|
1086
|
+
}
|
|
1087
|
+
return this.ensureAttempt();
|
|
1088
|
+
}
|
|
1089
|
+
stop() {
|
|
1090
|
+
this.shouldRun = false;
|
|
1091
|
+
this.restartAfterCurrentAttempt = false;
|
|
1092
|
+
this.clearReconnectTimer();
|
|
1093
|
+
this.clearStaleTimer();
|
|
1094
|
+
this.abort("manual");
|
|
1095
|
+
this.setState("idle");
|
|
1096
|
+
}
|
|
1097
|
+
close() {
|
|
1098
|
+
this.closed = true;
|
|
1099
|
+
this.shouldRun = false;
|
|
1100
|
+
this.restartAfterCurrentAttempt = false;
|
|
1101
|
+
this.clearReconnectTimer();
|
|
1102
|
+
this.clearStaleTimer();
|
|
1103
|
+
this.abort("manual");
|
|
1104
|
+
this.setState("closed");
|
|
1105
|
+
}
|
|
1106
|
+
reconnect() {
|
|
1107
|
+
if (this.closed) {
|
|
1108
|
+
return;
|
|
1109
|
+
}
|
|
1110
|
+
this.shouldRun = true;
|
|
1111
|
+
this.clearReconnectTimer();
|
|
1112
|
+
this.clearStaleTimer();
|
|
1113
|
+
if (this.abortController) {
|
|
1114
|
+
this.restartAfterCurrentAttempt = true;
|
|
1115
|
+
this.abort("restart");
|
|
1116
|
+
return;
|
|
1117
|
+
}
|
|
1118
|
+
void this.ensureAttempt();
|
|
1119
|
+
}
|
|
1120
|
+
ensureAttempt() {
|
|
1121
|
+
if (!this.shouldRun || this.closed) {
|
|
1122
|
+
return Promise.resolve();
|
|
1123
|
+
}
|
|
1124
|
+
if (!this.attemptPromise) {
|
|
1125
|
+
const attemptPromise = this.runAttempt().finally(() => {
|
|
1126
|
+
if (this.attemptPromise === attemptPromise) {
|
|
1127
|
+
this.attemptPromise = void 0;
|
|
1128
|
+
}
|
|
1129
|
+
});
|
|
1130
|
+
this.attemptPromise = attemptPromise;
|
|
1131
|
+
}
|
|
1132
|
+
return this.attemptPromise;
|
|
1133
|
+
}
|
|
1134
|
+
async runAttempt() {
|
|
1135
|
+
if (!this.shouldRun || this.closed) {
|
|
1136
|
+
return;
|
|
1137
|
+
}
|
|
1138
|
+
const isReconnect = this.lastConnectedAt !== null || this.reconnectAttempt > 0;
|
|
1139
|
+
this.setState("connecting");
|
|
1140
|
+
this.lastError = void 0;
|
|
1141
|
+
this.abortReason = void 0;
|
|
1142
|
+
const abortController = new AbortController();
|
|
1143
|
+
this.abortController = abortController;
|
|
1144
|
+
try {
|
|
1145
|
+
const token = this.forceRefreshNext ? await refreshTokenSingleFlight(this.options.getToken) : await this.options.getToken(void 0);
|
|
1146
|
+
this.forceRefreshNext = false;
|
|
1147
|
+
await this.options.dial({
|
|
1148
|
+
isReconnect,
|
|
1149
|
+
markActivity: () => this.markActivity(),
|
|
1150
|
+
markConnected: () => this.markConnected(),
|
|
1151
|
+
requestTokenRefresh: () => {
|
|
1152
|
+
this.forceRefreshNext = true;
|
|
1153
|
+
},
|
|
1154
|
+
signal: abortController.signal,
|
|
1155
|
+
token
|
|
1156
|
+
});
|
|
1157
|
+
if (!this.shouldRun || this.closed) {
|
|
1158
|
+
return;
|
|
1159
|
+
}
|
|
1160
|
+
this.scheduleReconnect("end");
|
|
1161
|
+
} catch (error) {
|
|
1162
|
+
if (isAbortError(error)) {
|
|
1163
|
+
if (this.abortReason === "stale" && this.shouldRun && !this.closed) {
|
|
1164
|
+
this.scheduleReconnect("end");
|
|
1165
|
+
}
|
|
1166
|
+
return;
|
|
1167
|
+
}
|
|
1168
|
+
if (!this.shouldRun || this.closed) {
|
|
1169
|
+
return;
|
|
1170
|
+
}
|
|
1171
|
+
this.handleFailure(error);
|
|
1172
|
+
} finally {
|
|
1173
|
+
if (this.abortController === abortController) {
|
|
1174
|
+
this.abortController = void 0;
|
|
1175
|
+
}
|
|
1176
|
+
this.abortReason = void 0;
|
|
1177
|
+
if (this.restartAfterCurrentAttempt && this.shouldRun && !this.closed) {
|
|
1178
|
+
this.restartAfterCurrentAttempt = false;
|
|
1179
|
+
queueMicrotask(() => {
|
|
1180
|
+
if (!this.closed && this.shouldRun) {
|
|
1181
|
+
void this.ensureAttempt();
|
|
1182
|
+
}
|
|
1183
|
+
});
|
|
1184
|
+
}
|
|
1185
|
+
}
|
|
1186
|
+
}
|
|
1187
|
+
handleFailure(error) {
|
|
1188
|
+
this.lastError = error;
|
|
1189
|
+
const isAuthError2 = this.options.isAuthError?.(error) ?? false;
|
|
1190
|
+
if (isAuthError2) {
|
|
1191
|
+
this.consecutiveAuthFailures += 1;
|
|
1192
|
+
this.forceRefreshNext = true;
|
|
1193
|
+
this.setState("reconnecting");
|
|
1194
|
+
if (this.consecutiveAuthFailures >= (this.options.maxConsecutiveAuthFailures ?? DEFAULT_MAX_CONSECUTIVE_AUTH_FAILURES)) {
|
|
1195
|
+
this.shouldRun = false;
|
|
1196
|
+
this.clearReconnectTimer();
|
|
1197
|
+
this.clearStaleTimer();
|
|
1198
|
+
this.setState("failed");
|
|
1199
|
+
this.options.onFatalError?.(error);
|
|
1200
|
+
return;
|
|
1201
|
+
}
|
|
1202
|
+
this.scheduleReconnect("auth");
|
|
1203
|
+
return;
|
|
1204
|
+
}
|
|
1205
|
+
this.consecutiveAuthFailures = 0;
|
|
1206
|
+
if (typeof navigator !== "undefined" && "onLine" in navigator && navigator.onLine === false) {
|
|
1207
|
+
this.setState("offline");
|
|
1208
|
+
} else {
|
|
1209
|
+
this.setState("reconnecting");
|
|
1210
|
+
}
|
|
1211
|
+
this.scheduleReconnect("failure");
|
|
1212
|
+
}
|
|
1213
|
+
scheduleReconnect(reason) {
|
|
1214
|
+
if (!this.shouldRun || this.closed) {
|
|
1215
|
+
return;
|
|
1216
|
+
}
|
|
1217
|
+
this.clearReconnectTimer();
|
|
1218
|
+
this.clearStaleTimer();
|
|
1219
|
+
const delay = reason === "auth" ? this.options.authRetryDelayMs ?? DEFAULT_AUTH_RETRY_DELAY_MS : reason === "failure" ? this.nextReconnectDelay() : this.options.baseReconnectDelayMs ?? DEFAULT_BASE_RECONNECT_DELAY_MS;
|
|
1220
|
+
this.reconnectTimer = setTimeout(() => {
|
|
1221
|
+
this.reconnectTimer = void 0;
|
|
1222
|
+
void this.ensureAttempt();
|
|
1223
|
+
}, delay);
|
|
1224
|
+
}
|
|
1225
|
+
nextReconnectDelay() {
|
|
1226
|
+
const baseDelay = this.options.baseReconnectDelayMs ?? DEFAULT_BASE_RECONNECT_DELAY_MS;
|
|
1227
|
+
const maxDelay = this.options.maxReconnectDelayMs ?? DEFAULT_MAX_RECONNECT_DELAY_MS;
|
|
1228
|
+
const cappedDelay = Math.min(baseDelay * Math.pow(2, this.reconnectAttempt), maxDelay);
|
|
1229
|
+
this.reconnectAttempt += 1;
|
|
1230
|
+
if (this.options.jitter === "full") {
|
|
1231
|
+
return Math.floor(Math.random() * cappedDelay);
|
|
1232
|
+
}
|
|
1233
|
+
return cappedDelay;
|
|
1234
|
+
}
|
|
1235
|
+
markConnected() {
|
|
1236
|
+
this.consecutiveAuthFailures = 0;
|
|
1237
|
+
this.reconnectAttempt = 0;
|
|
1238
|
+
this.lastConnectedAt = now();
|
|
1239
|
+
this.setState("connected");
|
|
1240
|
+
this.markActivity();
|
|
1241
|
+
}
|
|
1242
|
+
markActivity() {
|
|
1243
|
+
this.lastActivityAt = now();
|
|
1244
|
+
this.armStaleTimer();
|
|
1245
|
+
}
|
|
1246
|
+
armStaleTimer() {
|
|
1247
|
+
this.clearStaleTimer();
|
|
1248
|
+
const staleTimeoutMs = this.options.staleTimeoutMs;
|
|
1249
|
+
if (!staleTimeoutMs || staleTimeoutMs <= 0 || this.state !== "connected") {
|
|
1250
|
+
return;
|
|
1251
|
+
}
|
|
1252
|
+
this.staleTimer = setTimeout(() => {
|
|
1253
|
+
if (!this.shouldRun || this.closed || !this.abortController) {
|
|
1254
|
+
return;
|
|
1255
|
+
}
|
|
1256
|
+
this.setState("stale");
|
|
1257
|
+
this.abort("stale");
|
|
1258
|
+
}, staleTimeoutMs);
|
|
1259
|
+
}
|
|
1260
|
+
clearReconnectTimer() {
|
|
1261
|
+
if (!this.reconnectTimer) {
|
|
1262
|
+
return;
|
|
1263
|
+
}
|
|
1264
|
+
clearTimeout(this.reconnectTimer);
|
|
1265
|
+
this.reconnectTimer = void 0;
|
|
1266
|
+
}
|
|
1267
|
+
clearStaleTimer() {
|
|
1268
|
+
if (!this.staleTimer) {
|
|
1269
|
+
return;
|
|
1270
|
+
}
|
|
1271
|
+
clearTimeout(this.staleTimer);
|
|
1272
|
+
this.staleTimer = void 0;
|
|
1273
|
+
}
|
|
1274
|
+
abort(reason) {
|
|
1275
|
+
if (!this.abortController) {
|
|
1276
|
+
return;
|
|
1277
|
+
}
|
|
1278
|
+
this.abortReason = reason;
|
|
1279
|
+
this.abortController.abort();
|
|
1280
|
+
this.abortController = void 0;
|
|
1281
|
+
}
|
|
1282
|
+
setState(nextState) {
|
|
1283
|
+
if (this.state === nextState) {
|
|
1284
|
+
return;
|
|
1285
|
+
}
|
|
1286
|
+
this.state = nextState;
|
|
1287
|
+
this.options.onStateChange?.(this.snapshot);
|
|
1288
|
+
}
|
|
1289
|
+
};
|
|
1290
|
+
|
|
1291
|
+
// src/transport/core/sse-stream.ts
|
|
1292
|
+
async function consumeSseStream(body, signal, handlers) {
|
|
1293
|
+
const reader = body.getReader();
|
|
1294
|
+
const decoder = new TextDecoder();
|
|
1295
|
+
let buffer = "";
|
|
1296
|
+
try {
|
|
1297
|
+
while (!signal.aborted) {
|
|
1298
|
+
const { done, value } = await reader.read();
|
|
1299
|
+
if (done) {
|
|
1300
|
+
break;
|
|
1301
|
+
}
|
|
1302
|
+
handlers.onChunk?.();
|
|
1303
|
+
buffer += decoder.decode(value, { stream: true });
|
|
1304
|
+
const parts = buffer.split(/(?:\r\n|\r|\n){2}/);
|
|
1305
|
+
buffer = parts.pop() ?? "";
|
|
1306
|
+
for (const part of parts) {
|
|
1307
|
+
handlers.onEventBlock(part.trim());
|
|
1308
|
+
}
|
|
1309
|
+
}
|
|
1310
|
+
} finally {
|
|
1311
|
+
reader.releaseLock();
|
|
1312
|
+
}
|
|
1313
|
+
}
|
|
1314
|
+
|
|
1008
1315
|
// src/billing/client.ts
|
|
1009
1316
|
var INITIAL_RECONNECT_DELAY_MS = 1e3;
|
|
1010
1317
|
var MAX_RECONNECT_DELAY_MS = 3e4;
|
|
1011
|
-
var RECONNECT_BACKOFF_MULTIPLIER = 2;
|
|
1012
1318
|
var MAX_CONSECUTIVE_AUTH_FAILURES = 3;
|
|
1319
|
+
var STALE_CONNECTION_TIMEOUT_MS = 6e4;
|
|
1320
|
+
var BillingAuthStreamError = class extends Error {
|
|
1321
|
+
constructor(status) {
|
|
1322
|
+
super(`Billing SSE auth failed: ${status}`);
|
|
1323
|
+
this.status = status;
|
|
1324
|
+
this.name = "BillingAuthStreamError";
|
|
1325
|
+
}
|
|
1326
|
+
};
|
|
1327
|
+
var BillingSubscriptionStream = class {
|
|
1328
|
+
constructor(options, onClose) {
|
|
1329
|
+
this.options = options;
|
|
1330
|
+
this.onClose = onClose;
|
|
1331
|
+
this.supervisor = new ConnectionSupervisor({
|
|
1332
|
+
authRetryDelayMs: INITIAL_RECONNECT_DELAY_MS,
|
|
1333
|
+
baseReconnectDelayMs: INITIAL_RECONNECT_DELAY_MS,
|
|
1334
|
+
getToken: options.getToken,
|
|
1335
|
+
id: `billing-${Math.random().toString(36).slice(2)}`,
|
|
1336
|
+
isAuthError: (error) => error instanceof BillingAuthStreamError,
|
|
1337
|
+
kind: "billing-sse",
|
|
1338
|
+
maxConsecutiveAuthFailures: MAX_CONSECUTIVE_AUTH_FAILURES,
|
|
1339
|
+
maxReconnectDelayMs: MAX_RECONNECT_DELAY_MS,
|
|
1340
|
+
onFatalError: () => {
|
|
1341
|
+
this.options.onError?.(
|
|
1342
|
+
new chunkKV5SP7RP_cjs.DvinaAuthenticationError("Billing SSE stream authentication failed permanently")
|
|
1343
|
+
);
|
|
1344
|
+
this.close();
|
|
1345
|
+
},
|
|
1346
|
+
staleTimeoutMs: STALE_CONNECTION_TIMEOUT_MS,
|
|
1347
|
+
dial: async (context) => {
|
|
1348
|
+
const response = await fetch(this.options.billingSseUrl, {
|
|
1349
|
+
method: "GET",
|
|
1350
|
+
headers: {
|
|
1351
|
+
Authorization: `Bearer ${context.token}`,
|
|
1352
|
+
Accept: "text/event-stream",
|
|
1353
|
+
"Cache-Control": "no-cache"
|
|
1354
|
+
},
|
|
1355
|
+
signal: context.signal
|
|
1356
|
+
});
|
|
1357
|
+
if (!response.ok) {
|
|
1358
|
+
if (response.status === 401 || response.status === 403) {
|
|
1359
|
+
throw new BillingAuthStreamError(response.status);
|
|
1360
|
+
}
|
|
1361
|
+
throw new Error(`Billing SSE failed: ${response.status} ${response.statusText}`);
|
|
1362
|
+
}
|
|
1363
|
+
if (!response.body) {
|
|
1364
|
+
throw new Error("Billing SSE response has no body");
|
|
1365
|
+
}
|
|
1366
|
+
context.markConnected();
|
|
1367
|
+
await consumeSseStream(response.body, context.signal, {
|
|
1368
|
+
onChunk: () => context.markActivity(),
|
|
1369
|
+
onEventBlock: (block2) => this.processEventBlock(block2, context.requestTokenRefresh)
|
|
1370
|
+
});
|
|
1371
|
+
}
|
|
1372
|
+
});
|
|
1373
|
+
}
|
|
1374
|
+
abortFinished = false;
|
|
1375
|
+
queue = [];
|
|
1376
|
+
pendingResolve = null;
|
|
1377
|
+
supervisor;
|
|
1378
|
+
start() {
|
|
1379
|
+
void this.supervisor.start();
|
|
1380
|
+
}
|
|
1381
|
+
reconnect() {
|
|
1382
|
+
if (this.abortFinished) {
|
|
1383
|
+
return;
|
|
1384
|
+
}
|
|
1385
|
+
this.supervisor.reconnect();
|
|
1386
|
+
}
|
|
1387
|
+
next() {
|
|
1388
|
+
if (this.queue.length > 0) {
|
|
1389
|
+
return Promise.resolve({ value: this.queue.shift(), done: false });
|
|
1390
|
+
}
|
|
1391
|
+
if (this.abortFinished) {
|
|
1392
|
+
return Promise.resolve({ value: void 0, done: true });
|
|
1393
|
+
}
|
|
1394
|
+
return new Promise((resolve) => {
|
|
1395
|
+
this.pendingResolve = resolve;
|
|
1396
|
+
});
|
|
1397
|
+
}
|
|
1398
|
+
return() {
|
|
1399
|
+
this.close();
|
|
1400
|
+
return Promise.resolve({ value: void 0, done: true });
|
|
1401
|
+
}
|
|
1402
|
+
[Symbol.asyncIterator]() {
|
|
1403
|
+
return this;
|
|
1404
|
+
}
|
|
1405
|
+
close() {
|
|
1406
|
+
if (this.abortFinished) {
|
|
1407
|
+
return;
|
|
1408
|
+
}
|
|
1409
|
+
this.abortFinished = true;
|
|
1410
|
+
this.supervisor.close();
|
|
1411
|
+
if (this.pendingResolve) {
|
|
1412
|
+
this.pendingResolve({ value: void 0, done: true });
|
|
1413
|
+
this.pendingResolve = null;
|
|
1414
|
+
}
|
|
1415
|
+
this.onClose(this);
|
|
1416
|
+
}
|
|
1417
|
+
push(value) {
|
|
1418
|
+
if (this.abortFinished) {
|
|
1419
|
+
return;
|
|
1420
|
+
}
|
|
1421
|
+
if (this.pendingResolve) {
|
|
1422
|
+
this.pendingResolve({ value, done: false });
|
|
1423
|
+
this.pendingResolve = null;
|
|
1424
|
+
return;
|
|
1425
|
+
}
|
|
1426
|
+
this.queue.length = 0;
|
|
1427
|
+
this.queue.push(value);
|
|
1428
|
+
}
|
|
1429
|
+
processEventBlock(block2, requestTokenRefresh) {
|
|
1430
|
+
if (!block2 || block2.startsWith(":")) {
|
|
1431
|
+
return;
|
|
1432
|
+
}
|
|
1433
|
+
let eventType;
|
|
1434
|
+
const dataLines = [];
|
|
1435
|
+
for (const line of block2.split(/\r\n|\r|\n/)) {
|
|
1436
|
+
if (line.startsWith("event: ")) {
|
|
1437
|
+
eventType = line.slice(7).trim();
|
|
1438
|
+
} else if (line.startsWith("data: ")) {
|
|
1439
|
+
dataLines.push(line.slice(6));
|
|
1440
|
+
}
|
|
1441
|
+
}
|
|
1442
|
+
if (eventType === "token-expired") {
|
|
1443
|
+
requestTokenRefresh();
|
|
1444
|
+
throw new BillingAuthStreamError(401);
|
|
1445
|
+
}
|
|
1446
|
+
if (eventType !== "subscription" || dataLines.length === 0) {
|
|
1447
|
+
return;
|
|
1448
|
+
}
|
|
1449
|
+
try {
|
|
1450
|
+
const parsed = JSON.parse(dataLines.join("\n"));
|
|
1451
|
+
this.push(parsed);
|
|
1452
|
+
} catch {
|
|
1453
|
+
}
|
|
1454
|
+
}
|
|
1455
|
+
};
|
|
1013
1456
|
var BillingClient = class {
|
|
1014
1457
|
http;
|
|
1015
1458
|
options;
|
|
1016
|
-
|
|
1459
|
+
activeUpdateStreams = /* @__PURE__ */ new Set();
|
|
1017
1460
|
constructor(options) {
|
|
1018
1461
|
this.options = options;
|
|
1019
1462
|
this.http = createHttpTransport({
|
|
@@ -1028,12 +1471,16 @@ var BillingClient = class {
|
|
|
1028
1471
|
* Call this on logout or SDK teardown to prevent lingering connections.
|
|
1029
1472
|
*/
|
|
1030
1473
|
destroy() {
|
|
1031
|
-
for (const
|
|
1032
|
-
|
|
1474
|
+
for (const stream of Array.from(this.activeUpdateStreams)) {
|
|
1475
|
+
stream.close();
|
|
1476
|
+
}
|
|
1477
|
+
this.activeUpdateStreams.clear();
|
|
1478
|
+
}
|
|
1479
|
+
reconnectAll() {
|
|
1480
|
+
for (const stream of this.activeUpdateStreams) {
|
|
1481
|
+
stream.reconnect();
|
|
1033
1482
|
}
|
|
1034
|
-
this.activeAbortControllers.clear();
|
|
1035
1483
|
}
|
|
1036
|
-
// ─── Plans ───────────────────────────────────────────────────────────────
|
|
1037
1484
|
get plans() {
|
|
1038
1485
|
return {
|
|
1039
1486
|
list: async () => {
|
|
@@ -1054,7 +1501,6 @@ var BillingClient = class {
|
|
|
1054
1501
|
}
|
|
1055
1502
|
};
|
|
1056
1503
|
}
|
|
1057
|
-
// ─── Subscription ────────────────────────────────────────────────────────
|
|
1058
1504
|
get subscription() {
|
|
1059
1505
|
return {
|
|
1060
1506
|
get: async () => {
|
|
@@ -1093,17 +1539,16 @@ var BillingClient = class {
|
|
|
1093
1539
|
const result = await this.request(ReleaseScheduleDocument);
|
|
1094
1540
|
return result.releaseSchedule;
|
|
1095
1541
|
},
|
|
1096
|
-
/**
|
|
1097
|
-
* Subscribe to real-time subscription updates via SSE.
|
|
1098
|
-
* Returns an AsyncIterable that yields WorkspaceSubscription snapshots.
|
|
1099
|
-
* Automatically reconnects with exponential backoff and token refresh on auth errors.
|
|
1100
|
-
*/
|
|
1101
1542
|
updates: () => {
|
|
1102
|
-
|
|
1543
|
+
const stream = new BillingSubscriptionStream(this.options, (closedStream) => {
|
|
1544
|
+
this.activeUpdateStreams.delete(closedStream);
|
|
1545
|
+
});
|
|
1546
|
+
this.activeUpdateStreams.add(stream);
|
|
1547
|
+
stream.start();
|
|
1548
|
+
return stream;
|
|
1103
1549
|
}
|
|
1104
1550
|
};
|
|
1105
1551
|
}
|
|
1106
|
-
// ─── Seats ───────────────────────────────────────────────────────────────
|
|
1107
1552
|
get seats() {
|
|
1108
1553
|
return {
|
|
1109
1554
|
add: async (variables) => {
|
|
@@ -1116,7 +1561,6 @@ var BillingClient = class {
|
|
|
1116
1561
|
}
|
|
1117
1562
|
};
|
|
1118
1563
|
}
|
|
1119
|
-
// ─── Sessions ────────────────────────────────────────────────────────────
|
|
1120
1564
|
get session() {
|
|
1121
1565
|
return {
|
|
1122
1566
|
createSession: async (variables) => {
|
|
@@ -1129,7 +1573,6 @@ var BillingClient = class {
|
|
|
1129
1573
|
}
|
|
1130
1574
|
};
|
|
1131
1575
|
}
|
|
1132
|
-
// ─── Management ──────────────────────────────────────────────────────────
|
|
1133
1576
|
get management() {
|
|
1134
1577
|
return {
|
|
1135
1578
|
subscription: async (variables) => {
|
|
@@ -1154,197 +1597,19 @@ var BillingClient = class {
|
|
|
1154
1597
|
}
|
|
1155
1598
|
};
|
|
1156
1599
|
}
|
|
1157
|
-
// ─── Private ─────────────────────────────────────────────────────────────
|
|
1158
1600
|
request(document, variables) {
|
|
1159
1601
|
return this.http.request(document, variables);
|
|
1160
1602
|
}
|
|
1161
|
-
/**
|
|
1162
|
-
* Create an SSE stream that connects to the billing SSE endpoint.
|
|
1163
|
-
* Reconnects automatically with exponential backoff.
|
|
1164
|
-
* On 401/403, refreshes the token and retries with a short delay.
|
|
1165
|
-
*/
|
|
1166
|
-
createSseStream() {
|
|
1167
|
-
const options = this.options;
|
|
1168
|
-
const activeAbortControllers = this.activeAbortControllers;
|
|
1169
|
-
return {
|
|
1170
|
-
[Symbol.asyncIterator]() {
|
|
1171
|
-
const queue = [];
|
|
1172
|
-
let resolve = null;
|
|
1173
|
-
let done = false;
|
|
1174
|
-
let abortController;
|
|
1175
|
-
let reconnectTimer;
|
|
1176
|
-
let reconnectDelay = INITIAL_RECONNECT_DELAY_MS;
|
|
1177
|
-
let shouldReconnect = true;
|
|
1178
|
-
let needsForceRefresh = false;
|
|
1179
|
-
let consecutiveAuthFailures = 0;
|
|
1180
|
-
function push(value) {
|
|
1181
|
-
if (resolve) {
|
|
1182
|
-
resolve({ value, done: false });
|
|
1183
|
-
resolve = null;
|
|
1184
|
-
} else {
|
|
1185
|
-
queue.length = 0;
|
|
1186
|
-
queue.push(value);
|
|
1187
|
-
}
|
|
1188
|
-
}
|
|
1189
|
-
async function connect() {
|
|
1190
|
-
if (!shouldReconnect) return;
|
|
1191
|
-
if (abortController) {
|
|
1192
|
-
activeAbortControllers.delete(abortController);
|
|
1193
|
-
}
|
|
1194
|
-
abortController = new AbortController();
|
|
1195
|
-
activeAbortControllers.add(abortController);
|
|
1196
|
-
try {
|
|
1197
|
-
const forceRefresh = needsForceRefresh;
|
|
1198
|
-
needsForceRefresh = false;
|
|
1199
|
-
const token = await options.getToken(forceRefresh ? { forceRefresh: true } : void 0);
|
|
1200
|
-
const response = await fetch(options.billingSseUrl, {
|
|
1201
|
-
method: "GET",
|
|
1202
|
-
headers: {
|
|
1203
|
-
Authorization: `Bearer ${token}`,
|
|
1204
|
-
Accept: "text/event-stream",
|
|
1205
|
-
"Cache-Control": "no-cache"
|
|
1206
|
-
},
|
|
1207
|
-
signal: abortController.signal
|
|
1208
|
-
});
|
|
1209
|
-
if (!response.ok) {
|
|
1210
|
-
if (response.status === 401 || response.status === 403) {
|
|
1211
|
-
consecutiveAuthFailures++;
|
|
1212
|
-
if (consecutiveAuthFailures >= MAX_CONSECUTIVE_AUTH_FAILURES) {
|
|
1213
|
-
done = true;
|
|
1214
|
-
shouldReconnect = false;
|
|
1215
|
-
options.onError?.(
|
|
1216
|
-
new chunkKV5SP7RP_cjs.DvinaAuthenticationError(
|
|
1217
|
-
"Billing SSE stream authentication failed permanently"
|
|
1218
|
-
)
|
|
1219
|
-
);
|
|
1220
|
-
if (resolve) {
|
|
1221
|
-
resolve({ value: void 0, done: true });
|
|
1222
|
-
resolve = null;
|
|
1223
|
-
}
|
|
1224
|
-
return;
|
|
1225
|
-
}
|
|
1226
|
-
needsForceRefresh = true;
|
|
1227
|
-
scheduleReconnect(INITIAL_RECONNECT_DELAY_MS);
|
|
1228
|
-
return;
|
|
1229
|
-
}
|
|
1230
|
-
throw new Error(`Billing SSE failed: ${response.status} ${response.statusText}`);
|
|
1231
|
-
}
|
|
1232
|
-
if (!response.body) {
|
|
1233
|
-
throw new Error("Billing SSE response has no body");
|
|
1234
|
-
}
|
|
1235
|
-
consecutiveAuthFailures = 0;
|
|
1236
|
-
reconnectDelay = INITIAL_RECONNECT_DELAY_MS;
|
|
1237
|
-
await parseSseStream(response.body, abortController.signal);
|
|
1238
|
-
} catch (err) {
|
|
1239
|
-
if (err.name === "AbortError") return;
|
|
1240
|
-
if (shouldReconnect) {
|
|
1241
|
-
scheduleReconnect(reconnectDelay);
|
|
1242
|
-
reconnectDelay = Math.min(
|
|
1243
|
-
reconnectDelay * RECONNECT_BACKOFF_MULTIPLIER,
|
|
1244
|
-
MAX_RECONNECT_DELAY_MS
|
|
1245
|
-
);
|
|
1246
|
-
}
|
|
1247
|
-
}
|
|
1248
|
-
}
|
|
1249
|
-
async function parseSseStream(body, signal) {
|
|
1250
|
-
const reader = body.getReader();
|
|
1251
|
-
const decoder = new TextDecoder();
|
|
1252
|
-
let buffer = "";
|
|
1253
|
-
try {
|
|
1254
|
-
while (!signal.aborted && shouldReconnect) {
|
|
1255
|
-
const { done: streamDone, value } = await reader.read();
|
|
1256
|
-
if (streamDone) break;
|
|
1257
|
-
buffer += decoder.decode(value, { stream: true });
|
|
1258
|
-
const parts = buffer.split(/(?:\r\n|\r|\n){2}/);
|
|
1259
|
-
buffer = parts.pop() ?? "";
|
|
1260
|
-
for (const part of parts) {
|
|
1261
|
-
processEventBlock(part.trim());
|
|
1262
|
-
}
|
|
1263
|
-
}
|
|
1264
|
-
} finally {
|
|
1265
|
-
reader.releaseLock();
|
|
1266
|
-
}
|
|
1267
|
-
if (shouldReconnect) {
|
|
1268
|
-
scheduleReconnect(reconnectDelay);
|
|
1269
|
-
}
|
|
1270
|
-
}
|
|
1271
|
-
function processEventBlock(block2) {
|
|
1272
|
-
if (!block2 || block2.startsWith(":")) return;
|
|
1273
|
-
let eventType;
|
|
1274
|
-
let eventData;
|
|
1275
|
-
for (const line of block2.split(/\r\n|\r|\n/)) {
|
|
1276
|
-
if (line.startsWith("event: ")) {
|
|
1277
|
-
eventType = line.slice(7).trim();
|
|
1278
|
-
} else if (line.startsWith("data: ")) {
|
|
1279
|
-
eventData = line.slice(6);
|
|
1280
|
-
}
|
|
1281
|
-
}
|
|
1282
|
-
if (eventType === "token-expired") {
|
|
1283
|
-
needsForceRefresh = true;
|
|
1284
|
-
return;
|
|
1285
|
-
}
|
|
1286
|
-
if (eventType === "subscription" && eventData) {
|
|
1287
|
-
try {
|
|
1288
|
-
const parsed = JSON.parse(eventData);
|
|
1289
|
-
push(parsed);
|
|
1290
|
-
} catch {
|
|
1291
|
-
}
|
|
1292
|
-
}
|
|
1293
|
-
}
|
|
1294
|
-
function scheduleReconnect(delay) {
|
|
1295
|
-
if (!shouldReconnect) return;
|
|
1296
|
-
reconnectTimer = setTimeout(() => {
|
|
1297
|
-
connect();
|
|
1298
|
-
}, delay);
|
|
1299
|
-
}
|
|
1300
|
-
connect();
|
|
1301
|
-
return {
|
|
1302
|
-
next() {
|
|
1303
|
-
if (queue.length > 0) {
|
|
1304
|
-
return Promise.resolve({ value: queue.shift(), done: false });
|
|
1305
|
-
}
|
|
1306
|
-
if (done) {
|
|
1307
|
-
return Promise.resolve({
|
|
1308
|
-
value: void 0,
|
|
1309
|
-
done: true
|
|
1310
|
-
});
|
|
1311
|
-
}
|
|
1312
|
-
return new Promise((r) => {
|
|
1313
|
-
resolve = r;
|
|
1314
|
-
});
|
|
1315
|
-
},
|
|
1316
|
-
return() {
|
|
1317
|
-
shouldReconnect = false;
|
|
1318
|
-
done = true;
|
|
1319
|
-
if (reconnectTimer) {
|
|
1320
|
-
clearTimeout(reconnectTimer);
|
|
1321
|
-
reconnectTimer = void 0;
|
|
1322
|
-
}
|
|
1323
|
-
if (abortController) {
|
|
1324
|
-
activeAbortControllers.delete(abortController);
|
|
1325
|
-
abortController.abort();
|
|
1326
|
-
abortController = void 0;
|
|
1327
|
-
}
|
|
1328
|
-
if (resolve) {
|
|
1329
|
-
resolve({ value: void 0, done: true });
|
|
1330
|
-
resolve = null;
|
|
1331
|
-
}
|
|
1332
|
-
return Promise.resolve({ value: void 0, done: true });
|
|
1333
|
-
},
|
|
1334
|
-
[Symbol.asyncIterator]() {
|
|
1335
|
-
return this;
|
|
1336
|
-
}
|
|
1337
|
-
};
|
|
1338
|
-
}
|
|
1339
|
-
};
|
|
1340
|
-
}
|
|
1341
1603
|
};
|
|
1342
1604
|
|
|
1343
1605
|
exports.BillingClient = BillingClient;
|
|
1606
|
+
exports.ConnectionSupervisor = ConnectionSupervisor;
|
|
1344
1607
|
exports.EntitlementTier = EntitlementTier;
|
|
1345
1608
|
exports.SubscriptionCycle = SubscriptionCycle;
|
|
1346
1609
|
exports.SubscriptionProvider = SubscriptionProvider;
|
|
1610
|
+
exports.consumeSseStream = consumeSseStream;
|
|
1347
1611
|
exports.createHttpTransport = createHttpTransport;
|
|
1348
1612
|
exports.print = print;
|
|
1349
|
-
|
|
1350
|
-
//# sourceMappingURL=chunk-
|
|
1613
|
+
exports.refreshTokenSingleFlight = refreshTokenSingleFlight;
|
|
1614
|
+
//# sourceMappingURL=chunk-Y4OD5SDF.cjs.map
|
|
1615
|
+
//# sourceMappingURL=chunk-Y4OD5SDF.cjs.map
|