@playcademy/sdk 0.4.1-beta.7 → 0.4.1-beta.9
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.ts +8 -1
- package/dist/index.js +24 -12
- package/dist/internal.d.ts +8 -1
- package/dist/internal.js +24 -12
- package/dist/types.d.ts +8 -1
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -380,6 +380,9 @@ declare function parseOAuthState(state: string): {
|
|
|
380
380
|
|
|
381
381
|
/** Permitted HTTP verbs */
|
|
382
382
|
type Method = 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE';
|
|
383
|
+
interface RetryPolicy {
|
|
384
|
+
retryableMethods?: readonly Method[];
|
|
385
|
+
}
|
|
383
386
|
|
|
384
387
|
/**
|
|
385
388
|
* TimeBack Enums & Literal Types
|
|
@@ -946,11 +949,15 @@ declare abstract class PlaycademyBaseClient {
|
|
|
946
949
|
body?: unknown;
|
|
947
950
|
headers?: Record<string, string>;
|
|
948
951
|
raw?: boolean;
|
|
952
|
+
retryPolicy?: RetryPolicy;
|
|
949
953
|
}): Promise<T>;
|
|
950
954
|
/**
|
|
951
955
|
* Makes an authenticated HTTP request to the game's backend Worker.
|
|
952
956
|
*/
|
|
953
|
-
protected requestGameBackend<T>(path: string, method: Method, body?: unknown, headers?: Record<string, string>,
|
|
957
|
+
protected requestGameBackend<T>(path: string, method: Method, body?: unknown, headers?: Record<string, string>, options?: {
|
|
958
|
+
raw?: boolean;
|
|
959
|
+
retryPolicy?: RetryPolicy;
|
|
960
|
+
}): Promise<T>;
|
|
954
961
|
/**
|
|
955
962
|
* Ensures a gameId is available, throwing an error if not.
|
|
956
963
|
*/
|
package/dist/index.js
CHANGED
|
@@ -1037,7 +1037,7 @@ function createBackendNamespace(client) {
|
|
|
1037
1037
|
return client["requestGameBackend"](normalizePath(path), method, body, headers);
|
|
1038
1038
|
},
|
|
1039
1039
|
async download(path, method = "GET", body, headers) {
|
|
1040
|
-
return client["requestGameBackend"](normalizePath(path), method, body, headers, true);
|
|
1040
|
+
return client["requestGameBackend"](normalizePath(path), method, body, headers, { raw: true });
|
|
1041
1041
|
},
|
|
1042
1042
|
url(pathOrStrings, ...values) {
|
|
1043
1043
|
if (Array.isArray(pathOrStrings) && "raw" in pathOrStrings) {
|
|
@@ -1391,6 +1391,7 @@ function isValidUUID(value) {
|
|
|
1391
1391
|
// src/core/activity-tracker.ts
|
|
1392
1392
|
var DEFAULT_HIDDEN_TIMEOUT_MS = 10 * 60 * 1000;
|
|
1393
1393
|
var DEFAULT_HEARTBEAT_INTERVAL_MS = 15000;
|
|
1394
|
+
var HEARTBEAT_RETRY_POLICY = { retryableMethods: ["POST"] };
|
|
1394
1395
|
function getCurrentPausedTotal(activity, now = Date.now()) {
|
|
1395
1396
|
if (activity.pauseStartTime === null) {
|
|
1396
1397
|
return activity.pausedTime;
|
|
@@ -1417,9 +1418,11 @@ function markPersistedTiming(activity, timing) {
|
|
|
1417
1418
|
activity.totalPersistedPausedMs += timing.pausedMs;
|
|
1418
1419
|
}
|
|
1419
1420
|
function queueHeartbeatFlush(activity, timing, flush) {
|
|
1420
|
-
markPersistedTiming(activity, timing);
|
|
1421
1421
|
async function doFlush() {
|
|
1422
|
-
await flush();
|
|
1422
|
+
const wasPersisted = await flush();
|
|
1423
|
+
if (wasPersisted) {
|
|
1424
|
+
markPersistedTiming(activity, timing);
|
|
1425
|
+
}
|
|
1423
1426
|
}
|
|
1424
1427
|
if (activity.flushInFlight) {
|
|
1425
1428
|
const previousFlush = activity.flushInFlight.catch(() => {
|
|
@@ -1537,8 +1540,11 @@ function createTimebackActivityTracker(client) {
|
|
|
1537
1540
|
const body = buildHeartbeatBody(trackedActivity, timing, isFinal);
|
|
1538
1541
|
await queueHeartbeatFlush(trackedActivity, timing, async () => {
|
|
1539
1542
|
try {
|
|
1540
|
-
await client["requestGameBackend"](TIMEBACK_ROUTES.HEARTBEAT, "POST", body);
|
|
1541
|
-
|
|
1543
|
+
await client["requestGameBackend"](TIMEBACK_ROUTES.HEARTBEAT, "POST", body, undefined, { retryPolicy: HEARTBEAT_RETRY_POLICY });
|
|
1544
|
+
return true;
|
|
1545
|
+
} catch {
|
|
1546
|
+
return false;
|
|
1547
|
+
}
|
|
1542
1548
|
});
|
|
1543
1549
|
}
|
|
1544
1550
|
function handlePageHide() {
|
|
@@ -1566,9 +1572,10 @@ function createTimebackActivityTracker(client) {
|
|
|
1566
1572
|
keepalive: true
|
|
1567
1573
|
});
|
|
1568
1574
|
if (response.ok) {
|
|
1569
|
-
return;
|
|
1575
|
+
return true;
|
|
1570
1576
|
}
|
|
1571
1577
|
} catch {}
|
|
1578
|
+
return false;
|
|
1572
1579
|
});
|
|
1573
1580
|
}
|
|
1574
1581
|
function cleanupListeners() {
|
|
@@ -2219,7 +2226,8 @@ async function fetchWithRetry(url, init2) {
|
|
|
2219
2226
|
const startedAt = Date.now();
|
|
2220
2227
|
for (let attempt = 0;attempt <= RETRY_DELAYS_MS.length; attempt++) {
|
|
2221
2228
|
const retryDelayMs = RETRY_DELAYS_MS[attempt];
|
|
2222
|
-
const
|
|
2229
|
+
const retryableMethods = init2.retryPolicy?.retryableMethods ?? ["GET"];
|
|
2230
|
+
const canRetry = retryDelayMs !== undefined && retryableMethods.includes(init2.method);
|
|
2223
2231
|
try {
|
|
2224
2232
|
const response = await fetch(url, init2);
|
|
2225
2233
|
if (canRetry && isRetryableStatus(response.status)) {
|
|
@@ -2303,7 +2311,8 @@ async function request({
|
|
|
2303
2311
|
method = "GET",
|
|
2304
2312
|
body,
|
|
2305
2313
|
extraHeaders = {},
|
|
2306
|
-
raw = false
|
|
2314
|
+
raw = false,
|
|
2315
|
+
retryPolicy
|
|
2307
2316
|
}) {
|
|
2308
2317
|
const url = baseUrl.replace(/\/$/, "") + (path.startsWith("/") ? path : `/${path}`);
|
|
2309
2318
|
const headers = { ...extraHeaders };
|
|
@@ -2312,7 +2321,8 @@ async function request({
|
|
|
2312
2321
|
method,
|
|
2313
2322
|
headers,
|
|
2314
2323
|
body: payload,
|
|
2315
|
-
credentials: "omit"
|
|
2324
|
+
credentials: "omit",
|
|
2325
|
+
retryPolicy
|
|
2316
2326
|
}));
|
|
2317
2327
|
if (raw) {
|
|
2318
2328
|
return res;
|
|
@@ -2447,7 +2457,8 @@ class PlaycademyBaseClient {
|
|
|
2447
2457
|
body: options?.body,
|
|
2448
2458
|
baseUrl: this.baseUrl,
|
|
2449
2459
|
extraHeaders: effectiveHeaders,
|
|
2450
|
-
raw: options?.raw
|
|
2460
|
+
raw: options?.raw,
|
|
2461
|
+
retryPolicy: options?.retryPolicy
|
|
2451
2462
|
});
|
|
2452
2463
|
this.connectionManager?.reportRequestSuccess();
|
|
2453
2464
|
return result;
|
|
@@ -2456,7 +2467,7 @@ class PlaycademyBaseClient {
|
|
|
2456
2467
|
throw error;
|
|
2457
2468
|
}
|
|
2458
2469
|
}
|
|
2459
|
-
async requestGameBackend(path, method, body, headers,
|
|
2470
|
+
async requestGameBackend(path, method, body, headers, options) {
|
|
2460
2471
|
const effectiveHeaders = {
|
|
2461
2472
|
...headers,
|
|
2462
2473
|
...this.authStrategy.getHeaders()
|
|
@@ -2468,7 +2479,8 @@ class PlaycademyBaseClient {
|
|
|
2468
2479
|
body,
|
|
2469
2480
|
baseUrl: this.getGameBackendUrl(),
|
|
2470
2481
|
extraHeaders: effectiveHeaders,
|
|
2471
|
-
raw
|
|
2482
|
+
raw: options?.raw,
|
|
2483
|
+
retryPolicy: options?.retryPolicy
|
|
2472
2484
|
});
|
|
2473
2485
|
this.connectionManager?.reportRequestSuccess();
|
|
2474
2486
|
return result;
|
package/dist/internal.d.ts
CHANGED
|
@@ -7,6 +7,9 @@ import { AUTH_PROVIDER_IDS } from '@playcademy/constants';
|
|
|
7
7
|
|
|
8
8
|
/** Permitted HTTP verbs */
|
|
9
9
|
type Method = 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE';
|
|
10
|
+
interface RetryPolicy {
|
|
11
|
+
retryableMethods?: readonly Method[];
|
|
12
|
+
}
|
|
10
13
|
|
|
11
14
|
/**
|
|
12
15
|
* TimeBack Enums & Literal Types
|
|
@@ -5137,11 +5140,15 @@ declare abstract class PlaycademyBaseClient {
|
|
|
5137
5140
|
body?: unknown;
|
|
5138
5141
|
headers?: Record<string, string>;
|
|
5139
5142
|
raw?: boolean;
|
|
5143
|
+
retryPolicy?: RetryPolicy;
|
|
5140
5144
|
}): Promise<T>;
|
|
5141
5145
|
/**
|
|
5142
5146
|
* Makes an authenticated HTTP request to the game's backend Worker.
|
|
5143
5147
|
*/
|
|
5144
|
-
protected requestGameBackend<T>(path: string, method: Method, body?: unknown, headers?: Record<string, string>,
|
|
5148
|
+
protected requestGameBackend<T>(path: string, method: Method, body?: unknown, headers?: Record<string, string>, options?: {
|
|
5149
|
+
raw?: boolean;
|
|
5150
|
+
retryPolicy?: RetryPolicy;
|
|
5151
|
+
}): Promise<T>;
|
|
5145
5152
|
/**
|
|
5146
5153
|
* Ensures a gameId is available, throwing an error if not.
|
|
5147
5154
|
*/
|
package/dist/internal.js
CHANGED
|
@@ -1037,7 +1037,7 @@ function createBackendNamespace(client) {
|
|
|
1037
1037
|
return client["requestGameBackend"](normalizePath(path), method, body, headers);
|
|
1038
1038
|
},
|
|
1039
1039
|
async download(path, method = "GET", body, headers) {
|
|
1040
|
-
return client["requestGameBackend"](normalizePath(path), method, body, headers, true);
|
|
1040
|
+
return client["requestGameBackend"](normalizePath(path), method, body, headers, { raw: true });
|
|
1041
1041
|
},
|
|
1042
1042
|
url(pathOrStrings, ...values) {
|
|
1043
1043
|
if (Array.isArray(pathOrStrings) && "raw" in pathOrStrings) {
|
|
@@ -1391,6 +1391,7 @@ function isValidUUID(value) {
|
|
|
1391
1391
|
// src/core/activity-tracker.ts
|
|
1392
1392
|
var DEFAULT_HIDDEN_TIMEOUT_MS = 10 * 60 * 1000;
|
|
1393
1393
|
var DEFAULT_HEARTBEAT_INTERVAL_MS = 15000;
|
|
1394
|
+
var HEARTBEAT_RETRY_POLICY = { retryableMethods: ["POST"] };
|
|
1394
1395
|
function getCurrentPausedTotal(activity, now = Date.now()) {
|
|
1395
1396
|
if (activity.pauseStartTime === null) {
|
|
1396
1397
|
return activity.pausedTime;
|
|
@@ -1417,9 +1418,11 @@ function markPersistedTiming(activity, timing) {
|
|
|
1417
1418
|
activity.totalPersistedPausedMs += timing.pausedMs;
|
|
1418
1419
|
}
|
|
1419
1420
|
function queueHeartbeatFlush(activity, timing, flush) {
|
|
1420
|
-
markPersistedTiming(activity, timing);
|
|
1421
1421
|
async function doFlush() {
|
|
1422
|
-
await flush();
|
|
1422
|
+
const wasPersisted = await flush();
|
|
1423
|
+
if (wasPersisted) {
|
|
1424
|
+
markPersistedTiming(activity, timing);
|
|
1425
|
+
}
|
|
1423
1426
|
}
|
|
1424
1427
|
if (activity.flushInFlight) {
|
|
1425
1428
|
const previousFlush = activity.flushInFlight.catch(() => {
|
|
@@ -1537,8 +1540,11 @@ function createTimebackActivityTracker(client) {
|
|
|
1537
1540
|
const body = buildHeartbeatBody(trackedActivity, timing, isFinal);
|
|
1538
1541
|
await queueHeartbeatFlush(trackedActivity, timing, async () => {
|
|
1539
1542
|
try {
|
|
1540
|
-
await client["requestGameBackend"](TIMEBACK_ROUTES.HEARTBEAT, "POST", body);
|
|
1541
|
-
|
|
1543
|
+
await client["requestGameBackend"](TIMEBACK_ROUTES.HEARTBEAT, "POST", body, undefined, { retryPolicy: HEARTBEAT_RETRY_POLICY });
|
|
1544
|
+
return true;
|
|
1545
|
+
} catch {
|
|
1546
|
+
return false;
|
|
1547
|
+
}
|
|
1542
1548
|
});
|
|
1543
1549
|
}
|
|
1544
1550
|
function handlePageHide() {
|
|
@@ -1566,9 +1572,10 @@ function createTimebackActivityTracker(client) {
|
|
|
1566
1572
|
keepalive: true
|
|
1567
1573
|
});
|
|
1568
1574
|
if (response.ok) {
|
|
1569
|
-
return;
|
|
1575
|
+
return true;
|
|
1570
1576
|
}
|
|
1571
1577
|
} catch {}
|
|
1578
|
+
return false;
|
|
1572
1579
|
});
|
|
1573
1580
|
}
|
|
1574
1581
|
function cleanupListeners() {
|
|
@@ -3179,7 +3186,8 @@ async function fetchWithRetry(url, init2) {
|
|
|
3179
3186
|
const startedAt = Date.now();
|
|
3180
3187
|
for (let attempt = 0;attempt <= RETRY_DELAYS_MS.length; attempt++) {
|
|
3181
3188
|
const retryDelayMs = RETRY_DELAYS_MS[attempt];
|
|
3182
|
-
const
|
|
3189
|
+
const retryableMethods = init2.retryPolicy?.retryableMethods ?? ["GET"];
|
|
3190
|
+
const canRetry = retryDelayMs !== undefined && retryableMethods.includes(init2.method);
|
|
3183
3191
|
try {
|
|
3184
3192
|
const response = await fetch(url, init2);
|
|
3185
3193
|
if (canRetry && isRetryableStatus(response.status)) {
|
|
@@ -3263,7 +3271,8 @@ async function request({
|
|
|
3263
3271
|
method = "GET",
|
|
3264
3272
|
body,
|
|
3265
3273
|
extraHeaders = {},
|
|
3266
|
-
raw = false
|
|
3274
|
+
raw = false,
|
|
3275
|
+
retryPolicy
|
|
3267
3276
|
}) {
|
|
3268
3277
|
const url = baseUrl.replace(/\/$/, "") + (path.startsWith("/") ? path : `/${path}`);
|
|
3269
3278
|
const headers = { ...extraHeaders };
|
|
@@ -3272,7 +3281,8 @@ async function request({
|
|
|
3272
3281
|
method,
|
|
3273
3282
|
headers,
|
|
3274
3283
|
body: payload,
|
|
3275
|
-
credentials: "omit"
|
|
3284
|
+
credentials: "omit",
|
|
3285
|
+
retryPolicy
|
|
3276
3286
|
}));
|
|
3277
3287
|
if (raw) {
|
|
3278
3288
|
return res;
|
|
@@ -3407,7 +3417,8 @@ class PlaycademyBaseClient {
|
|
|
3407
3417
|
body: options?.body,
|
|
3408
3418
|
baseUrl: this.baseUrl,
|
|
3409
3419
|
extraHeaders: effectiveHeaders,
|
|
3410
|
-
raw: options?.raw
|
|
3420
|
+
raw: options?.raw,
|
|
3421
|
+
retryPolicy: options?.retryPolicy
|
|
3411
3422
|
});
|
|
3412
3423
|
this.connectionManager?.reportRequestSuccess();
|
|
3413
3424
|
return result;
|
|
@@ -3416,7 +3427,7 @@ class PlaycademyBaseClient {
|
|
|
3416
3427
|
throw error;
|
|
3417
3428
|
}
|
|
3418
3429
|
}
|
|
3419
|
-
async requestGameBackend(path, method, body, headers,
|
|
3430
|
+
async requestGameBackend(path, method, body, headers, options) {
|
|
3420
3431
|
const effectiveHeaders = {
|
|
3421
3432
|
...headers,
|
|
3422
3433
|
...this.authStrategy.getHeaders()
|
|
@@ -3428,7 +3439,8 @@ class PlaycademyBaseClient {
|
|
|
3428
3439
|
body,
|
|
3429
3440
|
baseUrl: this.getGameBackendUrl(),
|
|
3430
3441
|
extraHeaders: effectiveHeaders,
|
|
3431
|
-
raw
|
|
3442
|
+
raw: options?.raw,
|
|
3443
|
+
retryPolicy: options?.retryPolicy
|
|
3432
3444
|
});
|
|
3433
3445
|
this.connectionManager?.reportRequestSuccess();
|
|
3434
3446
|
return result;
|
package/dist/types.d.ts
CHANGED
|
@@ -21,6 +21,9 @@ declare function parseOAuthState(state: string): {
|
|
|
21
21
|
|
|
22
22
|
/** Permitted HTTP verbs */
|
|
23
23
|
type Method = 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE';
|
|
24
|
+
interface RetryPolicy {
|
|
25
|
+
retryableMethods?: readonly Method[];
|
|
26
|
+
}
|
|
24
27
|
|
|
25
28
|
/**
|
|
26
29
|
* TimeBack Enums & Literal Types
|
|
@@ -4680,11 +4683,15 @@ declare abstract class PlaycademyBaseClient {
|
|
|
4680
4683
|
body?: unknown;
|
|
4681
4684
|
headers?: Record<string, string>;
|
|
4682
4685
|
raw?: boolean;
|
|
4686
|
+
retryPolicy?: RetryPolicy;
|
|
4683
4687
|
}): Promise<T>;
|
|
4684
4688
|
/**
|
|
4685
4689
|
* Makes an authenticated HTTP request to the game's backend Worker.
|
|
4686
4690
|
*/
|
|
4687
|
-
protected requestGameBackend<T>(path: string, method: Method, body?: unknown, headers?: Record<string, string>,
|
|
4691
|
+
protected requestGameBackend<T>(path: string, method: Method, body?: unknown, headers?: Record<string, string>, options?: {
|
|
4692
|
+
raw?: boolean;
|
|
4693
|
+
retryPolicy?: RetryPolicy;
|
|
4694
|
+
}): Promise<T>;
|
|
4688
4695
|
/**
|
|
4689
4696
|
* Ensures a gameId is available, throwing an error if not.
|
|
4690
4697
|
*/
|