@pickleball/server-sdk 0.1.1 → 0.3.0
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/CHANGELOG.md +30 -0
- package/README.md +22 -0
- package/dist/{chunk-HBTNLURN.js → chunk-UPS4VFER.js} +188 -21
- package/dist/index.cjs +189 -21
- package/dist/index.d.cts +168 -3
- package/dist/index.d.ts +168 -3
- package/dist/index.js +3 -1
- package/dist/proxy.cjs +321 -23
- package/dist/proxy.d.cts +11 -1
- package/dist/proxy.d.ts +11 -1
- package/dist/proxy.js +133 -3
- package/package.json +2 -2
package/dist/proxy.cjs
CHANGED
|
@@ -22,6 +22,8 @@ var proxy_exports = {};
|
|
|
22
22
|
__export(proxy_exports, {
|
|
23
23
|
createStableIdempotencyKey: () => createStableIdempotencyKey,
|
|
24
24
|
handleBootstrap: () => handleBootstrap,
|
|
25
|
+
handleDeviceHeartbeat: () => handleDeviceHeartbeat,
|
|
26
|
+
handleDeviceRegister: () => handleDeviceRegister,
|
|
25
27
|
handleSessionAction: () => handleSessionAction,
|
|
26
28
|
handleSessions: () => handleSessions
|
|
27
29
|
});
|
|
@@ -48,6 +50,15 @@ var PickleballApiError = class extends PickleballLiveError {
|
|
|
48
50
|
this.name = "PickleballApiError";
|
|
49
51
|
}
|
|
50
52
|
};
|
|
53
|
+
var PickleballPartnerApiError = class extends PickleballLiveError {
|
|
54
|
+
constructor(message, partnerCode, status, details = {}) {
|
|
55
|
+
super(message, "PARTNER_API_ERROR");
|
|
56
|
+
this.partnerCode = partnerCode;
|
|
57
|
+
this.status = status;
|
|
58
|
+
this.details = details;
|
|
59
|
+
this.name = "PickleballPartnerApiError";
|
|
60
|
+
}
|
|
61
|
+
};
|
|
51
62
|
var PickleballHttpError = class extends PickleballLiveError {
|
|
52
63
|
constructor(status) {
|
|
53
64
|
super(`Request failed with HTTP ${status}`, "HTTP_ERROR");
|
|
@@ -276,6 +287,49 @@ function validateGrant(value) {
|
|
|
276
287
|
config: validateRemoteConfig(data.config)
|
|
277
288
|
};
|
|
278
289
|
}
|
|
290
|
+
function validateCameraGrant(value) {
|
|
291
|
+
const data = record(value);
|
|
292
|
+
const base = validateGrant(data);
|
|
293
|
+
if (!/^rtmps?:\/\//i.test(base.serverUrl)) invalid();
|
|
294
|
+
if (data.status !== "scheduled" && data.status !== "live") invalid();
|
|
295
|
+
return {
|
|
296
|
+
...base,
|
|
297
|
+
status: data.status,
|
|
298
|
+
matchRef: nullableString(data.matchRef),
|
|
299
|
+
courtRef: nullableString(data.courtRef),
|
|
300
|
+
watchUrl: nullableString(data.watchUrl),
|
|
301
|
+
convexUrl: nullableString(data.convexUrl),
|
|
302
|
+
issuedAt: finite(data.issuedAt),
|
|
303
|
+
expiresAt: finite(data.expiresAt)
|
|
304
|
+
};
|
|
305
|
+
}
|
|
306
|
+
function validateCameraSessionCreated(value) {
|
|
307
|
+
const data = record(value);
|
|
308
|
+
if (data.status !== "scheduled" && data.status !== "live" && data.status !== "ended") {
|
|
309
|
+
invalid();
|
|
310
|
+
}
|
|
311
|
+
if (data.visibility !== "public" && data.visibility !== "private") invalid();
|
|
312
|
+
if (typeof data.reused !== "boolean") invalid();
|
|
313
|
+
return {
|
|
314
|
+
id: nonblank(data.id),
|
|
315
|
+
status: data.status,
|
|
316
|
+
visibility: data.visibility,
|
|
317
|
+
quality: finite(data.quality),
|
|
318
|
+
deliveryMode: nonblank(data.deliveryMode),
|
|
319
|
+
matchRef: nullableString(data.matchRef),
|
|
320
|
+
courtRef: nullableString(data.courtRef),
|
|
321
|
+
playbackUrl: nullableString(data.playbackUrl),
|
|
322
|
+
whepUrl: nullableString(data.whepUrl),
|
|
323
|
+
watchUrl: nullableString(data.watchUrl),
|
|
324
|
+
reused: data.reused,
|
|
325
|
+
cameraGrant: validateCameraGrant(data.cameraGrant)
|
|
326
|
+
};
|
|
327
|
+
}
|
|
328
|
+
function validateLegacyEnd(value) {
|
|
329
|
+
const data = record(value);
|
|
330
|
+
if (data.ok !== true || data.status !== "ended") invalid();
|
|
331
|
+
return { status: "ended" };
|
|
332
|
+
}
|
|
279
333
|
function validateRefresh(value) {
|
|
280
334
|
const data = record(value);
|
|
281
335
|
return {
|
|
@@ -313,6 +367,7 @@ function validateSession(value) {
|
|
|
313
367
|
matchRef: nullableString(data.matchRef),
|
|
314
368
|
playbackUrl: nullableString(data.playbackUrl),
|
|
315
369
|
watchUrl: nullableString(data.watchUrl),
|
|
370
|
+
whepUrl: nullableString(data.whepUrl),
|
|
316
371
|
scheduledAt: nullableNumber(data.scheduledAt),
|
|
317
372
|
startedAt: nullableNumber(data.startedAt),
|
|
318
373
|
endedAt: nullableNumber(data.endedAt),
|
|
@@ -343,6 +398,29 @@ function validateRecordings(value, sessionId) {
|
|
|
343
398
|
if (!Array.isArray(value)) invalid();
|
|
344
399
|
return value.map((item) => validateRecording(item, sessionId));
|
|
345
400
|
}
|
|
401
|
+
function validateDeviceRegister(value) {
|
|
402
|
+
const data = record(value);
|
|
403
|
+
const heartbeatIntervalMs = finite(data.heartbeatIntervalMs);
|
|
404
|
+
if (heartbeatIntervalMs <= 0) invalid();
|
|
405
|
+
return {
|
|
406
|
+
deviceId: nonblank(data.deviceId),
|
|
407
|
+
heartbeatIntervalMs
|
|
408
|
+
};
|
|
409
|
+
}
|
|
410
|
+
function validateDeviceHeartbeat(value) {
|
|
411
|
+
const data = record(value);
|
|
412
|
+
if (data.controlSession === null || data.controlSession === void 0) {
|
|
413
|
+
return { controlSession: null };
|
|
414
|
+
}
|
|
415
|
+
const grant = record(data.controlSession);
|
|
416
|
+
return {
|
|
417
|
+
controlSession: {
|
|
418
|
+
id: nonblank(grant.id),
|
|
419
|
+
token: nonblank(grant.token),
|
|
420
|
+
convexUrl: nonblank(grant.convexUrl)
|
|
421
|
+
}
|
|
422
|
+
};
|
|
423
|
+
}
|
|
346
424
|
|
|
347
425
|
// src/index.ts
|
|
348
426
|
var TRANSIENT_STATUSES = /* @__PURE__ */ new Set([408, 429]);
|
|
@@ -478,7 +556,23 @@ async function readBoundedResponseText(response2) {
|
|
|
478
556
|
}
|
|
479
557
|
return new TextDecoder().decode(bytes);
|
|
480
558
|
}
|
|
481
|
-
|
|
559
|
+
function parsePartnerError(payload, status, apiKey) {
|
|
560
|
+
if (!isRecord(payload) || !("error" in payload)) {
|
|
561
|
+
return new PickleballInvalidResponseError();
|
|
562
|
+
}
|
|
563
|
+
if (isRecord(payload.error)) {
|
|
564
|
+
return parseSdkError(payload, status, apiKey);
|
|
565
|
+
}
|
|
566
|
+
if (typeof payload.error !== "string") return new PickleballInvalidResponseError();
|
|
567
|
+
const { error, code, ...details } = payload;
|
|
568
|
+
const safeMessage = apiKey === "" ? error : error.split(apiKey).join("[REDACTED]");
|
|
569
|
+
const partnerCode = typeof code === "string" && code.trim() !== "" ? code : `HTTP_${status}`;
|
|
570
|
+
return new PickleballPartnerApiError(safeMessage, partnerCode, status, details);
|
|
571
|
+
}
|
|
572
|
+
async function decodeResponse(response2, apiKey, validate, options = {
|
|
573
|
+
errorStyle: "sdk",
|
|
574
|
+
envelope: "data"
|
|
575
|
+
}) {
|
|
482
576
|
if (response2.status >= 300 && response2.status < 400) {
|
|
483
577
|
await response2.body?.cancel().catch(() => void 0);
|
|
484
578
|
throw new PickleballHttpError(response2.status);
|
|
@@ -490,7 +584,10 @@ async function decodeResponse(response2, apiKey, validate) {
|
|
|
490
584
|
} catch {
|
|
491
585
|
throw new PickleballInvalidResponseError();
|
|
492
586
|
}
|
|
493
|
-
if (!response2.ok)
|
|
587
|
+
if (!response2.ok) {
|
|
588
|
+
throw options.errorStyle === "v1" ? parsePartnerError(payload, response2.status, apiKey) : parseSdkError(payload, response2.status, apiKey);
|
|
589
|
+
}
|
|
590
|
+
if (options.envelope === "raw") return validate(payload);
|
|
494
591
|
if (!isRecord(payload) || !("data" in payload)) {
|
|
495
592
|
throw new PickleballInvalidResponseError();
|
|
496
593
|
}
|
|
@@ -498,7 +595,15 @@ async function decodeResponse(response2, apiKey, validate) {
|
|
|
498
595
|
}
|
|
499
596
|
function createPickleballLiveClient(options) {
|
|
500
597
|
const baseUrl = validatedBaseUrl(options.baseUrl);
|
|
501
|
-
const
|
|
598
|
+
const configuredAppId = options.appId === void 0 ? null : requiredNonblank(options.appId, "appId");
|
|
599
|
+
const requireAppId = () => {
|
|
600
|
+
if (configuredAppId === null) {
|
|
601
|
+
throw new PickleballConfigurationError(
|
|
602
|
+
"appId is required for /api/v2/sdk operations (apps/sessions/devices)"
|
|
603
|
+
);
|
|
604
|
+
}
|
|
605
|
+
return configuredAppId;
|
|
606
|
+
};
|
|
502
607
|
const apiKey = requiredNonblank(options.apiKey, "apiKey");
|
|
503
608
|
const fetchImplementation = options.fetch ?? globalThis.fetch;
|
|
504
609
|
if (typeof fetchImplementation !== "function") {
|
|
@@ -511,7 +616,9 @@ function createPickleballLiveClient(options) {
|
|
|
511
616
|
path,
|
|
512
617
|
body,
|
|
513
618
|
retryPolicy,
|
|
514
|
-
validate
|
|
619
|
+
validate,
|
|
620
|
+
errorStyle = "sdk",
|
|
621
|
+
envelope = "data"
|
|
515
622
|
}) {
|
|
516
623
|
const retryLimit = retryPolicy === "transient" ? maxRetries : 0;
|
|
517
624
|
for (let attempt = 0; attempt <= retryLimit; attempt += 1) {
|
|
@@ -532,14 +639,14 @@ function createPickleballLiveClient(options) {
|
|
|
532
639
|
} : {
|
|
533
640
|
accept: "application/json",
|
|
534
641
|
"x-api-key": apiKey,
|
|
535
|
-
"x-pickleball-app-id":
|
|
642
|
+
...configuredAppId === null ? {} : { "x-pickleball-app-id": configuredAppId }
|
|
536
643
|
},
|
|
537
644
|
...body === void 0 ? {} : { body: JSON.stringify(body) },
|
|
538
645
|
signal: controller.signal,
|
|
539
646
|
redirect: "error"
|
|
540
647
|
});
|
|
541
648
|
try {
|
|
542
|
-
return await decodeResponse(response2, apiKey, validate);
|
|
649
|
+
return await decodeResponse(response2, apiKey, validate, { errorStyle, envelope });
|
|
543
650
|
} catch (error) {
|
|
544
651
|
if (isTransientStatus(response2.status) && attempt < retryLimit && isRetryableResponseError(error)) {
|
|
545
652
|
clearTimeout(timer);
|
|
@@ -551,7 +658,7 @@ function createPickleballLiveClient(options) {
|
|
|
551
658
|
throw error;
|
|
552
659
|
}
|
|
553
660
|
} catch (error) {
|
|
554
|
-
if (error instanceof PickleballApiError || error instanceof PickleballHttpError || error instanceof PickleballInvalidResponseError) {
|
|
661
|
+
if (error instanceof PickleballApiError || error instanceof PickleballPartnerApiError || error instanceof PickleballHttpError || error instanceof PickleballInvalidResponseError) {
|
|
555
662
|
throw error;
|
|
556
663
|
}
|
|
557
664
|
if (attempt < retryLimit) {
|
|
@@ -570,54 +677,115 @@ function createPickleballLiveClient(options) {
|
|
|
570
677
|
const post = (path, body, retryPolicy, validate) => request({ method: "POST", path, body, retryPolicy, validate });
|
|
571
678
|
const get = (path, validate) => request({ method: "GET", path, retryPolicy: "transient", validate });
|
|
572
679
|
const sessionPath = (sessionId) => `/api/v2/sdk/sessions/${encodeURIComponent(sessionId)}`;
|
|
680
|
+
const v1SessionPath = (sessionId) => `/api/v1/live-sessions/${encodeURIComponent(sessionId)}`;
|
|
681
|
+
const postV1 = (path, body, retryPolicy, validate, envelope = "data") => request({ method: "POST", path, body, retryPolicy, validate, errorStyle: "v1", envelope });
|
|
682
|
+
const getV1 = (path, validate) => request({ method: "GET", path, retryPolicy: "transient", validate, errorStyle: "v1" });
|
|
573
683
|
return {
|
|
574
684
|
apps: {
|
|
575
|
-
bootstrap: (input) => post(
|
|
685
|
+
bootstrap: async (input) => post(
|
|
576
686
|
"/api/v2/sdk/bootstrap",
|
|
577
|
-
{ ...input, appId },
|
|
687
|
+
{ ...input, appId: requireAppId() },
|
|
578
688
|
"transient",
|
|
579
689
|
validateBootstrap
|
|
580
690
|
)
|
|
581
691
|
},
|
|
582
692
|
sessions: {
|
|
583
|
-
start: (input) => post(
|
|
693
|
+
start: async (input) => post(
|
|
584
694
|
"/api/v2/sdk/sessions",
|
|
585
|
-
{ ...input, appId },
|
|
695
|
+
{ ...input, appId: requireAppId() },
|
|
586
696
|
"transient",
|
|
587
697
|
validateGrant
|
|
588
698
|
),
|
|
589
|
-
refresh: (sessionId, input) => post(
|
|
699
|
+
refresh: async (sessionId, input) => post(
|
|
590
700
|
`${sessionPath(sessionId)}/refresh`,
|
|
591
|
-
{ ...input, appId },
|
|
701
|
+
{ ...input, appId: requireAppId() },
|
|
592
702
|
"never",
|
|
593
703
|
validateRefresh
|
|
594
704
|
),
|
|
595
705
|
end: async (input) => {
|
|
596
706
|
await post(
|
|
597
707
|
`${sessionPath(input.sessionId)}/end`,
|
|
598
|
-
{ ...input, appId },
|
|
708
|
+
{ ...input, appId: requireAppId() },
|
|
599
709
|
"transient",
|
|
600
710
|
(value) => validateEnd(value, input.sessionId)
|
|
601
711
|
);
|
|
602
712
|
},
|
|
603
|
-
publish: (sessionId) => post(
|
|
713
|
+
publish: async (sessionId) => post(
|
|
604
714
|
`${sessionPath(sessionId)}/publish`,
|
|
605
|
-
{ sessionId, appId },
|
|
715
|
+
{ sessionId, appId: requireAppId() },
|
|
606
716
|
"transient",
|
|
607
717
|
(value) => validatePublishState(value, sessionId)
|
|
608
718
|
),
|
|
609
|
-
unpublish: (sessionId) => post(
|
|
719
|
+
unpublish: async (sessionId) => post(
|
|
610
720
|
`${sessionPath(sessionId)}/unpublish`,
|
|
611
|
-
{ sessionId, appId },
|
|
721
|
+
{ sessionId, appId: requireAppId() },
|
|
612
722
|
"transient",
|
|
613
723
|
(value) => validatePublishState(value, sessionId)
|
|
614
724
|
),
|
|
615
|
-
get: (sessionId) =>
|
|
616
|
-
|
|
617
|
-
|
|
725
|
+
get: async (sessionId) => {
|
|
726
|
+
requireAppId();
|
|
727
|
+
return get(sessionPath(sessionId), validateSession);
|
|
728
|
+
},
|
|
729
|
+
getRecordings: async (sessionId) => {
|
|
730
|
+
requireAppId();
|
|
731
|
+
return get(
|
|
732
|
+
`${sessionPath(sessionId)}/recordings`,
|
|
733
|
+
(value) => validateRecordings(value, sessionId)
|
|
734
|
+
);
|
|
735
|
+
}
|
|
736
|
+
},
|
|
737
|
+
liveSessions: {
|
|
738
|
+
createCameraSession: (input) => postV1(
|
|
739
|
+
"/api/v1/live-sessions",
|
|
740
|
+
{
|
|
741
|
+
title: input.title,
|
|
742
|
+
matchRef: input.matchRef,
|
|
743
|
+
cameraGrant: true,
|
|
744
|
+
deliveryMode: "device-rtmp",
|
|
745
|
+
...input.visibility === void 0 ? {} : { visibility: input.visibility },
|
|
746
|
+
...input.description === void 0 ? {} : { description: input.description },
|
|
747
|
+
...input.quality === void 0 ? {} : { quality: input.quality },
|
|
748
|
+
...input.courtRef === void 0 ? {} : { courtRef: input.courtRef }
|
|
749
|
+
},
|
|
750
|
+
// Idempotent theo (host, matchRef): gọi lại chỉ cấp grant mới, an toàn để retry.
|
|
751
|
+
"transient",
|
|
752
|
+
validateCameraSessionCreated
|
|
753
|
+
),
|
|
754
|
+
cameraGrant: (sessionId) => postV1(
|
|
755
|
+
`${v1SessionPath(sessionId)}/camera-grant`,
|
|
756
|
+
{},
|
|
757
|
+
"transient",
|
|
758
|
+
validateCameraGrant
|
|
759
|
+
),
|
|
760
|
+
end: (sessionId) => postV1(
|
|
761
|
+
`${v1SessionPath(sessionId)}/end`,
|
|
762
|
+
{},
|
|
763
|
+
"transient",
|
|
764
|
+
validateLegacyEnd,
|
|
765
|
+
"raw"
|
|
766
|
+
),
|
|
767
|
+
get: (sessionId) => getV1(v1SessionPath(sessionId), validateSession),
|
|
768
|
+
getRecordings: (sessionId) => getV1(
|
|
769
|
+
`${v1SessionPath(sessionId)}/recordings`,
|
|
618
770
|
(value) => validateRecordings(value, sessionId)
|
|
619
771
|
)
|
|
620
772
|
},
|
|
773
|
+
devices: {
|
|
774
|
+
register: async (input) => post(
|
|
775
|
+
"/api/v2/sdk/devices/register",
|
|
776
|
+
{ ...input, appId: requireAppId() },
|
|
777
|
+
"transient",
|
|
778
|
+
validateDeviceRegister
|
|
779
|
+
),
|
|
780
|
+
heartbeat: async (input) => post(
|
|
781
|
+
"/api/v2/sdk/devices/heartbeat",
|
|
782
|
+
{ ...input, appId: requireAppId() },
|
|
783
|
+
// Heartbeat có side effect trao token 1 lần — không retry để tránh
|
|
784
|
+
// nhận token đã bị đánh dấu delivered ở lần trước
|
|
785
|
+
"never",
|
|
786
|
+
validateDeviceHeartbeat
|
|
787
|
+
)
|
|
788
|
+
},
|
|
621
789
|
webhooks: { verify: verifyWebhook }
|
|
622
790
|
};
|
|
623
791
|
}
|
|
@@ -752,8 +920,9 @@ function startBody(value) {
|
|
|
752
920
|
"visibility",
|
|
753
921
|
"consentVersion",
|
|
754
922
|
"metadata",
|
|
755
|
-
"standby"
|
|
756
|
-
|
|
923
|
+
"standby",
|
|
924
|
+
"quality"
|
|
925
|
+
]) || !nonblank2(value.externalSessionId, 256) || !nonblank2(value.title, 200) || !nonblank2(value.consentVersion, 100) || value.visibility !== void 0 && value.visibility !== "public" && value.visibility !== "private" || value.standby !== void 0 && typeof value.standby !== "boolean" || value.quality !== void 0 && value.quality !== 720 && value.quality !== 1080) {
|
|
757
926
|
throw new InvalidRequest();
|
|
758
927
|
}
|
|
759
928
|
if (value.metadata !== void 0) {
|
|
@@ -1009,10 +1178,139 @@ async function handleSessionAction(request, params, deps) {
|
|
|
1009
1178
|
return null;
|
|
1010
1179
|
});
|
|
1011
1180
|
}
|
|
1181
|
+
var DEVICE_INFO_FIELDS = ["appVersion", "deviceName", "deviceModel", "osVersion"];
|
|
1182
|
+
var DEVICE_METRIC_NUMBER_FIELDS = [
|
|
1183
|
+
"ramFreeMb",
|
|
1184
|
+
"ramTotalMb",
|
|
1185
|
+
"storageFreeGb",
|
|
1186
|
+
"batteryPct",
|
|
1187
|
+
"batteryTempC",
|
|
1188
|
+
"thermal",
|
|
1189
|
+
"linkMbps",
|
|
1190
|
+
"rssi"
|
|
1191
|
+
];
|
|
1192
|
+
var DEVICE_METRIC_STRING_FIELDS = ["netType", "connQuality"];
|
|
1193
|
+
function deviceInfoFields(value) {
|
|
1194
|
+
const info = {};
|
|
1195
|
+
for (const field of DEVICE_INFO_FIELDS) {
|
|
1196
|
+
const entry = value[field];
|
|
1197
|
+
if (entry === void 0) continue;
|
|
1198
|
+
if (!nonblank2(entry, 200)) throw new InvalidRequest();
|
|
1199
|
+
info[field] = entry;
|
|
1200
|
+
}
|
|
1201
|
+
return info;
|
|
1202
|
+
}
|
|
1203
|
+
function deviceRegisterBody(value) {
|
|
1204
|
+
if (!hasOnlyKeys(value, [
|
|
1205
|
+
"sdkVersion",
|
|
1206
|
+
"platform",
|
|
1207
|
+
"bundleId",
|
|
1208
|
+
"installationId",
|
|
1209
|
+
...DEVICE_INFO_FIELDS
|
|
1210
|
+
])) {
|
|
1211
|
+
throw new InvalidRequest();
|
|
1212
|
+
}
|
|
1213
|
+
const identity = bootstrapBody({
|
|
1214
|
+
sdkVersion: value.sdkVersion,
|
|
1215
|
+
platform: value.platform,
|
|
1216
|
+
bundleId: value.bundleId,
|
|
1217
|
+
installationId: value.installationId
|
|
1218
|
+
});
|
|
1219
|
+
return { ...identity, ...deviceInfoFields(value) };
|
|
1220
|
+
}
|
|
1221
|
+
function deviceMetricsFields(value) {
|
|
1222
|
+
if (value === void 0) return void 0;
|
|
1223
|
+
if (!isObject(value)) throw new InvalidRequest();
|
|
1224
|
+
if (!hasOnlyKeys(value, [...DEVICE_METRIC_NUMBER_FIELDS, ...DEVICE_METRIC_STRING_FIELDS])) {
|
|
1225
|
+
throw new InvalidRequest();
|
|
1226
|
+
}
|
|
1227
|
+
const metrics = {};
|
|
1228
|
+
for (const field of DEVICE_METRIC_NUMBER_FIELDS) {
|
|
1229
|
+
const entry = value[field];
|
|
1230
|
+
if (entry === void 0) continue;
|
|
1231
|
+
if (typeof entry !== "number" || !Number.isFinite(entry)) throw new InvalidRequest();
|
|
1232
|
+
metrics[field] = entry;
|
|
1233
|
+
}
|
|
1234
|
+
for (const field of DEVICE_METRIC_STRING_FIELDS) {
|
|
1235
|
+
const entry = value[field];
|
|
1236
|
+
if (entry === void 0) continue;
|
|
1237
|
+
if (!nonblank2(entry, 50)) throw new InvalidRequest();
|
|
1238
|
+
metrics[field] = entry;
|
|
1239
|
+
}
|
|
1240
|
+
return metrics;
|
|
1241
|
+
}
|
|
1242
|
+
function deviceHeartbeatBody(value) {
|
|
1243
|
+
if (!hasOnlyKeys(value, [
|
|
1244
|
+
"sdkVersion",
|
|
1245
|
+
"platform",
|
|
1246
|
+
"bundleId",
|
|
1247
|
+
"installationId",
|
|
1248
|
+
"agentState",
|
|
1249
|
+
"currentSessionId",
|
|
1250
|
+
"appVersion",
|
|
1251
|
+
"metrics"
|
|
1252
|
+
])) {
|
|
1253
|
+
throw new InvalidRequest();
|
|
1254
|
+
}
|
|
1255
|
+
const identity = bootstrapBody({
|
|
1256
|
+
sdkVersion: value.sdkVersion,
|
|
1257
|
+
platform: value.platform,
|
|
1258
|
+
bundleId: value.bundleId,
|
|
1259
|
+
installationId: value.installationId
|
|
1260
|
+
});
|
|
1261
|
+
if (value.agentState !== void 0 && !nonblank2(value.agentState, 50)) {
|
|
1262
|
+
throw new InvalidRequest();
|
|
1263
|
+
}
|
|
1264
|
+
if (value.currentSessionId !== void 0 && value.currentSessionId !== null && !nonblank2(value.currentSessionId, 256)) {
|
|
1265
|
+
throw new InvalidRequest();
|
|
1266
|
+
}
|
|
1267
|
+
if (value.appVersion !== void 0 && !nonblank2(value.appVersion, 200)) {
|
|
1268
|
+
throw new InvalidRequest();
|
|
1269
|
+
}
|
|
1270
|
+
const metrics = deviceMetricsFields(value.metrics);
|
|
1271
|
+
return {
|
|
1272
|
+
...identity,
|
|
1273
|
+
...value.agentState === void 0 ? {} : { agentState: value.agentState },
|
|
1274
|
+
...value.currentSessionId === void 0 ? {} : { currentSessionId: value.currentSessionId },
|
|
1275
|
+
...value.appVersion === void 0 ? {} : { appVersion: value.appVersion },
|
|
1276
|
+
...metrics === void 0 ? {} : { metrics }
|
|
1277
|
+
};
|
|
1278
|
+
}
|
|
1279
|
+
function deviceClientFor(deps) {
|
|
1280
|
+
const client = clientFor(deps);
|
|
1281
|
+
if (!client.devices) {
|
|
1282
|
+
throw new PickleballConfigurationError("Proxy client does not support devices");
|
|
1283
|
+
}
|
|
1284
|
+
return client.devices;
|
|
1285
|
+
}
|
|
1286
|
+
async function handleDeviceRegister(request, deps) {
|
|
1287
|
+
return execute(deps, async () => {
|
|
1288
|
+
const authorization = userAuthorization(request.headers);
|
|
1289
|
+
const attestation = appAttestation(request.headers);
|
|
1290
|
+
const identity = identityFromHeaders(request.headers);
|
|
1291
|
+
const input = deviceRegisterBody(await bodyObject(request));
|
|
1292
|
+
if (!identitiesMatch(identity, input)) throw new InvalidRequest();
|
|
1293
|
+
await authorize(deps, authorization, "device_register", identity, {}, attestation);
|
|
1294
|
+
return deviceClientFor(deps).register(input);
|
|
1295
|
+
});
|
|
1296
|
+
}
|
|
1297
|
+
async function handleDeviceHeartbeat(request, deps) {
|
|
1298
|
+
return execute(deps, async () => {
|
|
1299
|
+
const authorization = userAuthorization(request.headers);
|
|
1300
|
+
const attestation = appAttestation(request.headers);
|
|
1301
|
+
const identity = identityFromHeaders(request.headers);
|
|
1302
|
+
const input = deviceHeartbeatBody(await bodyObject(request));
|
|
1303
|
+
if (!identitiesMatch(identity, input)) throw new InvalidRequest();
|
|
1304
|
+
await authorize(deps, authorization, "device_heartbeat", identity, {}, attestation);
|
|
1305
|
+
return deviceClientFor(deps).heartbeat(input);
|
|
1306
|
+
});
|
|
1307
|
+
}
|
|
1012
1308
|
// Annotate the CommonJS export names for ESM import in node:
|
|
1013
1309
|
0 && (module.exports = {
|
|
1014
1310
|
createStableIdempotencyKey,
|
|
1015
1311
|
handleBootstrap,
|
|
1312
|
+
handleDeviceHeartbeat,
|
|
1313
|
+
handleDeviceRegister,
|
|
1016
1314
|
handleSessionAction,
|
|
1017
1315
|
handleSessions
|
|
1018
1316
|
});
|
package/dist/proxy.d.cts
CHANGED
|
@@ -1,5 +1,12 @@
|
|
|
1
1
|
import { PickleballLiveClient } from './index.cjs';
|
|
2
2
|
|
|
3
|
+
/**
|
|
4
|
+
* @deprecated Mô hình "proxy do đối tác host" (SDK v2, 6 route + authz gateway)
|
|
5
|
+
* được giữ để tương thích. Tích hợp mới dùng camera grant (ADR-0002):
|
|
6
|
+
* `createPickleballLiveClient().liveSessions.createCameraSession()` ở backend
|
|
7
|
+
* đối tác và `@pickleball/expo-sdk` chế độ grant trên app — không cần proxy.
|
|
8
|
+
*/
|
|
9
|
+
|
|
3
10
|
interface ProxyEnvironment {
|
|
4
11
|
PICKLEBALL_API_BASE_URL: string;
|
|
5
12
|
PICKLEBALL_APP_ID: string;
|
|
@@ -21,6 +28,7 @@ interface ProxyDependencies {
|
|
|
21
28
|
type ProxyClient = {
|
|
22
29
|
apps: Pick<PickleballLiveClient["apps"], "bootstrap">;
|
|
23
30
|
sessions: Pick<PickleballLiveClient["sessions"], "start" | "refresh" | "end" | "publish" | "unpublish">;
|
|
31
|
+
devices?: Pick<PickleballLiveClient["devices"], "register" | "heartbeat">;
|
|
24
32
|
};
|
|
25
33
|
interface SessionActionParams {
|
|
26
34
|
id: string;
|
|
@@ -35,5 +43,7 @@ declare function createStableIdempotencyKey(input: {
|
|
|
35
43
|
declare function handleBootstrap(request: Request, deps: ProxyDependencies): Promise<Response>;
|
|
36
44
|
declare function handleSessions(request: Request, deps: ProxyDependencies): Promise<Response>;
|
|
37
45
|
declare function handleSessionAction(request: Request, params: SessionActionParams, deps: ProxyDependencies): Promise<Response>;
|
|
46
|
+
declare function handleDeviceRegister(request: Request, deps: ProxyDependencies): Promise<Response>;
|
|
47
|
+
declare function handleDeviceHeartbeat(request: Request, deps: ProxyDependencies): Promise<Response>;
|
|
38
48
|
|
|
39
|
-
export { type ProxyDependencies, type ProxyEnvironment, type SessionActionParams, createStableIdempotencyKey, handleBootstrap, handleSessionAction, handleSessions };
|
|
49
|
+
export { type ProxyDependencies, type ProxyEnvironment, type SessionActionParams, createStableIdempotencyKey, handleBootstrap, handleDeviceHeartbeat, handleDeviceRegister, handleSessionAction, handleSessions };
|
package/dist/proxy.d.ts
CHANGED
|
@@ -1,5 +1,12 @@
|
|
|
1
1
|
import { PickleballLiveClient } from './index.js';
|
|
2
2
|
|
|
3
|
+
/**
|
|
4
|
+
* @deprecated Mô hình "proxy do đối tác host" (SDK v2, 6 route + authz gateway)
|
|
5
|
+
* được giữ để tương thích. Tích hợp mới dùng camera grant (ADR-0002):
|
|
6
|
+
* `createPickleballLiveClient().liveSessions.createCameraSession()` ở backend
|
|
7
|
+
* đối tác và `@pickleball/expo-sdk` chế độ grant trên app — không cần proxy.
|
|
8
|
+
*/
|
|
9
|
+
|
|
3
10
|
interface ProxyEnvironment {
|
|
4
11
|
PICKLEBALL_API_BASE_URL: string;
|
|
5
12
|
PICKLEBALL_APP_ID: string;
|
|
@@ -21,6 +28,7 @@ interface ProxyDependencies {
|
|
|
21
28
|
type ProxyClient = {
|
|
22
29
|
apps: Pick<PickleballLiveClient["apps"], "bootstrap">;
|
|
23
30
|
sessions: Pick<PickleballLiveClient["sessions"], "start" | "refresh" | "end" | "publish" | "unpublish">;
|
|
31
|
+
devices?: Pick<PickleballLiveClient["devices"], "register" | "heartbeat">;
|
|
24
32
|
};
|
|
25
33
|
interface SessionActionParams {
|
|
26
34
|
id: string;
|
|
@@ -35,5 +43,7 @@ declare function createStableIdempotencyKey(input: {
|
|
|
35
43
|
declare function handleBootstrap(request: Request, deps: ProxyDependencies): Promise<Response>;
|
|
36
44
|
declare function handleSessions(request: Request, deps: ProxyDependencies): Promise<Response>;
|
|
37
45
|
declare function handleSessionAction(request: Request, params: SessionActionParams, deps: ProxyDependencies): Promise<Response>;
|
|
46
|
+
declare function handleDeviceRegister(request: Request, deps: ProxyDependencies): Promise<Response>;
|
|
47
|
+
declare function handleDeviceHeartbeat(request: Request, deps: ProxyDependencies): Promise<Response>;
|
|
38
48
|
|
|
39
|
-
export { type ProxyDependencies, type ProxyEnvironment, type SessionActionParams, createStableIdempotencyKey, handleBootstrap, handleSessionAction, handleSessions };
|
|
49
|
+
export { type ProxyDependencies, type ProxyEnvironment, type SessionActionParams, createStableIdempotencyKey, handleBootstrap, handleDeviceHeartbeat, handleDeviceRegister, handleSessionAction, handleSessions };
|