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