@tinycloud/sdk-services 3.3.0-beta.7 → 3.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/dist/{BaseService-Cc6Ueicm.d.cts → BaseService-BhfKzKke.d.cts} +5 -1
- package/dist/{BaseService-DOdXd7sr.d.ts → BaseService-x4AFZiEa.d.ts} +5 -1
- package/dist/encryption/index.cjs +18 -5
- package/dist/encryption/index.cjs.map +1 -1
- package/dist/encryption/index.d.cts +2 -2
- package/dist/encryption/index.d.ts +2 -2
- package/dist/encryption/index.js +18 -5
- package/dist/encryption/index.js.map +1 -1
- package/dist/index.cjs +911 -797
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +95 -11
- package/dist/index.d.ts +95 -11
- package/dist/index.js +911 -797
- package/dist/index.js.map +1 -1
- package/dist/internal/decrypt-transport-response-error.d.cts +1 -1
- package/dist/internal/decrypt-transport-response-error.d.ts +1 -1
- package/dist/kv/index.cjs +72 -80
- package/dist/kv/index.cjs.map +1 -1
- package/dist/kv/index.d.cts +4 -5
- package/dist/kv/index.d.ts +4 -5
- package/dist/kv/index.js +72 -80
- package/dist/kv/index.js.map +1 -1
- package/dist/kv/replication/index.cjs +6 -5
- package/dist/kv/replication/index.cjs.map +1 -1
- package/dist/kv/replication/index.d.cts +3 -3
- package/dist/kv/replication/index.d.ts +3 -3
- package/dist/kv/replication/index.js +6 -5
- package/dist/kv/replication/index.js.map +1 -1
- package/dist/{scope-B-m6cKH-.d.ts → scope-BHTkk5Yk.d.ts} +13 -2
- package/dist/{scope-B7oeKdLM.d.cts → scope-DMhalRFn.d.cts} +13 -2
- package/dist/sql/index.cjs +30 -16
- package/dist/sql/index.cjs.map +1 -1
- package/dist/sql/index.d.cts +2 -2
- package/dist/sql/index.d.ts +2 -2
- package/dist/sql/index.js +30 -16
- package/dist/sql/index.js.map +1 -1
- package/dist/{types-BjH0ZtGy.d.cts → types-eVS1Whuf.d.cts} +24 -4
- package/dist/{types-BjH0ZtGy.d.ts → types-eVS1Whuf.d.ts} +24 -4
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -16,7 +16,7 @@ var ErrorCodes = {
|
|
|
16
16
|
PERMISSION_DENIED: "PERMISSION_DENIED",
|
|
17
17
|
// Node retry signals (tinycloud-node #347). `meta.retryable` says whether a
|
|
18
18
|
// retry is safe; `meta.retryAfterMs` carries the node's Retry-After.
|
|
19
|
-
/** HTTP 503 `sql_busy:<stage>` / `unavailable_retryable`: nothing was applied; retry after `meta.retryAfterMs`. */
|
|
19
|
+
/** HTTP 503 `sql_busy:<stage>` / `unavailable_retryable`/`read_audit_busy`: nothing was applied; retry after `meta.retryAfterMs`. */
|
|
20
20
|
NODE_BUSY: "NODE_BUSY",
|
|
21
21
|
/** HTTP 503 `request_timeout`: the node's deadline passed before any write began; nothing was applied. */
|
|
22
22
|
REQUEST_TIMEOUT: "REQUEST_TIMEOUT",
|
|
@@ -57,11 +57,12 @@ var ErrorCodes = {
|
|
|
57
57
|
DUCKDB_READONLY_VIOLATION: "DUCKDB_READONLY_VIOLATION"
|
|
58
58
|
};
|
|
59
59
|
var defaultRetryPolicy = {
|
|
60
|
-
maxAttempts:
|
|
60
|
+
maxAttempts: 4,
|
|
61
61
|
backoff: "exponential",
|
|
62
|
-
baseDelayMs:
|
|
63
|
-
maxDelayMs:
|
|
64
|
-
retryableErrors: [ErrorCodes.
|
|
62
|
+
baseDelayMs: 250,
|
|
63
|
+
maxDelayMs: 4e3,
|
|
64
|
+
retryableErrors: [ErrorCodes.NODE_BUSY, ErrorCodes.REQUEST_TIMEOUT],
|
|
65
|
+
maxTotalMs: 12e3
|
|
65
66
|
};
|
|
66
67
|
var TelemetryEvents = {
|
|
67
68
|
SPAN: "telemetry.span",
|
|
@@ -488,7 +489,9 @@ var RetryPolicySchema = z.object({
|
|
|
488
489
|
/** Maximum delay in milliseconds between retries */
|
|
489
490
|
maxDelayMs: z.number().nonnegative(),
|
|
490
491
|
/** Error codes that should trigger a retry */
|
|
491
|
-
retryableErrors: z.array(z.string())
|
|
492
|
+
retryableErrors: z.array(z.string()),
|
|
493
|
+
/** Total time budget for all attempts and waits, in milliseconds */
|
|
494
|
+
maxTotalMs: z.number().positive().optional()
|
|
492
495
|
});
|
|
493
496
|
var ServiceSessionSchema = z.object({
|
|
494
497
|
/** The delegation header containing the UCAN */
|
|
@@ -625,448 +628,107 @@ function validateTelemetrySpanEvent(data) {
|
|
|
625
628
|
return { ok: true, data: result.data };
|
|
626
629
|
}
|
|
627
630
|
|
|
628
|
-
// src/
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
this._abortController = new AbortController();
|
|
635
|
-
this._retired = false;
|
|
636
|
-
this._invoke = this.wrapInvoke(config.invoke);
|
|
637
|
-
this._invokeAny = config.invokeAny ? this.wrapInvokeAny(config.invokeAny) : void 0;
|
|
638
|
-
this._fetch = this.wrapFetch(config.fetch ?? globalThis.fetch.bind(globalThis));
|
|
639
|
-
this._hosts = config.hosts;
|
|
640
|
-
this._session = config.session ?? null;
|
|
641
|
-
this._retryPolicy = {
|
|
642
|
-
...defaultRetryPolicy,
|
|
643
|
-
...config.retryPolicy
|
|
644
|
-
};
|
|
645
|
-
this._telemetryEnabled = typeof config.telemetry === "boolean" ? config.telemetry : config.telemetry?.enabled === true;
|
|
646
|
-
this._telemetryHandler = typeof config.telemetry === "object" ? config.telemetry.onEvent : void 0;
|
|
647
|
-
}
|
|
648
|
-
// ============================================================
|
|
649
|
-
// Session Management
|
|
650
|
-
// ============================================================
|
|
651
|
-
/**
|
|
652
|
-
* Get the current session.
|
|
653
|
-
*/
|
|
654
|
-
get session() {
|
|
655
|
-
return this._retired ? null : this._session;
|
|
631
|
+
// src/responseErrors.ts
|
|
632
|
+
function parseServiceErrorBody(errorText) {
|
|
633
|
+
try {
|
|
634
|
+
return JSON.parse(errorText);
|
|
635
|
+
} catch {
|
|
636
|
+
return {};
|
|
656
637
|
}
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
return !this._retired && this._session !== null;
|
|
638
|
+
}
|
|
639
|
+
function formatServiceResponseError(serviceLabel, operation, status, errorText, parsed) {
|
|
640
|
+
if (parsed.message) {
|
|
641
|
+
return compactMessage(parsed.message);
|
|
662
642
|
}
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
* @param session - New session or null to clear
|
|
667
|
-
*/
|
|
668
|
-
setSession(session) {
|
|
669
|
-
this.assertActive();
|
|
670
|
-
this._session = session;
|
|
671
|
-
this.emit("session.changed", { authenticated: session !== null });
|
|
672
|
-
for (const service of this._services.values()) {
|
|
673
|
-
service.onSessionChange(session);
|
|
643
|
+
if (looksLikeHtml(errorText)) {
|
|
644
|
+
if (status === 524 || /524\s*[:-]/i.test(errorText) || /a timeout occurred/i.test(errorText)) {
|
|
645
|
+
return `${serviceLabel} ${operation} failed: upstream request timed out. Please retry.`;
|
|
674
646
|
}
|
|
647
|
+
return `${serviceLabel} ${operation} failed: upstream service returned an HTML error page (${status}).`;
|
|
675
648
|
}
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
this.assertActive();
|
|
684
|
-
return this._invoke;
|
|
685
|
-
}
|
|
686
|
-
/**
|
|
687
|
-
* Get the multi-resource invoke function when available.
|
|
688
|
-
*/
|
|
689
|
-
get invokeAny() {
|
|
690
|
-
this.assertActive();
|
|
691
|
-
return this._invokeAny;
|
|
649
|
+
return `${serviceLabel} ${operation} failed: ${status} - ${compactMessage(errorText)}`;
|
|
650
|
+
}
|
|
651
|
+
function responseErrorMeta(status, statusText, errorText) {
|
|
652
|
+
const meta = { status, statusText };
|
|
653
|
+
const snippet = compactMessage(errorText);
|
|
654
|
+
if (snippet) {
|
|
655
|
+
meta.responseSnippet = snippet.slice(0, 300);
|
|
692
656
|
}
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
657
|
+
return meta;
|
|
658
|
+
}
|
|
659
|
+
function looksLikeHtml(text) {
|
|
660
|
+
return /<!doctype html/i.test(text) || /<html[\s>]/i.test(text);
|
|
661
|
+
}
|
|
662
|
+
function compactMessage(message) {
|
|
663
|
+
const compact = message.replace(/\s+/g, " ").trim();
|
|
664
|
+
return compact.length > 500 ? `${compact.slice(0, 500)}...` : compact;
|
|
665
|
+
}
|
|
666
|
+
|
|
667
|
+
// src/errors.ts
|
|
668
|
+
import { CAPABILITY_REGISTRY } from "@tinycloud/bootstrap";
|
|
669
|
+
|
|
670
|
+
// src/encryption/networkId.ts
|
|
671
|
+
var URN_PREFIX = "urn:tinycloud:encryption:";
|
|
672
|
+
var NETWORK_NAME_RE = /^[a-z0-9][a-z0-9-]*$/;
|
|
673
|
+
var PKH_EIP155_DID_RE = /^did:pkh:eip155:(\d+):(0x[a-fA-F0-9]{40})$/;
|
|
674
|
+
var NetworkIdError = class extends Error {
|
|
675
|
+
constructor(message) {
|
|
676
|
+
super(message);
|
|
677
|
+
this.name = "NetworkIdError";
|
|
699
678
|
}
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
this.assertActive();
|
|
705
|
-
return this._hosts;
|
|
679
|
+
};
|
|
680
|
+
function parseNetworkId(networkId) {
|
|
681
|
+
if (typeof networkId !== "string" || networkId.length === 0) {
|
|
682
|
+
throw new NetworkIdError("networkId must be a non-empty string");
|
|
706
683
|
}
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
* Register a service with the context.
|
|
712
|
-
*
|
|
713
|
-
* @param name - Service name (e.g., 'kv')
|
|
714
|
-
* @param service - Service instance
|
|
715
|
-
*/
|
|
716
|
-
registerService(name, service) {
|
|
717
|
-
this._services.set(name, service);
|
|
684
|
+
if (!networkId.startsWith(URN_PREFIX)) {
|
|
685
|
+
throw new NetworkIdError(
|
|
686
|
+
`networkId must start with ${URN_PREFIX} (got ${JSON.stringify(networkId)})`
|
|
687
|
+
);
|
|
718
688
|
}
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
this._services.delete(name);
|
|
689
|
+
const body = networkId.slice(URN_PREFIX.length);
|
|
690
|
+
const lastColon = body.lastIndexOf(":");
|
|
691
|
+
if (lastColon <= 0 || lastColon === body.length - 1) {
|
|
692
|
+
throw new NetworkIdError(
|
|
693
|
+
`networkId missing ownerDid or name segment (got ${JSON.stringify(networkId)})`
|
|
694
|
+
);
|
|
726
695
|
}
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
getService(name) {
|
|
734
|
-
return this._services.get(name);
|
|
696
|
+
const ownerDid = body.slice(0, lastColon);
|
|
697
|
+
const name = body.slice(lastColon + 1);
|
|
698
|
+
if (!ownerDid.startsWith("did:")) {
|
|
699
|
+
throw new NetworkIdError(
|
|
700
|
+
`networkId ownerDid must be a DID (got ${JSON.stringify(ownerDid)})`
|
|
701
|
+
);
|
|
735
702
|
}
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
*
|
|
742
|
-
* @param event - Event name
|
|
743
|
-
* @param data - Event data
|
|
744
|
-
*/
|
|
745
|
-
emit(event, data) {
|
|
746
|
-
tinyCloudDebugLogger.log(event, data);
|
|
747
|
-
if (this._telemetryEnabled && this._telemetryHandler) {
|
|
748
|
-
try {
|
|
749
|
-
this._telemetryHandler(event, projectDiagnosticData(data));
|
|
750
|
-
} catch (error) {
|
|
751
|
-
console.error(`Error in telemetry handler for "${event}":`, error);
|
|
752
|
-
}
|
|
753
|
-
}
|
|
754
|
-
const handlers = this._eventHandlers.get(event);
|
|
755
|
-
if (handlers) {
|
|
756
|
-
for (const handler of handlers) {
|
|
757
|
-
try {
|
|
758
|
-
handler(data);
|
|
759
|
-
} catch (error) {
|
|
760
|
-
console.error(`Error in event handler for "${event}":`, error);
|
|
761
|
-
}
|
|
762
|
-
}
|
|
763
|
-
}
|
|
703
|
+
const didParts = ownerDid.split(":");
|
|
704
|
+
if (didParts.length < 3 || didParts.some((p) => p.length === 0)) {
|
|
705
|
+
throw new NetworkIdError(
|
|
706
|
+
`networkId ownerDid is not a well-formed DID (got ${JSON.stringify(ownerDid)})`
|
|
707
|
+
);
|
|
764
708
|
}
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
* @param handler - Handler function
|
|
770
|
-
* @returns Unsubscribe function
|
|
771
|
-
*/
|
|
772
|
-
on(event, handler) {
|
|
773
|
-
if (!this._eventHandlers.has(event)) {
|
|
774
|
-
this._eventHandlers.set(event, /* @__PURE__ */ new Set());
|
|
775
|
-
}
|
|
776
|
-
this._eventHandlers.get(event).add(handler);
|
|
777
|
-
return () => {
|
|
778
|
-
const handlers = this._eventHandlers.get(event);
|
|
779
|
-
if (handlers) {
|
|
780
|
-
handlers.delete(handler);
|
|
781
|
-
if (handlers.size === 0) {
|
|
782
|
-
this._eventHandlers.delete(event);
|
|
783
|
-
}
|
|
784
|
-
}
|
|
785
|
-
};
|
|
709
|
+
if (!NETWORK_NAME_RE.test(name)) {
|
|
710
|
+
throw new NetworkIdError(
|
|
711
|
+
`networkId name ${JSON.stringify(name)} must match ${NETWORK_NAME_RE.source}`
|
|
712
|
+
);
|
|
786
713
|
}
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
clearEventHandlers(event) {
|
|
793
|
-
if (event) {
|
|
794
|
-
this._eventHandlers.delete(event);
|
|
795
|
-
} else {
|
|
796
|
-
this._eventHandlers.clear();
|
|
797
|
-
}
|
|
714
|
+
return { networkId, ownerDid, name };
|
|
715
|
+
}
|
|
716
|
+
function buildNetworkId(ownerDid, name) {
|
|
717
|
+
if (typeof ownerDid !== "string" || !ownerDid.startsWith("did:")) {
|
|
718
|
+
throw new NetworkIdError("ownerDid must be a DID");
|
|
798
719
|
}
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
* Get the abort signal for cancelling operations.
|
|
804
|
-
*/
|
|
805
|
-
get abortSignal() {
|
|
806
|
-
return this._abortController.signal;
|
|
720
|
+
if (typeof name !== "string" || !NETWORK_NAME_RE.test(name)) {
|
|
721
|
+
throw new NetworkIdError(
|
|
722
|
+
`network name ${JSON.stringify(name)} must match ${NETWORK_NAME_RE.source}`
|
|
723
|
+
);
|
|
807
724
|
}
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
* Permanently retire this graph after its owner installs a replacement.
|
|
816
|
-
* Unlike `abort()`, retirement never creates a fresh controller, so captured
|
|
817
|
-
* services cannot resume calls under a superseded session.
|
|
818
|
-
*/
|
|
819
|
-
retire() {
|
|
820
|
-
if (this._retired) return;
|
|
821
|
-
this._retired = true;
|
|
822
|
-
this._session = null;
|
|
823
|
-
this._abortController.abort();
|
|
824
|
-
for (const service of this._services.values()) {
|
|
825
|
-
try {
|
|
826
|
-
service.onSignOut();
|
|
827
|
-
} catch (error) {
|
|
828
|
-
console.error("Error retiring service:", error);
|
|
829
|
-
}
|
|
830
|
-
}
|
|
831
|
-
}
|
|
832
|
-
/**
|
|
833
|
-
* Abort all pending operations and notify services.
|
|
834
|
-
* Creates a new AbortController for future operations.
|
|
835
|
-
*/
|
|
836
|
-
abort() {
|
|
837
|
-
if (this._retired) return;
|
|
838
|
-
this._abortController.abort();
|
|
839
|
-
this._abortController = new AbortController();
|
|
840
|
-
for (const service of this._services.values()) {
|
|
841
|
-
try {
|
|
842
|
-
service.onSignOut();
|
|
843
|
-
} catch (error) {
|
|
844
|
-
console.error("Error signing out service:", error);
|
|
845
|
-
}
|
|
846
|
-
}
|
|
847
|
-
}
|
|
848
|
-
/**
|
|
849
|
-
* Sign out - abort operations and clear session.
|
|
850
|
-
*/
|
|
851
|
-
signOut() {
|
|
852
|
-
if (this._retired) return;
|
|
853
|
-
this.abort();
|
|
854
|
-
this.setSession(null);
|
|
855
|
-
this.emit("session.expired", {});
|
|
856
|
-
}
|
|
857
|
-
// ============================================================
|
|
858
|
-
// Retry Policy
|
|
859
|
-
// ============================================================
|
|
860
|
-
/**
|
|
861
|
-
* Get the retry policy configuration.
|
|
862
|
-
*/
|
|
863
|
-
get retryPolicy() {
|
|
864
|
-
return this._retryPolicy;
|
|
865
|
-
}
|
|
866
|
-
wrapInvoke(invoke) {
|
|
867
|
-
return (session, service, path, action, facts) => {
|
|
868
|
-
this.assertActive();
|
|
869
|
-
if (!tinyCloudDebugLogger.isEnabled()) {
|
|
870
|
-
return invoke(session, service, path, action, facts);
|
|
871
|
-
}
|
|
872
|
-
const timer = tinyCloudDebugLogger.startTimer("sdk.invoke", {
|
|
873
|
-
service,
|
|
874
|
-
path,
|
|
875
|
-
action,
|
|
876
|
-
facts
|
|
877
|
-
});
|
|
878
|
-
try {
|
|
879
|
-
const result = invoke(session, service, path, action, facts);
|
|
880
|
-
timer.stop({ ok: true, service, path, action });
|
|
881
|
-
return result;
|
|
882
|
-
} catch (error) {
|
|
883
|
-
timer.stop({ ok: false, service, path, action, error });
|
|
884
|
-
throw error;
|
|
885
|
-
}
|
|
886
|
-
};
|
|
887
|
-
}
|
|
888
|
-
assertActive() {
|
|
889
|
-
if (this._retired) {
|
|
890
|
-
throw new Error("Service graph has been retired: its session was replaced or signed out.");
|
|
891
|
-
}
|
|
892
|
-
}
|
|
893
|
-
wrapInvokeAny(invokeAny) {
|
|
894
|
-
return (session, entries, facts) => {
|
|
895
|
-
this.assertActive();
|
|
896
|
-
if (!tinyCloudDebugLogger.isEnabled()) {
|
|
897
|
-
return invokeAny(session, entries, facts);
|
|
898
|
-
}
|
|
899
|
-
const timer = tinyCloudDebugLogger.startTimer("sdk.invokeAny", {
|
|
900
|
-
entries,
|
|
901
|
-
facts
|
|
902
|
-
});
|
|
903
|
-
try {
|
|
904
|
-
const result = invokeAny(session, entries, facts);
|
|
905
|
-
timer.stop({ ok: true, entries });
|
|
906
|
-
return result;
|
|
907
|
-
} catch (error) {
|
|
908
|
-
timer.stop({ ok: false, entries, error });
|
|
909
|
-
throw error;
|
|
910
|
-
}
|
|
911
|
-
};
|
|
912
|
-
}
|
|
913
|
-
wrapFetch(fetchFn) {
|
|
914
|
-
return async (url, init) => {
|
|
915
|
-
this.assertActive();
|
|
916
|
-
const request = {
|
|
917
|
-
...init,
|
|
918
|
-
signal: this.combineSignals(init?.signal)
|
|
919
|
-
};
|
|
920
|
-
if (!tinyCloudDebugLogger.isEnabled()) {
|
|
921
|
-
return fetchFn(url, request);
|
|
922
|
-
}
|
|
923
|
-
const timer = tinyCloudDebugLogger.startTimer("sdk.fetch", {
|
|
924
|
-
url,
|
|
925
|
-
method: request.method ?? "GET",
|
|
926
|
-
init: request
|
|
927
|
-
});
|
|
928
|
-
try {
|
|
929
|
-
const response = await fetchFn(url, request);
|
|
930
|
-
timer.stop({
|
|
931
|
-
ok: response.ok,
|
|
932
|
-
url,
|
|
933
|
-
method: request.method ?? "GET",
|
|
934
|
-
status: response.status,
|
|
935
|
-
statusText: response.statusText
|
|
936
|
-
});
|
|
937
|
-
return response;
|
|
938
|
-
} catch (error) {
|
|
939
|
-
timer.stop({
|
|
940
|
-
ok: false,
|
|
941
|
-
url,
|
|
942
|
-
method: request.method ?? "GET",
|
|
943
|
-
error
|
|
944
|
-
});
|
|
945
|
-
throw error;
|
|
946
|
-
}
|
|
947
|
-
};
|
|
948
|
-
}
|
|
949
|
-
combineSignals(signal) {
|
|
950
|
-
if (!signal) return this._abortController.signal;
|
|
951
|
-
const combined = new AbortController();
|
|
952
|
-
const abort = (source) => combined.abort(source.reason);
|
|
953
|
-
if (signal.aborted) {
|
|
954
|
-
abort(signal);
|
|
955
|
-
} else if (this._abortController.signal.aborted) {
|
|
956
|
-
abort(this._abortController.signal);
|
|
957
|
-
} else {
|
|
958
|
-
signal.addEventListener("abort", () => abort(signal), { once: true });
|
|
959
|
-
this._abortController.signal.addEventListener(
|
|
960
|
-
"abort",
|
|
961
|
-
() => abort(this._abortController.signal),
|
|
962
|
-
{ once: true }
|
|
963
|
-
);
|
|
964
|
-
}
|
|
965
|
-
return combined.signal;
|
|
966
|
-
}
|
|
967
|
-
};
|
|
968
|
-
|
|
969
|
-
// src/responseErrors.ts
|
|
970
|
-
function parseServiceErrorBody(errorText) {
|
|
971
|
-
try {
|
|
972
|
-
return JSON.parse(errorText);
|
|
973
|
-
} catch {
|
|
974
|
-
return {};
|
|
975
|
-
}
|
|
976
|
-
}
|
|
977
|
-
function formatServiceResponseError(serviceLabel, operation, status, errorText, parsed) {
|
|
978
|
-
if (parsed.message) {
|
|
979
|
-
return compactMessage(parsed.message);
|
|
980
|
-
}
|
|
981
|
-
if (looksLikeHtml(errorText)) {
|
|
982
|
-
if (status === 524 || /524\s*[:-]/i.test(errorText) || /a timeout occurred/i.test(errorText)) {
|
|
983
|
-
return `${serviceLabel} ${operation} failed: upstream request timed out. Please retry.`;
|
|
984
|
-
}
|
|
985
|
-
return `${serviceLabel} ${operation} failed: upstream service returned an HTML error page (${status}).`;
|
|
986
|
-
}
|
|
987
|
-
return `${serviceLabel} ${operation} failed: ${status} - ${compactMessage(errorText)}`;
|
|
988
|
-
}
|
|
989
|
-
function responseErrorMeta(status, statusText, errorText) {
|
|
990
|
-
const meta = { status, statusText };
|
|
991
|
-
const snippet = compactMessage(errorText);
|
|
992
|
-
if (snippet) {
|
|
993
|
-
meta.responseSnippet = snippet.slice(0, 300);
|
|
994
|
-
}
|
|
995
|
-
return meta;
|
|
996
|
-
}
|
|
997
|
-
function looksLikeHtml(text) {
|
|
998
|
-
return /<!doctype html/i.test(text) || /<html[\s>]/i.test(text);
|
|
999
|
-
}
|
|
1000
|
-
function compactMessage(message) {
|
|
1001
|
-
const compact = message.replace(/\s+/g, " ").trim();
|
|
1002
|
-
return compact.length > 500 ? `${compact.slice(0, 500)}...` : compact;
|
|
1003
|
-
}
|
|
1004
|
-
|
|
1005
|
-
// src/errors.ts
|
|
1006
|
-
import { CAPABILITY_REGISTRY } from "@tinycloud/bootstrap";
|
|
1007
|
-
|
|
1008
|
-
// src/encryption/networkId.ts
|
|
1009
|
-
var URN_PREFIX = "urn:tinycloud:encryption:";
|
|
1010
|
-
var NETWORK_NAME_RE = /^[a-z0-9][a-z0-9-]*$/;
|
|
1011
|
-
var PKH_EIP155_DID_RE = /^did:pkh:eip155:(\d+):(0x[a-fA-F0-9]{40})$/;
|
|
1012
|
-
var NetworkIdError = class extends Error {
|
|
1013
|
-
constructor(message) {
|
|
1014
|
-
super(message);
|
|
1015
|
-
this.name = "NetworkIdError";
|
|
1016
|
-
}
|
|
1017
|
-
};
|
|
1018
|
-
function parseNetworkId(networkId) {
|
|
1019
|
-
if (typeof networkId !== "string" || networkId.length === 0) {
|
|
1020
|
-
throw new NetworkIdError("networkId must be a non-empty string");
|
|
1021
|
-
}
|
|
1022
|
-
if (!networkId.startsWith(URN_PREFIX)) {
|
|
1023
|
-
throw new NetworkIdError(
|
|
1024
|
-
`networkId must start with ${URN_PREFIX} (got ${JSON.stringify(networkId)})`
|
|
1025
|
-
);
|
|
1026
|
-
}
|
|
1027
|
-
const body = networkId.slice(URN_PREFIX.length);
|
|
1028
|
-
const lastColon = body.lastIndexOf(":");
|
|
1029
|
-
if (lastColon <= 0 || lastColon === body.length - 1) {
|
|
1030
|
-
throw new NetworkIdError(
|
|
1031
|
-
`networkId missing ownerDid or name segment (got ${JSON.stringify(networkId)})`
|
|
1032
|
-
);
|
|
1033
|
-
}
|
|
1034
|
-
const ownerDid = body.slice(0, lastColon);
|
|
1035
|
-
const name = body.slice(lastColon + 1);
|
|
1036
|
-
if (!ownerDid.startsWith("did:")) {
|
|
1037
|
-
throw new NetworkIdError(
|
|
1038
|
-
`networkId ownerDid must be a DID (got ${JSON.stringify(ownerDid)})`
|
|
1039
|
-
);
|
|
1040
|
-
}
|
|
1041
|
-
const didParts = ownerDid.split(":");
|
|
1042
|
-
if (didParts.length < 3 || didParts.some((p) => p.length === 0)) {
|
|
1043
|
-
throw new NetworkIdError(
|
|
1044
|
-
`networkId ownerDid is not a well-formed DID (got ${JSON.stringify(ownerDid)})`
|
|
1045
|
-
);
|
|
1046
|
-
}
|
|
1047
|
-
if (!NETWORK_NAME_RE.test(name)) {
|
|
1048
|
-
throw new NetworkIdError(
|
|
1049
|
-
`networkId name ${JSON.stringify(name)} must match ${NETWORK_NAME_RE.source}`
|
|
1050
|
-
);
|
|
1051
|
-
}
|
|
1052
|
-
return { networkId, ownerDid, name };
|
|
1053
|
-
}
|
|
1054
|
-
function buildNetworkId(ownerDid, name) {
|
|
1055
|
-
if (typeof ownerDid !== "string" || !ownerDid.startsWith("did:")) {
|
|
1056
|
-
throw new NetworkIdError("ownerDid must be a DID");
|
|
1057
|
-
}
|
|
1058
|
-
if (typeof name !== "string" || !NETWORK_NAME_RE.test(name)) {
|
|
1059
|
-
throw new NetworkIdError(
|
|
1060
|
-
`network name ${JSON.stringify(name)} must match ${NETWORK_NAME_RE.source}`
|
|
1061
|
-
);
|
|
1062
|
-
}
|
|
1063
|
-
const networkId = `${URN_PREFIX}${ownerDid}:${name}`;
|
|
1064
|
-
parseNetworkId(networkId);
|
|
1065
|
-
return networkId;
|
|
1066
|
-
}
|
|
1067
|
-
function isNetworkId(networkId) {
|
|
1068
|
-
if (typeof networkId !== "string") {
|
|
1069
|
-
return false;
|
|
725
|
+
const networkId = `${URN_PREFIX}${ownerDid}:${name}`;
|
|
726
|
+
parseNetworkId(networkId);
|
|
727
|
+
return networkId;
|
|
728
|
+
}
|
|
729
|
+
function isNetworkId(networkId) {
|
|
730
|
+
if (typeof networkId !== "string") {
|
|
731
|
+
return false;
|
|
1070
732
|
}
|
|
1071
733
|
try {
|
|
1072
734
|
parseNetworkId(networkId);
|
|
@@ -1165,7 +827,7 @@ function nodeRetryError(service, message, response, errorText) {
|
|
|
1165
827
|
const body = errorText.trim();
|
|
1166
828
|
const [, token, stage] = /^([a-z_]+)(?::\s*([a-z_]+):)?/.exec(body) ?? [];
|
|
1167
829
|
let code;
|
|
1168
|
-
if (response.status === 503 && (token === "sql_busy" || token === "unavailable_retryable")) {
|
|
830
|
+
if (response.status === 503 && (token === "sql_busy" || token === "unavailable_retryable" || token === "read_audit_busy")) {
|
|
1169
831
|
code = ErrorCodes.NODE_BUSY;
|
|
1170
832
|
} else if (response.status === 503 && token === "request_timeout") {
|
|
1171
833
|
code = ErrorCodes.REQUEST_TIMEOUT;
|
|
@@ -1414,25 +1076,411 @@ function wrapError(service, error, defaultCode = ErrorCodes.NETWORK_ERROR) {
|
|
|
1414
1076
|
if (error.name === "AbortError") {
|
|
1415
1077
|
return abortedError(service);
|
|
1416
1078
|
}
|
|
1417
|
-
if (error.name === "TimeoutError" || error.message.toLowerCase().includes("timeout")) {
|
|
1418
|
-
return timeoutError(service);
|
|
1079
|
+
if (error.name === "TimeoutError" || error.message.toLowerCase().includes("timeout")) {
|
|
1080
|
+
return timeoutError(service);
|
|
1081
|
+
}
|
|
1082
|
+
return {
|
|
1083
|
+
code: defaultCode,
|
|
1084
|
+
message: error.message,
|
|
1085
|
+
service,
|
|
1086
|
+
cause: error
|
|
1087
|
+
};
|
|
1088
|
+
}
|
|
1089
|
+
return {
|
|
1090
|
+
code: defaultCode,
|
|
1091
|
+
message: String(error),
|
|
1092
|
+
service
|
|
1093
|
+
};
|
|
1094
|
+
}
|
|
1095
|
+
function errorResult(error) {
|
|
1096
|
+
return err(error);
|
|
1097
|
+
}
|
|
1098
|
+
|
|
1099
|
+
// src/sleep.ts
|
|
1100
|
+
var abortableSleep = (ms, signal, unref = false) => new Promise((resolve) => {
|
|
1101
|
+
const done = () => {
|
|
1102
|
+
clearTimeout(timer);
|
|
1103
|
+
signal.removeEventListener("abort", done);
|
|
1104
|
+
resolve();
|
|
1105
|
+
};
|
|
1106
|
+
const timer = setTimeout(done, ms);
|
|
1107
|
+
if (unref) timer.unref?.();
|
|
1108
|
+
signal.aborted ? done() : signal.addEventListener("abort", done);
|
|
1109
|
+
});
|
|
1110
|
+
|
|
1111
|
+
// src/context.ts
|
|
1112
|
+
var ServiceContext = class {
|
|
1113
|
+
constructor(config) {
|
|
1114
|
+
this._session = null;
|
|
1115
|
+
this._services = /* @__PURE__ */ new Map();
|
|
1116
|
+
this._eventHandlers = /* @__PURE__ */ new Map();
|
|
1117
|
+
this._abortController = new AbortController();
|
|
1118
|
+
this._retired = false;
|
|
1119
|
+
this._invoke = this.wrapInvoke(config.invoke);
|
|
1120
|
+
this._invokeAny = config.invokeAny ? this.wrapInvokeAny(config.invokeAny) : void 0;
|
|
1121
|
+
this._fetch = this.wrapFetch(config.fetch ?? globalThis.fetch.bind(globalThis));
|
|
1122
|
+
this._hosts = config.hosts;
|
|
1123
|
+
this._session = config.session ?? null;
|
|
1124
|
+
this._retryPolicy = {
|
|
1125
|
+
...defaultRetryPolicy,
|
|
1126
|
+
...config.retryPolicy
|
|
1127
|
+
};
|
|
1128
|
+
this._telemetryEnabled = typeof config.telemetry === "boolean" ? config.telemetry : config.telemetry?.enabled === true;
|
|
1129
|
+
this._telemetryHandler = typeof config.telemetry === "object" ? config.telemetry.onEvent : void 0;
|
|
1130
|
+
}
|
|
1131
|
+
// ============================================================
|
|
1132
|
+
// Session Management
|
|
1133
|
+
// ============================================================
|
|
1134
|
+
/**
|
|
1135
|
+
* Get the current session.
|
|
1136
|
+
*/
|
|
1137
|
+
get session() {
|
|
1138
|
+
return this._retired ? null : this._session;
|
|
1139
|
+
}
|
|
1140
|
+
/**
|
|
1141
|
+
* Check if the context has an authenticated session.
|
|
1142
|
+
*/
|
|
1143
|
+
get isAuthenticated() {
|
|
1144
|
+
return !this._retired && this._session !== null;
|
|
1145
|
+
}
|
|
1146
|
+
/**
|
|
1147
|
+
* Update the session and notify all registered services.
|
|
1148
|
+
*
|
|
1149
|
+
* @param session - New session or null to clear
|
|
1150
|
+
*/
|
|
1151
|
+
setSession(session) {
|
|
1152
|
+
this.assertActive();
|
|
1153
|
+
this._session = session;
|
|
1154
|
+
this.emit("session.changed", { authenticated: session !== null });
|
|
1155
|
+
for (const service of this._services.values()) {
|
|
1156
|
+
service.onSessionChange(session);
|
|
1157
|
+
}
|
|
1158
|
+
}
|
|
1159
|
+
// ============================================================
|
|
1160
|
+
// Platform Dependencies
|
|
1161
|
+
// ============================================================
|
|
1162
|
+
/**
|
|
1163
|
+
* Get the invoke function for WASM operations.
|
|
1164
|
+
*/
|
|
1165
|
+
get invoke() {
|
|
1166
|
+
this.assertActive();
|
|
1167
|
+
return this._invoke;
|
|
1168
|
+
}
|
|
1169
|
+
/**
|
|
1170
|
+
* Get the multi-resource invoke function when available.
|
|
1171
|
+
*/
|
|
1172
|
+
get invokeAny() {
|
|
1173
|
+
this.assertActive();
|
|
1174
|
+
return this._invokeAny;
|
|
1175
|
+
}
|
|
1176
|
+
/**
|
|
1177
|
+
* Get the fetch function for HTTP requests.
|
|
1178
|
+
*/
|
|
1179
|
+
get fetch() {
|
|
1180
|
+
this.assertActive();
|
|
1181
|
+
return this._fetch;
|
|
1182
|
+
}
|
|
1183
|
+
/**
|
|
1184
|
+
* Get the list of TinyCloud host URLs.
|
|
1185
|
+
*/
|
|
1186
|
+
get hosts() {
|
|
1187
|
+
this.assertActive();
|
|
1188
|
+
return this._hosts;
|
|
1189
|
+
}
|
|
1190
|
+
// ============================================================
|
|
1191
|
+
// Service Registry
|
|
1192
|
+
// ============================================================
|
|
1193
|
+
/**
|
|
1194
|
+
* Register a service with the context.
|
|
1195
|
+
*
|
|
1196
|
+
* @param name - Service name (e.g., 'kv')
|
|
1197
|
+
* @param service - Service instance
|
|
1198
|
+
*/
|
|
1199
|
+
registerService(name, service) {
|
|
1200
|
+
this._services.set(name, service);
|
|
1201
|
+
}
|
|
1202
|
+
/**
|
|
1203
|
+
* Unregister a service from the context.
|
|
1204
|
+
*
|
|
1205
|
+
* @param name - Service name to remove
|
|
1206
|
+
*/
|
|
1207
|
+
unregisterService(name) {
|
|
1208
|
+
this._services.delete(name);
|
|
1209
|
+
}
|
|
1210
|
+
/**
|
|
1211
|
+
* Get a registered service by name.
|
|
1212
|
+
*
|
|
1213
|
+
* @param name - Service name
|
|
1214
|
+
* @returns The service instance or undefined if not registered
|
|
1215
|
+
*/
|
|
1216
|
+
getService(name) {
|
|
1217
|
+
return this._services.get(name);
|
|
1218
|
+
}
|
|
1219
|
+
// ============================================================
|
|
1220
|
+
// Event System (Telemetry)
|
|
1221
|
+
// ============================================================
|
|
1222
|
+
/**
|
|
1223
|
+
* Emit a telemetry event.
|
|
1224
|
+
*
|
|
1225
|
+
* @param event - Event name
|
|
1226
|
+
* @param data - Event data
|
|
1227
|
+
*/
|
|
1228
|
+
emit(event, data) {
|
|
1229
|
+
tinyCloudDebugLogger.log(event, data);
|
|
1230
|
+
if (this._telemetryEnabled && this._telemetryHandler) {
|
|
1231
|
+
try {
|
|
1232
|
+
this._telemetryHandler(event, projectDiagnosticData(data));
|
|
1233
|
+
} catch (error) {
|
|
1234
|
+
console.error(`Error in telemetry handler for "${event}":`, error);
|
|
1235
|
+
}
|
|
1236
|
+
}
|
|
1237
|
+
const handlers = this._eventHandlers.get(event);
|
|
1238
|
+
if (handlers) {
|
|
1239
|
+
for (const handler of handlers) {
|
|
1240
|
+
try {
|
|
1241
|
+
handler(data);
|
|
1242
|
+
} catch (error) {
|
|
1243
|
+
console.error(`Error in event handler for "${event}":`, error);
|
|
1244
|
+
}
|
|
1245
|
+
}
|
|
1246
|
+
}
|
|
1247
|
+
}
|
|
1248
|
+
/**
|
|
1249
|
+
* Subscribe to telemetry events.
|
|
1250
|
+
*
|
|
1251
|
+
* @param event - Event name to subscribe to
|
|
1252
|
+
* @param handler - Handler function
|
|
1253
|
+
* @returns Unsubscribe function
|
|
1254
|
+
*/
|
|
1255
|
+
on(event, handler) {
|
|
1256
|
+
if (!this._eventHandlers.has(event)) {
|
|
1257
|
+
this._eventHandlers.set(event, /* @__PURE__ */ new Set());
|
|
1258
|
+
}
|
|
1259
|
+
this._eventHandlers.get(event).add(handler);
|
|
1260
|
+
return () => {
|
|
1261
|
+
const handlers = this._eventHandlers.get(event);
|
|
1262
|
+
if (handlers) {
|
|
1263
|
+
handlers.delete(handler);
|
|
1264
|
+
if (handlers.size === 0) {
|
|
1265
|
+
this._eventHandlers.delete(event);
|
|
1266
|
+
}
|
|
1267
|
+
}
|
|
1268
|
+
};
|
|
1269
|
+
}
|
|
1270
|
+
/**
|
|
1271
|
+
* Remove all event handlers for an event.
|
|
1272
|
+
*
|
|
1273
|
+
* @param event - Event name (if omitted, clears all events)
|
|
1274
|
+
*/
|
|
1275
|
+
clearEventHandlers(event) {
|
|
1276
|
+
if (event) {
|
|
1277
|
+
this._eventHandlers.delete(event);
|
|
1278
|
+
} else {
|
|
1279
|
+
this._eventHandlers.clear();
|
|
1280
|
+
}
|
|
1281
|
+
}
|
|
1282
|
+
// ============================================================
|
|
1283
|
+
// Lifecycle
|
|
1284
|
+
// ============================================================
|
|
1285
|
+
/**
|
|
1286
|
+
* Get the abort signal for cancelling operations.
|
|
1287
|
+
*/
|
|
1288
|
+
get abortSignal() {
|
|
1289
|
+
return this._abortController.signal;
|
|
1290
|
+
}
|
|
1291
|
+
get operationAbortSignal() {
|
|
1292
|
+
return this._operationAbortSignal;
|
|
1293
|
+
}
|
|
1294
|
+
setOperationAbortSignal(signal) {
|
|
1295
|
+
this._operationAbortSignal = signal;
|
|
1296
|
+
}
|
|
1297
|
+
/**
|
|
1298
|
+
* Permanently retire this graph after its owner installs a replacement.
|
|
1299
|
+
* Unlike `abort()`, retirement never creates a fresh controller, so captured
|
|
1300
|
+
* services cannot resume calls under a superseded session.
|
|
1301
|
+
*/
|
|
1302
|
+
retire() {
|
|
1303
|
+
if (this._retired) return;
|
|
1304
|
+
this._retired = true;
|
|
1305
|
+
this._session = null;
|
|
1306
|
+
this._abortController.abort();
|
|
1307
|
+
for (const service of this._services.values()) {
|
|
1308
|
+
try {
|
|
1309
|
+
service.onSignOut();
|
|
1310
|
+
} catch (error) {
|
|
1311
|
+
console.error("Error retiring service:", error);
|
|
1312
|
+
}
|
|
1313
|
+
}
|
|
1314
|
+
}
|
|
1315
|
+
/**
|
|
1316
|
+
* Abort all pending operations and notify services.
|
|
1317
|
+
* Creates a new AbortController for future operations.
|
|
1318
|
+
*/
|
|
1319
|
+
abort() {
|
|
1320
|
+
if (this._retired) return;
|
|
1321
|
+
this._abortController.abort();
|
|
1322
|
+
this._abortController = new AbortController();
|
|
1323
|
+
for (const service of this._services.values()) {
|
|
1324
|
+
try {
|
|
1325
|
+
service.onSignOut();
|
|
1326
|
+
} catch (error) {
|
|
1327
|
+
console.error("Error signing out service:", error);
|
|
1328
|
+
}
|
|
1329
|
+
}
|
|
1330
|
+
}
|
|
1331
|
+
/**
|
|
1332
|
+
* Sign out - abort operations and clear session.
|
|
1333
|
+
*/
|
|
1334
|
+
signOut() {
|
|
1335
|
+
if (this._retired) return;
|
|
1336
|
+
this.abort();
|
|
1337
|
+
this.setSession(null);
|
|
1338
|
+
this.emit("session.expired", {});
|
|
1339
|
+
}
|
|
1340
|
+
// ============================================================
|
|
1341
|
+
// Retry Policy
|
|
1342
|
+
// ============================================================
|
|
1343
|
+
/**
|
|
1344
|
+
* Get the retry policy configuration.
|
|
1345
|
+
*/
|
|
1346
|
+
get retryPolicy() {
|
|
1347
|
+
return this._retryPolicy;
|
|
1348
|
+
}
|
|
1349
|
+
wrapInvoke(invoke) {
|
|
1350
|
+
return (session, service, path, action, facts) => {
|
|
1351
|
+
this.assertActive();
|
|
1352
|
+
if (!tinyCloudDebugLogger.isEnabled()) {
|
|
1353
|
+
return invoke(session, service, path, action, facts);
|
|
1354
|
+
}
|
|
1355
|
+
const timer = tinyCloudDebugLogger.startTimer("sdk.invoke", {
|
|
1356
|
+
service,
|
|
1357
|
+
path,
|
|
1358
|
+
action,
|
|
1359
|
+
facts
|
|
1360
|
+
});
|
|
1361
|
+
try {
|
|
1362
|
+
const result = invoke(session, service, path, action, facts);
|
|
1363
|
+
timer.stop({ ok: true, service, path, action });
|
|
1364
|
+
return result;
|
|
1365
|
+
} catch (error) {
|
|
1366
|
+
timer.stop({ ok: false, service, path, action, error });
|
|
1367
|
+
throw error;
|
|
1368
|
+
}
|
|
1369
|
+
};
|
|
1370
|
+
}
|
|
1371
|
+
assertActive() {
|
|
1372
|
+
if (this._retired) {
|
|
1373
|
+
throw new Error("Service graph has been retired: its session was replaced or signed out.");
|
|
1374
|
+
}
|
|
1375
|
+
}
|
|
1376
|
+
wrapInvokeAny(invokeAny) {
|
|
1377
|
+
return (session, entries, facts) => {
|
|
1378
|
+
this.assertActive();
|
|
1379
|
+
if (!tinyCloudDebugLogger.isEnabled()) {
|
|
1380
|
+
return invokeAny(session, entries, facts);
|
|
1381
|
+
}
|
|
1382
|
+
const timer = tinyCloudDebugLogger.startTimer("sdk.invokeAny", {
|
|
1383
|
+
entries,
|
|
1384
|
+
facts
|
|
1385
|
+
});
|
|
1386
|
+
try {
|
|
1387
|
+
const result = invokeAny(session, entries, facts);
|
|
1388
|
+
timer.stop({ ok: true, entries });
|
|
1389
|
+
return result;
|
|
1390
|
+
} catch (error) {
|
|
1391
|
+
timer.stop({ ok: false, entries, error });
|
|
1392
|
+
throw error;
|
|
1393
|
+
}
|
|
1394
|
+
};
|
|
1395
|
+
}
|
|
1396
|
+
/** Retry `NODE_BUSY` / `REQUEST_TIMEOUT`; `build` must re-sign per attempt (the node 409s a repeated invocation). */
|
|
1397
|
+
async invokeWithRetry(url, build) {
|
|
1398
|
+
this.assertActive();
|
|
1399
|
+
const policy = this._retryPolicy;
|
|
1400
|
+
const startedAt = Date.now();
|
|
1401
|
+
let backoffMs = 0;
|
|
1402
|
+
let signal;
|
|
1403
|
+
for (let attempt = 1; ; attempt++) {
|
|
1404
|
+
const init = await build();
|
|
1405
|
+
signal ?? (signal = this.combineSignals(init.signal));
|
|
1406
|
+
const response = await this._fetch(url, init);
|
|
1407
|
+
const done = () => {
|
|
1408
|
+
try {
|
|
1409
|
+
response.retry = { attempts: attempt, backoffMs };
|
|
1410
|
+
} catch {
|
|
1411
|
+
}
|
|
1412
|
+
return response;
|
|
1413
|
+
};
|
|
1414
|
+
if (attempt >= policy.maxAttempts || response.status !== 503 || !response.clone) return done();
|
|
1415
|
+
const text = await response.clone().text().catch(() => "");
|
|
1416
|
+
const error = nodeRetryError("", "", response, text);
|
|
1417
|
+
if (!error || error.meta?.retryable !== true || !policy.retryableErrors.includes(error.code)) {
|
|
1418
|
+
return done();
|
|
1419
|
+
}
|
|
1420
|
+
const remaining = (policy.maxTotalMs ?? Infinity) - (Date.now() - startedAt);
|
|
1421
|
+
if (remaining <= 0) return done();
|
|
1422
|
+
const retryAfterMs = error.meta.retryAfterMs;
|
|
1423
|
+
const step = Math.min(
|
|
1424
|
+
policy.maxDelayMs,
|
|
1425
|
+
policy.backoff === "exponential" ? policy.baseDelayMs * 2 ** (attempt - 1) : policy.backoff === "linear" ? policy.baseDelayMs * attempt : 0
|
|
1426
|
+
);
|
|
1427
|
+
const delay = Math.min(
|
|
1428
|
+
typeof retryAfterMs === "number" ? retryAfterMs : Math.random() * step,
|
|
1429
|
+
remaining
|
|
1430
|
+
);
|
|
1431
|
+
response.body?.cancel?.()?.catch?.(() => {
|
|
1432
|
+
});
|
|
1433
|
+
await abortableSleep(delay, signal);
|
|
1434
|
+
if (signal.aborted) throw signal.reason;
|
|
1435
|
+
backoffMs += delay;
|
|
1419
1436
|
}
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
|
|
1437
|
+
}
|
|
1438
|
+
wrapFetch(fetchFn) {
|
|
1439
|
+
return async (url, init) => {
|
|
1440
|
+
this.assertActive();
|
|
1441
|
+
const request = {
|
|
1442
|
+
...init,
|
|
1443
|
+
signal: this.combineSignals(init?.signal)
|
|
1444
|
+
};
|
|
1445
|
+
if (!tinyCloudDebugLogger.isEnabled()) {
|
|
1446
|
+
return fetchFn(url, request);
|
|
1447
|
+
}
|
|
1448
|
+
const meta = { url, method: request.method ?? "GET" };
|
|
1449
|
+
const timer = tinyCloudDebugLogger.startTimer("sdk.fetch", { ...meta, init: request });
|
|
1450
|
+
try {
|
|
1451
|
+
const response = await fetchFn(url, request);
|
|
1452
|
+
timer.stop({
|
|
1453
|
+
...meta,
|
|
1454
|
+
ok: response.ok,
|
|
1455
|
+
status: response.status,
|
|
1456
|
+
statusText: response.statusText
|
|
1457
|
+
});
|
|
1458
|
+
return response;
|
|
1459
|
+
} catch (error) {
|
|
1460
|
+
timer.stop({ ...meta, ok: false, error });
|
|
1461
|
+
throw error;
|
|
1462
|
+
}
|
|
1425
1463
|
};
|
|
1426
1464
|
}
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
|
|
1431
|
-
|
|
1432
|
-
|
|
1433
|
-
|
|
1434
|
-
|
|
1435
|
-
}
|
|
1465
|
+
combineSignals(signal) {
|
|
1466
|
+
if (!signal) return this._abortController.signal;
|
|
1467
|
+
const combined = new AbortController();
|
|
1468
|
+
const abort = (source) => combined.abort(source.reason);
|
|
1469
|
+
if (signal.aborted) {
|
|
1470
|
+
abort(signal);
|
|
1471
|
+
} else if (this._abortController.signal.aborted) {
|
|
1472
|
+
abort(this._abortController.signal);
|
|
1473
|
+
} else {
|
|
1474
|
+
signal.addEventListener("abort", () => abort(signal), { once: true });
|
|
1475
|
+
this._abortController.signal.addEventListener(
|
|
1476
|
+
"abort",
|
|
1477
|
+
() => abort(this._abortController.signal),
|
|
1478
|
+
{ once: true }
|
|
1479
|
+
);
|
|
1480
|
+
}
|
|
1481
|
+
return combined.signal;
|
|
1482
|
+
}
|
|
1483
|
+
};
|
|
1436
1484
|
|
|
1437
1485
|
// src/base/BaseService.ts
|
|
1438
1486
|
var MAX_TIMER_DELAY_MS = 2147483647;
|
|
@@ -1616,6 +1664,18 @@ var BaseService = class {
|
|
|
1616
1664
|
}
|
|
1617
1665
|
return controller.signal;
|
|
1618
1666
|
}
|
|
1667
|
+
/** POST a signed invocation with retry; `mint` re-signs per attempt, `init.headers` add to the signed ones. */
|
|
1668
|
+
invokeWithRetry(url, mint, { headers: extra, ...init } = {}) {
|
|
1669
|
+
const build = () => {
|
|
1670
|
+
const signed = mint();
|
|
1671
|
+
return {
|
|
1672
|
+
method: "POST",
|
|
1673
|
+
...init,
|
|
1674
|
+
headers: Array.isArray(signed) ? [...signed, ...Object.entries(extra ?? {})] : { ...signed, ...extra }
|
|
1675
|
+
};
|
|
1676
|
+
};
|
|
1677
|
+
return this.context.invokeWithRetry ? this.context.invokeWithRetry(url, build) : this.context.fetch(url, build());
|
|
1678
|
+
}
|
|
1619
1679
|
/**
|
|
1620
1680
|
* Create the abort signal for one request, bounded by the request timeout.
|
|
1621
1681
|
*
|
|
@@ -1983,15 +2043,6 @@ var KVService = class extends BaseService {
|
|
|
1983
2043
|
get host() {
|
|
1984
2044
|
return this.context.hosts[0];
|
|
1985
2045
|
}
|
|
1986
|
-
withJsonContentType(headers) {
|
|
1987
|
-
if (Array.isArray(headers)) {
|
|
1988
|
-
return [...headers, ["content-type", "application/json"]];
|
|
1989
|
-
}
|
|
1990
|
-
return {
|
|
1991
|
-
...headers,
|
|
1992
|
-
"content-type": "application/json"
|
|
1993
|
-
};
|
|
1994
|
-
}
|
|
1995
2046
|
/**
|
|
1996
2047
|
* Execute an invoke operation.
|
|
1997
2048
|
*
|
|
@@ -2002,20 +2053,11 @@ var KVService = class extends BaseService {
|
|
|
2002
2053
|
* @returns Fetch response
|
|
2003
2054
|
*/
|
|
2004
2055
|
async invokeOperation(path, action, body, signal, extraHeaders) {
|
|
2005
|
-
|
|
2006
|
-
|
|
2007
|
-
session,
|
|
2008
|
-
|
|
2009
|
-
path,
|
|
2010
|
-
action
|
|
2056
|
+
return this.invokeWithRetry(
|
|
2057
|
+
`${this.host}/invoke`,
|
|
2058
|
+
() => this.context.invoke(this.context.session, "kv", path, action),
|
|
2059
|
+
{ headers: extraHeaders, body, signal }
|
|
2011
2060
|
);
|
|
2012
|
-
const requestHeaders = Array.isArray(headers) ? [...headers, ...Object.entries(extraHeaders ?? {})] : { ...headers, ...extraHeaders };
|
|
2013
|
-
return this.context.fetch(`${this.host}/invoke`, {
|
|
2014
|
-
method: "POST",
|
|
2015
|
-
headers: requestHeaders,
|
|
2016
|
-
body,
|
|
2017
|
-
signal
|
|
2018
|
-
});
|
|
2019
2061
|
}
|
|
2020
2062
|
/**
|
|
2021
2063
|
* Serialize a single put value into a fetch body.
|
|
@@ -2221,28 +2263,28 @@ var KVService = class extends BaseService {
|
|
|
2221
2263
|
}
|
|
2222
2264
|
const request = this.createRequestSignal(options?.signal, options?.timeout);
|
|
2223
2265
|
try {
|
|
2224
|
-
const session = this.context.session;
|
|
2225
|
-
const invocationHeaders = this.context.invokeAny(
|
|
2226
|
-
session,
|
|
2227
|
-
paths.map((path) => ({
|
|
2228
|
-
spaceId: session.spaceId,
|
|
2229
|
-
service: "kv",
|
|
2230
|
-
path,
|
|
2231
|
-
action
|
|
2232
|
-
}))
|
|
2233
|
-
);
|
|
2234
2266
|
const limitHeaders = {};
|
|
2235
2267
|
if (getOptions?.maxResponseBytes !== void 0) {
|
|
2236
2268
|
limitHeaders["x-tinycloud-max-response-bytes"] = String(
|
|
2237
2269
|
getOptions.maxResponseBytes
|
|
2238
2270
|
);
|
|
2239
2271
|
}
|
|
2240
|
-
const
|
|
2241
|
-
|
|
2242
|
-
|
|
2243
|
-
|
|
2244
|
-
|
|
2245
|
-
|
|
2272
|
+
const response = await this.invokeWithRetry(
|
|
2273
|
+
`${this.host}/invoke`,
|
|
2274
|
+
() => {
|
|
2275
|
+
const session = this.context.session;
|
|
2276
|
+
return this.context.invokeAny(
|
|
2277
|
+
session,
|
|
2278
|
+
paths.map((path) => ({
|
|
2279
|
+
spaceId: session.spaceId,
|
|
2280
|
+
service: "kv",
|
|
2281
|
+
path,
|
|
2282
|
+
action
|
|
2283
|
+
}))
|
|
2284
|
+
);
|
|
2285
|
+
},
|
|
2286
|
+
{ headers: limitHeaders, signal: request.signal }
|
|
2287
|
+
);
|
|
2246
2288
|
if (!response.ok) {
|
|
2247
2289
|
const errorText = response.status === 401 || response.status === 403 ? await this.readAuthorizationText(response) : await response.text();
|
|
2248
2290
|
if (response.status === 401 || response.status === 403) {
|
|
@@ -2288,10 +2330,10 @@ var KVService = class extends BaseService {
|
|
|
2288
2330
|
result: err(serviceError(item.error.code, item.error.message, "kv"))
|
|
2289
2331
|
};
|
|
2290
2332
|
}
|
|
2291
|
-
const
|
|
2333
|
+
const headers = item.headers;
|
|
2292
2334
|
const data = action === KVAction.HEAD ? void 0 : this.parseBatchValue(
|
|
2293
2335
|
item.dataBase64,
|
|
2294
|
-
|
|
2336
|
+
headers,
|
|
2295
2337
|
getOptions?.raw,
|
|
2296
2338
|
getOptions?.binary
|
|
2297
2339
|
);
|
|
@@ -2299,7 +2341,7 @@ var KVService = class extends BaseService {
|
|
|
2299
2341
|
key: keys[index],
|
|
2300
2342
|
result: ok({
|
|
2301
2343
|
data,
|
|
2302
|
-
headers: this.createBatchResponseHeaders(
|
|
2344
|
+
headers: this.createBatchResponseHeaders(headers)
|
|
2303
2345
|
})
|
|
2304
2346
|
};
|
|
2305
2347
|
});
|
|
@@ -2534,7 +2576,7 @@ var KVService = class extends BaseService {
|
|
|
2534
2576
|
));
|
|
2535
2577
|
}
|
|
2536
2578
|
const data = await this.parseResponse(response, options?.raw, options?.binary);
|
|
2537
|
-
return ok({ data, headers: this.createResponseHeaders(response.headers) });
|
|
2579
|
+
return ok({ data, headers: this.createResponseHeaders(response.headers), ...response.retry && { retry: response.retry } });
|
|
2538
2580
|
}
|
|
2539
2581
|
async batchGet(keys, options) {
|
|
2540
2582
|
return this.withTelemetry(
|
|
@@ -2624,7 +2666,8 @@ var KVService = class extends BaseService {
|
|
|
2624
2666
|
}
|
|
2625
2667
|
return ok({
|
|
2626
2668
|
data: void 0,
|
|
2627
|
-
headers: this.createResponseHeaders(response.headers)
|
|
2669
|
+
headers: this.createResponseHeaders(response.headers),
|
|
2670
|
+
...response.retry && { retry: response.retry }
|
|
2628
2671
|
});
|
|
2629
2672
|
}
|
|
2630
2673
|
/**
|
|
@@ -2697,25 +2740,23 @@ var KVService = class extends BaseService {
|
|
|
2697
2740
|
entries?.[index]?.body ?? this.serializeBatchPutValue(items[index])
|
|
2698
2741
|
);
|
|
2699
2742
|
}
|
|
2700
|
-
const
|
|
2701
|
-
|
|
2702
|
-
|
|
2703
|
-
|
|
2704
|
-
|
|
2705
|
-
|
|
2706
|
-
|
|
2707
|
-
|
|
2743
|
+
const response = await this.invokeWithRetry(
|
|
2744
|
+
`${this.host}/invoke`,
|
|
2745
|
+
() => {
|
|
2746
|
+
const headers = this.context.invokeAny(
|
|
2747
|
+
session,
|
|
2748
|
+
paths.map((path) => ({
|
|
2749
|
+
spaceId: session.spaceId,
|
|
2750
|
+
service: "kv",
|
|
2751
|
+
path,
|
|
2752
|
+
action: KVAction.PUT
|
|
2753
|
+
}))
|
|
2754
|
+
);
|
|
2755
|
+
requestMayHaveDispatched = true;
|
|
2756
|
+
return headers;
|
|
2757
|
+
},
|
|
2758
|
+
{ body, signal }
|
|
2708
2759
|
);
|
|
2709
|
-
const url = `${this.host}/invoke`;
|
|
2710
|
-
const init = {
|
|
2711
|
-
method: "POST",
|
|
2712
|
-
headers,
|
|
2713
|
-
body,
|
|
2714
|
-
signal
|
|
2715
|
-
};
|
|
2716
|
-
const fetchFn = this.context.fetch;
|
|
2717
|
-
requestMayHaveDispatched = true;
|
|
2718
|
-
const response = await fetchFn(url, init);
|
|
2719
2760
|
if (!response.ok) {
|
|
2720
2761
|
let errorText;
|
|
2721
2762
|
try {
|
|
@@ -3185,7 +3226,8 @@ var KVService = class extends BaseService {
|
|
|
3185
3226
|
}
|
|
3186
3227
|
return ok({
|
|
3187
3228
|
data: void 0,
|
|
3188
|
-
headers: this.createResponseHeaders(response.headers)
|
|
3229
|
+
headers: this.createResponseHeaders(response.headers),
|
|
3230
|
+
...response.retry && { retry: response.retry }
|
|
3189
3231
|
});
|
|
3190
3232
|
}
|
|
3191
3233
|
/**
|
|
@@ -3259,12 +3301,6 @@ var KVService = class extends BaseService {
|
|
|
3259
3301
|
}
|
|
3260
3302
|
const path = this.getFullPath(key, options?.prefix);
|
|
3261
3303
|
const session = this.context.session;
|
|
3262
|
-
const headers = this.context.invoke(
|
|
3263
|
-
session,
|
|
3264
|
-
"kv",
|
|
3265
|
-
path,
|
|
3266
|
-
KVAction.GET
|
|
3267
|
-
);
|
|
3268
3304
|
const body = {
|
|
3269
3305
|
space: session.spaceId,
|
|
3270
3306
|
path,
|
|
@@ -3278,12 +3314,15 @@ var KVService = class extends BaseService {
|
|
|
3278
3314
|
}
|
|
3279
3315
|
const request = this.createRequestSignal(options?.signal, options?.timeout);
|
|
3280
3316
|
try {
|
|
3281
|
-
const response = await this.
|
|
3282
|
-
|
|
3283
|
-
|
|
3284
|
-
|
|
3285
|
-
|
|
3286
|
-
|
|
3317
|
+
const response = await this.invokeWithRetry(
|
|
3318
|
+
`${this.host}/signed/kv`,
|
|
3319
|
+
() => this.context.invoke(session, "kv", path, KVAction.GET),
|
|
3320
|
+
{
|
|
3321
|
+
headers: { "content-type": "application/json" },
|
|
3322
|
+
body: JSON.stringify(body),
|
|
3323
|
+
signal: request.signal
|
|
3324
|
+
}
|
|
3325
|
+
);
|
|
3287
3326
|
if (!response.ok) {
|
|
3288
3327
|
return await this.createSignedReadUrlError(response, key, path);
|
|
3289
3328
|
}
|
|
@@ -3780,18 +3819,19 @@ var SQLService = class extends BaseService {
|
|
|
3780
3819
|
}
|
|
3781
3820
|
// === Private helpers ===
|
|
3782
3821
|
async invokeSQL(dbName, actions, body, signal) {
|
|
3783
|
-
const session = this.context.session;
|
|
3784
3822
|
const actionList = Array.isArray(actions) ? actions : [actions];
|
|
3785
|
-
|
|
3786
|
-
|
|
3787
|
-
|
|
3788
|
-
|
|
3789
|
-
|
|
3790
|
-
"Content-Type": "application/json"
|
|
3823
|
+
return this.invokeWithRetry(
|
|
3824
|
+
`${this.host}/invoke`,
|
|
3825
|
+
() => {
|
|
3826
|
+
const session = this.context.session;
|
|
3827
|
+
return actionList.length === 1 ? this.context.invoke(session, "sql", dbName, actionList[0]) : this.invokeSQLAny(session, dbName, actionList);
|
|
3791
3828
|
},
|
|
3792
|
-
|
|
3793
|
-
|
|
3794
|
-
|
|
3829
|
+
{
|
|
3830
|
+
headers: { "Content-Type": "application/json" },
|
|
3831
|
+
body: JSON.stringify(body),
|
|
3832
|
+
signal
|
|
3833
|
+
}
|
|
3834
|
+
);
|
|
3795
3835
|
}
|
|
3796
3836
|
actionForSql(sql, fallback) {
|
|
3797
3837
|
const token = firstSqlToken(sql);
|
|
@@ -4252,22 +4292,20 @@ var DuckDbService = class extends BaseService {
|
|
|
4252
4292
|
}
|
|
4253
4293
|
const request = this.createRequestSignal(options?.signal);
|
|
4254
4294
|
try {
|
|
4255
|
-
const
|
|
4256
|
-
|
|
4257
|
-
|
|
4258
|
-
|
|
4259
|
-
|
|
4260
|
-
|
|
4295
|
+
const response = await this.invokeWithRetry(
|
|
4296
|
+
`${this.host}/invoke`,
|
|
4297
|
+
() => this.context.invoke(
|
|
4298
|
+
this.context.session,
|
|
4299
|
+
"duckdb",
|
|
4300
|
+
dbName,
|
|
4301
|
+
DuckDbAction.IMPORT
|
|
4302
|
+
),
|
|
4303
|
+
{
|
|
4304
|
+
headers: { "Content-Type": "application/x-duckdb" },
|
|
4305
|
+
body: new Blob([data]),
|
|
4306
|
+
signal: request.signal
|
|
4307
|
+
}
|
|
4261
4308
|
);
|
|
4262
|
-
const response = await this.context.fetch(`${this.host}/invoke`, {
|
|
4263
|
-
method: "POST",
|
|
4264
|
-
headers: {
|
|
4265
|
-
...headers,
|
|
4266
|
-
"Content-Type": "application/x-duckdb"
|
|
4267
|
-
},
|
|
4268
|
-
body: new Blob([data]),
|
|
4269
|
-
signal: request.signal
|
|
4270
|
-
});
|
|
4271
4309
|
if (!response.ok) {
|
|
4272
4310
|
return await this.handleErrorResponse(response, "import");
|
|
4273
4311
|
}
|
|
@@ -4281,18 +4319,15 @@ var DuckDbService = class extends BaseService {
|
|
|
4281
4319
|
}
|
|
4282
4320
|
// === Private helpers ===
|
|
4283
4321
|
async invokeDuckDb(dbName, action, body, signal, extraHeaders) {
|
|
4284
|
-
|
|
4285
|
-
|
|
4286
|
-
|
|
4287
|
-
|
|
4288
|
-
|
|
4289
|
-
|
|
4290
|
-
|
|
4291
|
-
|
|
4292
|
-
|
|
4293
|
-
body: JSON.stringify(body),
|
|
4294
|
-
signal
|
|
4295
|
-
});
|
|
4322
|
+
return this.invokeWithRetry(
|
|
4323
|
+
`${this.host}/invoke`,
|
|
4324
|
+
() => this.context.invoke(this.context.session, "duckdb", dbName, action),
|
|
4325
|
+
{
|
|
4326
|
+
headers: { "Content-Type": "application/json", ...extraHeaders },
|
|
4327
|
+
body: JSON.stringify(body),
|
|
4328
|
+
signal
|
|
4329
|
+
}
|
|
4330
|
+
);
|
|
4296
4331
|
}
|
|
4297
4332
|
async handleErrorResponse(response, operation) {
|
|
4298
4333
|
const errorText = await response.text();
|
|
@@ -4481,26 +4516,24 @@ var HooksService = class extends BaseService {
|
|
|
4481
4516
|
);
|
|
4482
4517
|
}
|
|
4483
4518
|
try {
|
|
4484
|
-
const response = await this.
|
|
4485
|
-
|
|
4486
|
-
|
|
4487
|
-
|
|
4488
|
-
|
|
4489
|
-
|
|
4490
|
-
|
|
4491
|
-
|
|
4492
|
-
|
|
4493
|
-
|
|
4494
|
-
|
|
4495
|
-
|
|
4496
|
-
|
|
4497
|
-
|
|
4498
|
-
|
|
4499
|
-
|
|
4500
|
-
|
|
4501
|
-
|
|
4502
|
-
})
|
|
4503
|
-
});
|
|
4519
|
+
const response = await this.invokeWithRetry(
|
|
4520
|
+
`${this.host}/hooks/webhooks`,
|
|
4521
|
+
() => this.createHookHeaders(
|
|
4522
|
+
"tinycloud.hooks/register",
|
|
4523
|
+
buildScopePath(webhook.service, webhook.pathPrefix)
|
|
4524
|
+
),
|
|
4525
|
+
{
|
|
4526
|
+
headers: { "content-type": "application/json" },
|
|
4527
|
+
body: JSON.stringify({
|
|
4528
|
+
space: webhook.space,
|
|
4529
|
+
service: webhook.service,
|
|
4530
|
+
pathPrefix: normalizePathPrefix(webhook.pathPrefix),
|
|
4531
|
+
abilities: webhook.abilities ?? [],
|
|
4532
|
+
callbackUrl: webhook.callbackUrl,
|
|
4533
|
+
secret: webhook.secret
|
|
4534
|
+
})
|
|
4535
|
+
}
|
|
4536
|
+
);
|
|
4504
4537
|
if (!response.ok) {
|
|
4505
4538
|
return err(
|
|
4506
4539
|
await responseError("hooks", "failed to register webhook", response)
|
|
@@ -4538,17 +4571,13 @@ var HooksService = class extends BaseService {
|
|
|
4538
4571
|
query.set("prefix", normalizedPrefix);
|
|
4539
4572
|
}
|
|
4540
4573
|
}
|
|
4541
|
-
const response = await this.
|
|
4574
|
+
const response = await this.invokeWithRetry(
|
|
4542
4575
|
`${this.host}/hooks/webhooks${query.size > 0 ? `?${query.toString()}` : ""}`,
|
|
4543
|
-
|
|
4544
|
-
|
|
4545
|
-
|
|
4546
|
-
|
|
4547
|
-
|
|
4548
|
-
options.service ? buildScopePath(options.service, options.pathPrefix) : "webhooks"
|
|
4549
|
-
)
|
|
4550
|
-
)
|
|
4551
|
-
}
|
|
4576
|
+
() => this.createHookHeaders(
|
|
4577
|
+
"tinycloud.hooks/list",
|
|
4578
|
+
options.service ? buildScopePath(options.service, options.pathPrefix) : "webhooks"
|
|
4579
|
+
),
|
|
4580
|
+
{ method: "GET" }
|
|
4552
4581
|
);
|
|
4553
4582
|
if (!response.ok) {
|
|
4554
4583
|
return err(
|
|
@@ -4575,20 +4604,16 @@ var HooksService = class extends BaseService {
|
|
|
4575
4604
|
return err(authRequiredError("hooks"));
|
|
4576
4605
|
}
|
|
4577
4606
|
try {
|
|
4578
|
-
const response = await this.
|
|
4607
|
+
const response = await this.invokeWithRetry(
|
|
4579
4608
|
`${this.host}/hooks/webhooks/${encodeURIComponent(id)}`,
|
|
4580
|
-
|
|
4581
|
-
|
|
4582
|
-
|
|
4583
|
-
|
|
4584
|
-
|
|
4585
|
-
|
|
4586
|
-
|
|
4587
|
-
|
|
4588
|
-
) : `webhooks/${id}`
|
|
4589
|
-
)
|
|
4590
|
-
)
|
|
4591
|
-
}
|
|
4609
|
+
() => this.createHookHeaders(
|
|
4610
|
+
"tinycloud.hooks/unregister",
|
|
4611
|
+
options.target ? buildScopePath(
|
|
4612
|
+
options.target.service,
|
|
4613
|
+
options.target.pathPrefix
|
|
4614
|
+
) : `webhooks/${id}`
|
|
4615
|
+
),
|
|
4616
|
+
{ method: "DELETE" }
|
|
4592
4617
|
);
|
|
4593
4618
|
if (!response.ok) {
|
|
4594
4619
|
return err(
|
|
@@ -5044,23 +5069,7 @@ function readProperty(value, key) {
|
|
|
5044
5069
|
return void 0;
|
|
5045
5070
|
}
|
|
5046
5071
|
}
|
|
5047
|
-
|
|
5048
|
-
return new Promise((resolve) => {
|
|
5049
|
-
const finish = () => {
|
|
5050
|
-
clearTimeout(timer);
|
|
5051
|
-
signal.removeEventListener("abort", stop);
|
|
5052
|
-
resolve();
|
|
5053
|
-
};
|
|
5054
|
-
const timer = setTimeout(finish, delayMs);
|
|
5055
|
-
timer.unref?.();
|
|
5056
|
-
const stop = () => finish();
|
|
5057
|
-
if (signal.aborted) {
|
|
5058
|
-
finish();
|
|
5059
|
-
} else {
|
|
5060
|
-
signal.addEventListener("abort", stop, { once: true });
|
|
5061
|
-
}
|
|
5062
|
-
});
|
|
5063
|
-
}
|
|
5072
|
+
var sleep = (delayMs, _attempt, signal) => abortableSleep(delayMs, signal, true);
|
|
5064
5073
|
function abortPromise(signal) {
|
|
5065
5074
|
let resolve;
|
|
5066
5075
|
const promise = new Promise((done) => {
|
|
@@ -5445,7 +5454,7 @@ var StorageFullMonitor = class {
|
|
|
5445
5454
|
var UNKNOWN_OWNER = "\0unknown";
|
|
5446
5455
|
|
|
5447
5456
|
// src/storage/status.ts
|
|
5448
|
-
var STORAGE_MANAGE_URL = "https://account.tinycloud.xyz/
|
|
5457
|
+
var STORAGE_MANAGE_URL = "https://account.tinycloud.xyz/storage";
|
|
5449
5458
|
var STORAGE_NEARLY_FULL_RATIO = 0.9;
|
|
5450
5459
|
function storageUsageState(usedBytes, limitBytes) {
|
|
5451
5460
|
if (limitBytes === null) return "ok";
|
|
@@ -5477,40 +5486,150 @@ function parseStorageStatus(body) {
|
|
|
5477
5486
|
if (status.account.plan) status.plan = status.account.plan;
|
|
5478
5487
|
status.state = storageUsageState(account.usedBytes, account.limitBytes);
|
|
5479
5488
|
}
|
|
5480
|
-
return status;
|
|
5489
|
+
return status;
|
|
5490
|
+
}
|
|
5491
|
+
|
|
5492
|
+
// src/quota/TinyCloudQuota.ts
|
|
5493
|
+
var TinyCloudQuota = class {
|
|
5494
|
+
constructor(config = {}) {
|
|
5495
|
+
this.quotaUrl = null;
|
|
5496
|
+
this.config = config;
|
|
5497
|
+
}
|
|
5498
|
+
/** Set the quota URL discovered from the /info endpoint */
|
|
5499
|
+
setQuotaUrl(url) {
|
|
5500
|
+
this.quotaUrl = url;
|
|
5501
|
+
}
|
|
5502
|
+
/** Whether a quota service is available */
|
|
5503
|
+
get available() {
|
|
5504
|
+
return this.quotaUrl !== null;
|
|
5505
|
+
}
|
|
5506
|
+
/** Query quota status for a space from the quota URL */
|
|
5507
|
+
async getQuota(spaceId) {
|
|
5508
|
+
if (!this.quotaUrl) return null;
|
|
5509
|
+
const resp = await fetch(
|
|
5510
|
+
`${this.quotaUrl}/api/quota/${encodeURIComponent(spaceId)}`
|
|
5511
|
+
);
|
|
5512
|
+
if (!resp.ok) return null;
|
|
5513
|
+
const data = await resp.json();
|
|
5514
|
+
return {
|
|
5515
|
+
limitBytes: data.storage_limit_bytes ?? 0
|
|
5516
|
+
};
|
|
5517
|
+
}
|
|
5518
|
+
/** Trigger the upgrade callback when a quota error is encountered */
|
|
5519
|
+
handleQuotaError(info) {
|
|
5520
|
+
this.config.onUpgradeRequired?.(info);
|
|
5521
|
+
}
|
|
5522
|
+
};
|
|
5523
|
+
|
|
5524
|
+
// src/encryption/canonical.ts
|
|
5525
|
+
function canonicalize(value) {
|
|
5526
|
+
if (value === void 0) {
|
|
5527
|
+
return "";
|
|
5528
|
+
}
|
|
5529
|
+
return stringify(value);
|
|
5530
|
+
}
|
|
5531
|
+
function stringify(value) {
|
|
5532
|
+
if (value === null) return "null";
|
|
5533
|
+
switch (typeof value) {
|
|
5534
|
+
case "boolean":
|
|
5535
|
+
case "number":
|
|
5536
|
+
return JSON.stringify(value);
|
|
5537
|
+
case "string":
|
|
5538
|
+
return JSON.stringify(value);
|
|
5539
|
+
case "object": {
|
|
5540
|
+
if (Array.isArray(value)) {
|
|
5541
|
+
return `[${value.map(stringify).join(",")}]`;
|
|
5542
|
+
}
|
|
5543
|
+
const keys = Object.keys(value).sort();
|
|
5544
|
+
const parts = [];
|
|
5545
|
+
for (const k of keys) {
|
|
5546
|
+
const v = value[k];
|
|
5547
|
+
if (v === void 0) continue;
|
|
5548
|
+
parts.push(`${JSON.stringify(k)}:${stringify(v)}`);
|
|
5549
|
+
}
|
|
5550
|
+
return `{${parts.join(",")}}`;
|
|
5551
|
+
}
|
|
5552
|
+
default:
|
|
5553
|
+
throw new TypeError(
|
|
5554
|
+
`canonicalize: unsupported value type ${typeof value}`
|
|
5555
|
+
);
|
|
5556
|
+
}
|
|
5557
|
+
}
|
|
5558
|
+
var HEX = "0123456789abcdef";
|
|
5559
|
+
function hexEncode(bytes) {
|
|
5560
|
+
let out = "";
|
|
5561
|
+
for (let i = 0; i < bytes.length; i++) {
|
|
5562
|
+
const b = bytes[i];
|
|
5563
|
+
out += HEX[b >> 4 & 15] + HEX[b & 15];
|
|
5564
|
+
}
|
|
5565
|
+
return out;
|
|
5481
5566
|
}
|
|
5482
|
-
|
|
5483
|
-
|
|
5484
|
-
|
|
5485
|
-
constructor(config = {}) {
|
|
5486
|
-
this.quotaUrl = null;
|
|
5487
|
-
this.config = config;
|
|
5567
|
+
function hexDecode(hex) {
|
|
5568
|
+
if (hex.length % 2 !== 0) {
|
|
5569
|
+
throw new Error("hex string must have even length");
|
|
5488
5570
|
}
|
|
5489
|
-
|
|
5490
|
-
|
|
5491
|
-
|
|
5571
|
+
const out = new Uint8Array(hex.length / 2);
|
|
5572
|
+
for (let i = 0; i < out.length; i++) {
|
|
5573
|
+
const hi = parseInt(hex[i * 2], 16);
|
|
5574
|
+
const lo = parseInt(hex[i * 2 + 1], 16);
|
|
5575
|
+
if (Number.isNaN(hi) || Number.isNaN(lo)) {
|
|
5576
|
+
throw new Error("invalid hex character");
|
|
5577
|
+
}
|
|
5578
|
+
out[i] = hi << 4 | lo;
|
|
5492
5579
|
}
|
|
5493
|
-
|
|
5494
|
-
|
|
5495
|
-
|
|
5580
|
+
return out;
|
|
5581
|
+
}
|
|
5582
|
+
function base64Encode(bytes) {
|
|
5583
|
+
const chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
|
|
5584
|
+
let out = "";
|
|
5585
|
+
for (let i = 0; i < bytes.length; i += 3) {
|
|
5586
|
+
const b0 = bytes[i];
|
|
5587
|
+
const b1 = i + 1 < bytes.length ? bytes[i + 1] : 0;
|
|
5588
|
+
const b2 = i + 2 < bytes.length ? bytes[i + 2] : 0;
|
|
5589
|
+
out += chars[b0 >> 2 & 63];
|
|
5590
|
+
out += chars[(b0 << 4 | b1 >> 4) & 63];
|
|
5591
|
+
out += i + 1 < bytes.length ? chars[(b1 << 2 | b2 >> 6) & 63] : "=";
|
|
5592
|
+
out += i + 2 < bytes.length ? chars[b2 & 63] : "=";
|
|
5496
5593
|
}
|
|
5497
|
-
|
|
5498
|
-
|
|
5499
|
-
|
|
5500
|
-
|
|
5501
|
-
|
|
5502
|
-
|
|
5503
|
-
|
|
5504
|
-
const data = await resp.json();
|
|
5505
|
-
return {
|
|
5506
|
-
limitBytes: data.storage_limit_bytes ?? 0
|
|
5507
|
-
};
|
|
5594
|
+
return out;
|
|
5595
|
+
}
|
|
5596
|
+
function base64Decode(s) {
|
|
5597
|
+
const clean = s.replace(/[^A-Za-z0-9+/=]/g, "");
|
|
5598
|
+
const len = clean.length;
|
|
5599
|
+
if (len % 4 !== 0) {
|
|
5600
|
+
throw new Error("invalid base64 input");
|
|
5508
5601
|
}
|
|
5509
|
-
|
|
5510
|
-
|
|
5511
|
-
|
|
5602
|
+
const padding = clean.endsWith("==") ? 2 : clean.endsWith("=") ? 1 : 0;
|
|
5603
|
+
const outLen = len / 4 * 3 - padding;
|
|
5604
|
+
const out = new Uint8Array(outLen);
|
|
5605
|
+
const lookup = {};
|
|
5606
|
+
const chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
|
|
5607
|
+
for (let i = 0; i < chars.length; i++) lookup[chars[i]] = i;
|
|
5608
|
+
let outIdx = 0;
|
|
5609
|
+
for (let i = 0; i < len; i += 4) {
|
|
5610
|
+
const v0 = lookup[clean[i]] ?? 0;
|
|
5611
|
+
const v1 = lookup[clean[i + 1]] ?? 0;
|
|
5612
|
+
const v2 = clean[i + 2] === "=" ? 0 : lookup[clean[i + 2]] ?? 0;
|
|
5613
|
+
const v3 = clean[i + 3] === "=" ? 0 : lookup[clean[i + 3]] ?? 0;
|
|
5614
|
+
const b0 = v0 << 2 | v1 >> 4;
|
|
5615
|
+
const b1 = (v1 & 15) << 4 | v2 >> 2;
|
|
5616
|
+
const b2 = (v2 & 3) << 6 | v3;
|
|
5617
|
+
if (outIdx < outLen) out[outIdx++] = b0;
|
|
5618
|
+
if (outIdx < outLen) out[outIdx++] = b1;
|
|
5619
|
+
if (outIdx < outLen) out[outIdx++] = b2;
|
|
5512
5620
|
}
|
|
5513
|
-
|
|
5621
|
+
return out;
|
|
5622
|
+
}
|
|
5623
|
+
function utf8Encode(s) {
|
|
5624
|
+
return new TextEncoder().encode(s);
|
|
5625
|
+
}
|
|
5626
|
+
function utf8Decode(b) {
|
|
5627
|
+
return new TextDecoder().decode(b);
|
|
5628
|
+
}
|
|
5629
|
+
function canonicalHashHex(sha256, value) {
|
|
5630
|
+
const canonical = canonicalize(value);
|
|
5631
|
+
return hexEncode(sha256(utf8Encode(canonical)));
|
|
5632
|
+
}
|
|
5514
5633
|
|
|
5515
5634
|
// src/vault/types.ts
|
|
5516
5635
|
var VaultPublicSpaceKVActions = [
|
|
@@ -5681,7 +5800,7 @@ function toBytes(str) {
|
|
|
5681
5800
|
function fromBytes(bytes) {
|
|
5682
5801
|
return new TextDecoder().decode(bytes);
|
|
5683
5802
|
}
|
|
5684
|
-
function
|
|
5803
|
+
function hexEncode2(bytes) {
|
|
5685
5804
|
return Array.from(bytes).map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
5686
5805
|
}
|
|
5687
5806
|
function concatBytes(...arrays) {
|
|
@@ -5694,14 +5813,14 @@ function concatBytes(...arrays) {
|
|
|
5694
5813
|
}
|
|
5695
5814
|
return result;
|
|
5696
5815
|
}
|
|
5697
|
-
function
|
|
5816
|
+
function base64Encode2(bytes) {
|
|
5698
5817
|
let binary = "";
|
|
5699
5818
|
for (let i = 0; i < bytes.length; i++) {
|
|
5700
5819
|
binary += String.fromCharCode(bytes[i]);
|
|
5701
5820
|
}
|
|
5702
5821
|
return btoa(binary);
|
|
5703
5822
|
}
|
|
5704
|
-
function
|
|
5823
|
+
function base64Decode2(str) {
|
|
5705
5824
|
const binary = atob(str);
|
|
5706
5825
|
const bytes = new Uint8Array(binary.length);
|
|
5707
5826
|
for (let i = 0; i < binary.length; i++) {
|
|
@@ -5743,6 +5862,11 @@ function defaultVaultMessage(input) {
|
|
|
5743
5862
|
return input.message;
|
|
5744
5863
|
case "PERMISSION_DENIED":
|
|
5745
5864
|
return input.message;
|
|
5865
|
+
case "AAD_MISMATCH":
|
|
5866
|
+
case "ETAG_MISMATCH":
|
|
5867
|
+
case "INVALID_ETAG":
|
|
5868
|
+
case "OUTCOME_UNKNOWN":
|
|
5869
|
+
case "NODE_BUSY":
|
|
5746
5870
|
case "STORAGE_QUOTA_EXCEEDED":
|
|
5747
5871
|
case "STORAGE_LIMIT_REACHED":
|
|
5748
5872
|
return input.message;
|
|
@@ -5765,6 +5889,37 @@ function typedKvCause(error) {
|
|
|
5765
5889
|
...error.cause !== void 0 ? { cause: error.cause } : {}
|
|
5766
5890
|
});
|
|
5767
5891
|
}
|
|
5892
|
+
function canonicalAadSpaceId(spaceId) {
|
|
5893
|
+
return spaceId.replace(/0x[0-9a-fA-F]{40}/g, (address) => address.toLowerCase());
|
|
5894
|
+
}
|
|
5895
|
+
function checkEnvelopeAad(value, spaceId, key) {
|
|
5896
|
+
const e = value;
|
|
5897
|
+
if (typeof e !== "object" || e === null || Array.isArray(e) || !["networkId", "alg", "encryptedSymmetricKey", "encryptedSymmetricKeyHash", "ciphertext"].every(
|
|
5898
|
+
(field) => typeof e[field] === "string" && e[field] !== ""
|
|
5899
|
+
) || !Number.isInteger(e.keyVersion) || e.aad !== void 0 && typeof e.aad !== "string") {
|
|
5900
|
+
return "corrupt";
|
|
5901
|
+
}
|
|
5902
|
+
let aad = "";
|
|
5903
|
+
try {
|
|
5904
|
+
if (typeof e.aad === "string") aad = fromBytes(base64Decode(e.aad));
|
|
5905
|
+
} catch {
|
|
5906
|
+
}
|
|
5907
|
+
const suffix = `:${key}`;
|
|
5908
|
+
return aad.endsWith(suffix) && canonicalAadSpaceId(aad.slice(0, -suffix.length)) === canonicalAadSpaceId(`tinycloud.vault:${spaceId}`) ? "ok" : "mismatch";
|
|
5909
|
+
}
|
|
5910
|
+
var AMBIGUOUS_WRITE_STATUSES = [500, 502, 503, 504, 520, 522, 524];
|
|
5911
|
+
function ifMatchWriteError(key, error) {
|
|
5912
|
+
const status = error.meta?.status;
|
|
5913
|
+
const code = error.code === ErrorCodes.KV_PRECONDITION_FAILED ? "ETAG_MISMATCH" : error.code === ErrorCodes.KV_WRITE_FAILED && status === 400 ? "INVALID_ETAG" : error.code === ErrorCodes.OUTCOME_UNKNOWN || error.code === ErrorCodes.KV_WRITE_FAILED && typeof status === "number" && AMBIGUOUS_WRITE_STATUSES.includes(status) || (error.code === ErrorCodes.NETWORK_ERROR || error.code === ErrorCodes.TIMEOUT) && status === void 0 && error.meta?.requestMayHaveDispatched !== false ? "OUTCOME_UNKNOWN" : error.code === ErrorCodes.NODE_BUSY || error.code === ErrorCodes.REQUEST_TIMEOUT ? "NODE_BUSY" : void 0;
|
|
5914
|
+
if (code === void 0) return void 0;
|
|
5915
|
+
return vaultError({
|
|
5916
|
+
code,
|
|
5917
|
+
key,
|
|
5918
|
+
message: error.message,
|
|
5919
|
+
...error.meta !== void 0 ? { meta: error.meta } : {},
|
|
5920
|
+
cause: typedKvCause(error)
|
|
5921
|
+
});
|
|
5922
|
+
}
|
|
5768
5923
|
function kvWriteError(context, error) {
|
|
5769
5924
|
if (error.code === ErrorCodes.STORAGE_QUOTA_EXCEEDED || error.code === ErrorCodes.STORAGE_LIMIT_REACHED) {
|
|
5770
5925
|
return vaultError({ code: error.code, message: error.message, meta: error.meta });
|
|
@@ -5951,7 +6106,7 @@ var DataVaultService = class extends BaseService {
|
|
|
5951
6106
|
}
|
|
5952
6107
|
if (existingPubKey) {
|
|
5953
6108
|
this.encryptionIdentity = {
|
|
5954
|
-
publicKey:
|
|
6109
|
+
publicKey: base64Decode2(existingPubKey),
|
|
5955
6110
|
privateKey: new Uint8Array(0)
|
|
5956
6111
|
// private key not available without signing
|
|
5957
6112
|
};
|
|
@@ -5976,7 +6131,7 @@ var DataVaultService = class extends BaseService {
|
|
|
5976
6131
|
);
|
|
5977
6132
|
this.encryptionIdentity = this.crypto.x25519FromSeed(seed);
|
|
5978
6133
|
try {
|
|
5979
|
-
const pubKeyB64 =
|
|
6134
|
+
const pubKeyB64 = base64Encode2(this.encryptionIdentity.publicKey);
|
|
5980
6135
|
await this.tc.ensurePublicSpace();
|
|
5981
6136
|
await this.tc.publicKV.put(".well-known/vault-pubkey", pubKeyB64);
|
|
5982
6137
|
await this.tc.publicKV.put(".well-known/vault-version", CURRENT_VAULT_VERSION);
|
|
@@ -6042,6 +6197,21 @@ var DataVaultService = class extends BaseService {
|
|
|
6042
6197
|
message: "Authentication required"
|
|
6043
6198
|
});
|
|
6044
6199
|
}
|
|
6200
|
+
if (options?.ifMatch !== void 0) {
|
|
6201
|
+
if (options.ifNoneMatch !== void 0) {
|
|
6202
|
+
return vaultError({
|
|
6203
|
+
code: "STORAGE_ERROR",
|
|
6204
|
+
cause: new Error("ifMatch cannot be combined with ifNoneMatch")
|
|
6205
|
+
});
|
|
6206
|
+
}
|
|
6207
|
+
if (!/^"[^"\s]+"$/.test(options.ifMatch)) {
|
|
6208
|
+
return vaultError({
|
|
6209
|
+
code: "INVALID_ETAG",
|
|
6210
|
+
key,
|
|
6211
|
+
message: options.ifMatch.startsWith("W/") ? 'ifMatch is a weak etag (W/"..."): the node only issues strong etags, so the etag was weakened in transit (a proxy or CDN rewrote it). Read it from a connection that preserves strong ETags' : "ifMatch must be the strong, quoted ETag returned by a network read (VaultEntry.etag), passed exactly as returned"
|
|
6212
|
+
});
|
|
6213
|
+
}
|
|
6214
|
+
}
|
|
6045
6215
|
try {
|
|
6046
6216
|
const { plaintext, contentType } = this.serializeValue(value, options);
|
|
6047
6217
|
const metadata = {
|
|
@@ -6064,7 +6234,8 @@ var DataVaultService = class extends BaseService {
|
|
|
6064
6234
|
}
|
|
6065
6235
|
const valuePath = `vault/${key}`;
|
|
6066
6236
|
const valueBody = JSON.stringify(envelopeResult.data);
|
|
6067
|
-
const
|
|
6237
|
+
const putOptions = options?.ifMatch !== void 0 ? { ifMatch: options.ifMatch } : options?.ifNoneMatch !== void 0 ? { ifNoneMatch: options.ifNoneMatch } : void 0;
|
|
6238
|
+
const valuePutResult = putOptions !== void 0 ? await this.tc.kv.put(valuePath, valueBody, putOptions) : await this.tc.kv.put(valuePath, valueBody);
|
|
6068
6239
|
if (!valuePutResult.ok) {
|
|
6069
6240
|
if (options?.ifNoneMatch !== void 0 && valuePutResult.error.code === ErrorCodes.KV_PRECONDITION_FAILED) {
|
|
6070
6241
|
return vaultError({
|
|
@@ -6074,6 +6245,10 @@ var DataVaultService = class extends BaseService {
|
|
|
6074
6245
|
cause: typedKvCause(valuePutResult.error)
|
|
6075
6246
|
});
|
|
6076
6247
|
}
|
|
6248
|
+
if (options?.ifMatch !== void 0) {
|
|
6249
|
+
const classified = ifMatchWriteError(key, valuePutResult.error);
|
|
6250
|
+
if (classified !== void 0) return classified;
|
|
6251
|
+
}
|
|
6077
6252
|
return kvWriteError("Failed to store encrypted value", valuePutResult.error);
|
|
6078
6253
|
}
|
|
6079
6254
|
return { ok: true, data: void 0 };
|
|
@@ -6108,6 +6283,19 @@ var DataVaultService = class extends BaseService {
|
|
|
6108
6283
|
}
|
|
6109
6284
|
const rawEnvelope = unwrapKVData(valueResult.data);
|
|
6110
6285
|
const envelope = typeof rawEnvelope === "string" ? JSON.parse(rawEnvelope) : rawEnvelope;
|
|
6286
|
+
if (options?.expectAad === true) {
|
|
6287
|
+
const verdict = checkEnvelopeAad(envelope, this.vaultConfig.spaceId, key);
|
|
6288
|
+
if (verdict === "corrupt") {
|
|
6289
|
+
return vaultError({ code: "DECRYPTION_FAILED", message: "envelope is malformed" });
|
|
6290
|
+
}
|
|
6291
|
+
if (verdict === "mismatch") {
|
|
6292
|
+
return vaultError({
|
|
6293
|
+
code: "AAD_MISMATCH",
|
|
6294
|
+
key,
|
|
6295
|
+
message: `Envelope at ${key} is not bound to this space and key`
|
|
6296
|
+
});
|
|
6297
|
+
}
|
|
6298
|
+
}
|
|
6111
6299
|
const proof = await this.decryptCapabilityProof();
|
|
6112
6300
|
const plaintextResult = await config.service.decryptEnvelope(envelope, proof);
|
|
6113
6301
|
if (!plaintextResult.ok) {
|
|
@@ -6124,7 +6312,11 @@ var DataVaultService = class extends BaseService {
|
|
|
6124
6312
|
contentType,
|
|
6125
6313
|
options
|
|
6126
6314
|
);
|
|
6127
|
-
|
|
6315
|
+
const etag = options?.source === "network" ? valueResult.data.headers?.etag : void 0;
|
|
6316
|
+
return {
|
|
6317
|
+
ok: true,
|
|
6318
|
+
data: { value, metadata, keyId, ...etag === void 0 ? {} : { etag } }
|
|
6319
|
+
};
|
|
6128
6320
|
} catch (error) {
|
|
6129
6321
|
return vaultError({
|
|
6130
6322
|
code: "STORAGE_ERROR",
|
|
@@ -6170,6 +6362,12 @@ var DataVaultService = class extends BaseService {
|
|
|
6170
6362
|
} catch {
|
|
6171
6363
|
return { status: "corrupt_envelope" };
|
|
6172
6364
|
}
|
|
6365
|
+
if (options?.expectAad === true) {
|
|
6366
|
+
const verdict = checkEnvelopeAad(envelope, this.vaultConfig.spaceId, key);
|
|
6367
|
+
if (verdict !== "ok") {
|
|
6368
|
+
return { status: verdict === "corrupt" ? "corrupt_envelope" : "aad_mismatch" };
|
|
6369
|
+
}
|
|
6370
|
+
}
|
|
6173
6371
|
let plaintextResult;
|
|
6174
6372
|
try {
|
|
6175
6373
|
const proof = await this.decryptCapabilityProof();
|
|
@@ -6201,7 +6399,11 @@ var DataVaultService = class extends BaseService {
|
|
|
6201
6399
|
contentType,
|
|
6202
6400
|
options
|
|
6203
6401
|
);
|
|
6204
|
-
|
|
6402
|
+
const etag = valueResult.data.headers?.etag;
|
|
6403
|
+
return {
|
|
6404
|
+
status: "ok",
|
|
6405
|
+
entry: { value, metadata, keyId, ...etag === void 0 ? {} : { etag } }
|
|
6406
|
+
};
|
|
6205
6407
|
} catch {
|
|
6206
6408
|
return { status: "invalid_payload" };
|
|
6207
6409
|
}
|
|
@@ -6251,6 +6453,14 @@ var DataVaultService = class extends BaseService {
|
|
|
6251
6453
|
)
|
|
6252
6454
|
});
|
|
6253
6455
|
}
|
|
6456
|
+
if (options?.ifMatch !== void 0) {
|
|
6457
|
+
return vaultError({
|
|
6458
|
+
code: "STORAGE_ERROR",
|
|
6459
|
+
cause: new Error(
|
|
6460
|
+
"ifMatch is only supported by network-encrypted vaults"
|
|
6461
|
+
)
|
|
6462
|
+
});
|
|
6463
|
+
}
|
|
6254
6464
|
if (!this._isUnlocked || !this.masterKey) {
|
|
6255
6465
|
return vaultError({
|
|
6256
6466
|
code: "VAULT_LOCKED",
|
|
@@ -6276,7 +6486,7 @@ var DataVaultService = class extends BaseService {
|
|
|
6276
6486
|
}
|
|
6277
6487
|
const contentType = options?.contentType ?? (value instanceof Uint8Array ? "application/octet-stream" : "application/json");
|
|
6278
6488
|
const entryKey = this.crypto.randomBytes(32);
|
|
6279
|
-
const keyId =
|
|
6489
|
+
const keyId = hexEncode2(this.crypto.sha256(entryKey)).slice(0, 16);
|
|
6280
6490
|
const encrypted = this.crypto.encrypt(entryKey, plaintext);
|
|
6281
6491
|
const keyBlob = this.crypto.encrypt(this.masterKey, entryKey);
|
|
6282
6492
|
const metadata = {
|
|
@@ -6294,7 +6504,7 @@ var DataVaultService = class extends BaseService {
|
|
|
6294
6504
|
...metadata
|
|
6295
6505
|
});
|
|
6296
6506
|
const keyPayload = JSON.stringify({
|
|
6297
|
-
key:
|
|
6507
|
+
key: base64Encode2(keyBlob),
|
|
6298
6508
|
metadata: keyMetadata
|
|
6299
6509
|
});
|
|
6300
6510
|
const keyPutResult = await this.tc.kv.put(
|
|
@@ -6305,7 +6515,7 @@ var DataVaultService = class extends BaseService {
|
|
|
6305
6515
|
return kvWriteError("Failed to store key blob", keyPutResult.error);
|
|
6306
6516
|
}
|
|
6307
6517
|
const valuePayload = JSON.stringify({
|
|
6308
|
-
data:
|
|
6518
|
+
data: base64Encode2(encrypted),
|
|
6309
6519
|
metadata
|
|
6310
6520
|
});
|
|
6311
6521
|
const valuePutResult = await this.tc.kv.put(
|
|
@@ -6336,6 +6546,12 @@ var DataVaultService = class extends BaseService {
|
|
|
6336
6546
|
if (this.usesNetworkEncryption) {
|
|
6337
6547
|
return this.getNetworkEncrypted(key, options);
|
|
6338
6548
|
}
|
|
6549
|
+
if (options?.expectAad === true) {
|
|
6550
|
+
return vaultError({
|
|
6551
|
+
code: "STORAGE_ERROR",
|
|
6552
|
+
cause: new Error("expectAad is only supported by network-encrypted vaults")
|
|
6553
|
+
});
|
|
6554
|
+
}
|
|
6339
6555
|
if (!this._isUnlocked || !this.masterKey) {
|
|
6340
6556
|
return vaultError({
|
|
6341
6557
|
code: "VAULT_LOCKED",
|
|
@@ -6357,7 +6573,7 @@ var DataVaultService = class extends BaseService {
|
|
|
6357
6573
|
return vaultError({ code: "KEY_NOT_FOUND", key });
|
|
6358
6574
|
}
|
|
6359
6575
|
const keyEnvelope = JSON.parse(keyResult.data.data);
|
|
6360
|
-
const keyBlobBytes =
|
|
6576
|
+
const keyBlobBytes = base64Decode2(keyEnvelope.key);
|
|
6361
6577
|
const entryKey = this.crypto.decrypt(this.masterKey, keyBlobBytes);
|
|
6362
6578
|
const valueResult = await this.tc.kv.get(`vault/${key}`, {
|
|
6363
6579
|
raw: true,
|
|
@@ -6367,7 +6583,7 @@ var DataVaultService = class extends BaseService {
|
|
|
6367
6583
|
return vaultError({ code: "KEY_NOT_FOUND", key });
|
|
6368
6584
|
}
|
|
6369
6585
|
const valueEnvelope = JSON.parse(valueResult.data.data);
|
|
6370
|
-
const encryptedBytes =
|
|
6586
|
+
const encryptedBytes = base64Decode2(valueEnvelope.data);
|
|
6371
6587
|
const plaintext = this.crypto.decrypt(entryKey, encryptedBytes);
|
|
6372
6588
|
const metadata = valueEnvelope.metadata ?? {};
|
|
6373
6589
|
const contentType = metadata[VaultHeaders.CONTENT_TYPE] ?? "application/json";
|
|
@@ -6615,7 +6831,7 @@ var DataVaultService = class extends BaseService {
|
|
|
6615
6831
|
return vaultError({ code: "KEY_NOT_FOUND", key });
|
|
6616
6832
|
}
|
|
6617
6833
|
const keyEnvelope = JSON.parse(keyResult.data.data);
|
|
6618
|
-
const keyBlobBytes =
|
|
6834
|
+
const keyBlobBytes = base64Decode2(keyEnvelope.key);
|
|
6619
6835
|
const entryKey = this.crypto.decrypt(this.masterKey, keyBlobBytes);
|
|
6620
6836
|
const ephemeralSeed = this.crypto.randomBytes(32);
|
|
6621
6837
|
const ephemeralKeyPair = this.crypto.x25519FromSeed(ephemeralSeed);
|
|
@@ -6634,7 +6850,7 @@ var DataVaultService = class extends BaseService {
|
|
|
6634
6850
|
encryptedGrant
|
|
6635
6851
|
);
|
|
6636
6852
|
const grantPayload = JSON.stringify({
|
|
6637
|
-
grant:
|
|
6853
|
+
grant: base64Encode2(grantBlob),
|
|
6638
6854
|
spaceId: this.vaultConfig.spaceId,
|
|
6639
6855
|
metadata: {
|
|
6640
6856
|
[VaultHeaders.GRANT_VERSION]: "1",
|
|
@@ -6674,6 +6890,14 @@ var DataVaultService = class extends BaseService {
|
|
|
6674
6890
|
*/
|
|
6675
6891
|
async getShared(grantorDID, key, options) {
|
|
6676
6892
|
return this.withTelemetry("getShared", key, async () => {
|
|
6893
|
+
if (options?.expectAad === true) {
|
|
6894
|
+
return vaultError({
|
|
6895
|
+
code: "STORAGE_ERROR",
|
|
6896
|
+
cause: new Error(
|
|
6897
|
+
"expectAad is not supported by getShared: the AAD binds the grantor's space, which this vault does not know"
|
|
6898
|
+
)
|
|
6899
|
+
});
|
|
6900
|
+
}
|
|
6677
6901
|
if (this.usesNetworkEncryption) {
|
|
6678
6902
|
const grantorKV = options?.kv;
|
|
6679
6903
|
if (!grantorKV) {
|
|
@@ -6768,7 +6992,7 @@ var DataVaultService = class extends BaseService {
|
|
|
6768
6992
|
});
|
|
6769
6993
|
}
|
|
6770
6994
|
const grantEnvelope = typeof grantResult.data?.data === "string" ? JSON.parse(grantResult.data.data) : grantResult.data?.data;
|
|
6771
|
-
const grantBlobBytes =
|
|
6995
|
+
const grantBlobBytes = base64Decode2(grantEnvelope.grant);
|
|
6772
6996
|
const ephemeralPubKey = grantBlobBytes.slice(0, 32);
|
|
6773
6997
|
const encryptedGrant = grantBlobBytes.slice(32);
|
|
6774
6998
|
const sharedSecret = this.crypto.x25519Dh(
|
|
@@ -6791,7 +7015,7 @@ var DataVaultService = class extends BaseService {
|
|
|
6791
7015
|
});
|
|
6792
7016
|
}
|
|
6793
7017
|
const valueEnvelope = typeof valueResult.data?.data === "string" ? JSON.parse(valueResult.data.data) : valueResult.data?.data;
|
|
6794
|
-
const encryptedBytes =
|
|
7018
|
+
const encryptedBytes = base64Decode2(valueEnvelope.data);
|
|
6795
7019
|
const plaintext = this.crypto.decrypt(entryKey, encryptedBytes);
|
|
6796
7020
|
const metadata = valueEnvelope.metadata ?? {};
|
|
6797
7021
|
const contentType = metadata[VaultHeaders.CONTENT_TYPE] ?? "application/json";
|
|
@@ -6845,7 +7069,7 @@ var DataVaultService = class extends BaseService {
|
|
|
6845
7069
|
if (!result.ok) {
|
|
6846
7070
|
return vaultError({ code: "PUBLIC_KEY_NOT_FOUND", did });
|
|
6847
7071
|
}
|
|
6848
|
-
const pubKeyBytes =
|
|
7072
|
+
const pubKeyBytes = base64Decode2(result.data);
|
|
6849
7073
|
return { ok: true, data: pubKeyBytes };
|
|
6850
7074
|
} catch (error) {
|
|
6851
7075
|
return vaultError({ code: "PUBLIC_KEY_NOT_FOUND", did });
|
|
@@ -6964,7 +7188,7 @@ var DataVaultService = class extends BaseService {
|
|
|
6964
7188
|
}
|
|
6965
7189
|
const currentEntry = getResult.data;
|
|
6966
7190
|
const newEntryKey = this.crypto.randomBytes(32);
|
|
6967
|
-
const newKeyId =
|
|
7191
|
+
const newKeyId = hexEncode2(this.crypto.sha256(newEntryKey)).slice(
|
|
6968
7192
|
0,
|
|
6969
7193
|
16
|
|
6970
7194
|
);
|
|
@@ -6981,7 +7205,7 @@ var DataVaultService = class extends BaseService {
|
|
|
6981
7205
|
[VaultHeaders.KEY_ID]: newKeyId
|
|
6982
7206
|
};
|
|
6983
7207
|
const keyPayload = JSON.stringify({
|
|
6984
|
-
key:
|
|
7208
|
+
key: base64Encode2(newKeyBlob),
|
|
6985
7209
|
metadata: JSON.stringify({
|
|
6986
7210
|
keyId: newKeyId,
|
|
6987
7211
|
...metadata
|
|
@@ -6995,7 +7219,7 @@ var DataVaultService = class extends BaseService {
|
|
|
6995
7219
|
return kvWriteError("Failed to store rotated key blob", keyPutResult.error);
|
|
6996
7220
|
}
|
|
6997
7221
|
const valuePayload = JSON.stringify({
|
|
6998
|
-
data:
|
|
7222
|
+
data: base64Encode2(encrypted),
|
|
6999
7223
|
metadata
|
|
7000
7224
|
});
|
|
7001
7225
|
const valuePutResult = await this.tc.kv.put(
|
|
@@ -7287,116 +7511,6 @@ var SecretsService = class {
|
|
|
7287
7511
|
}
|
|
7288
7512
|
};
|
|
7289
7513
|
|
|
7290
|
-
// src/encryption/canonical.ts
|
|
7291
|
-
function canonicalize(value) {
|
|
7292
|
-
if (value === void 0) {
|
|
7293
|
-
return "";
|
|
7294
|
-
}
|
|
7295
|
-
return stringify(value);
|
|
7296
|
-
}
|
|
7297
|
-
function stringify(value) {
|
|
7298
|
-
if (value === null) return "null";
|
|
7299
|
-
switch (typeof value) {
|
|
7300
|
-
case "boolean":
|
|
7301
|
-
case "number":
|
|
7302
|
-
return JSON.stringify(value);
|
|
7303
|
-
case "string":
|
|
7304
|
-
return JSON.stringify(value);
|
|
7305
|
-
case "object": {
|
|
7306
|
-
if (Array.isArray(value)) {
|
|
7307
|
-
return `[${value.map(stringify).join(",")}]`;
|
|
7308
|
-
}
|
|
7309
|
-
const keys = Object.keys(value).sort();
|
|
7310
|
-
const parts = [];
|
|
7311
|
-
for (const k of keys) {
|
|
7312
|
-
const v = value[k];
|
|
7313
|
-
if (v === void 0) continue;
|
|
7314
|
-
parts.push(`${JSON.stringify(k)}:${stringify(v)}`);
|
|
7315
|
-
}
|
|
7316
|
-
return `{${parts.join(",")}}`;
|
|
7317
|
-
}
|
|
7318
|
-
default:
|
|
7319
|
-
throw new TypeError(
|
|
7320
|
-
`canonicalize: unsupported value type ${typeof value}`
|
|
7321
|
-
);
|
|
7322
|
-
}
|
|
7323
|
-
}
|
|
7324
|
-
var HEX = "0123456789abcdef";
|
|
7325
|
-
function hexEncode2(bytes) {
|
|
7326
|
-
let out = "";
|
|
7327
|
-
for (let i = 0; i < bytes.length; i++) {
|
|
7328
|
-
const b = bytes[i];
|
|
7329
|
-
out += HEX[b >> 4 & 15] + HEX[b & 15];
|
|
7330
|
-
}
|
|
7331
|
-
return out;
|
|
7332
|
-
}
|
|
7333
|
-
function hexDecode(hex) {
|
|
7334
|
-
if (hex.length % 2 !== 0) {
|
|
7335
|
-
throw new Error("hex string must have even length");
|
|
7336
|
-
}
|
|
7337
|
-
const out = new Uint8Array(hex.length / 2);
|
|
7338
|
-
for (let i = 0; i < out.length; i++) {
|
|
7339
|
-
const hi = parseInt(hex[i * 2], 16);
|
|
7340
|
-
const lo = parseInt(hex[i * 2 + 1], 16);
|
|
7341
|
-
if (Number.isNaN(hi) || Number.isNaN(lo)) {
|
|
7342
|
-
throw new Error("invalid hex character");
|
|
7343
|
-
}
|
|
7344
|
-
out[i] = hi << 4 | lo;
|
|
7345
|
-
}
|
|
7346
|
-
return out;
|
|
7347
|
-
}
|
|
7348
|
-
function base64Encode2(bytes) {
|
|
7349
|
-
const chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
|
|
7350
|
-
let out = "";
|
|
7351
|
-
for (let i = 0; i < bytes.length; i += 3) {
|
|
7352
|
-
const b0 = bytes[i];
|
|
7353
|
-
const b1 = i + 1 < bytes.length ? bytes[i + 1] : 0;
|
|
7354
|
-
const b2 = i + 2 < bytes.length ? bytes[i + 2] : 0;
|
|
7355
|
-
out += chars[b0 >> 2 & 63];
|
|
7356
|
-
out += chars[(b0 << 4 | b1 >> 4) & 63];
|
|
7357
|
-
out += i + 1 < bytes.length ? chars[(b1 << 2 | b2 >> 6) & 63] : "=";
|
|
7358
|
-
out += i + 2 < bytes.length ? chars[b2 & 63] : "=";
|
|
7359
|
-
}
|
|
7360
|
-
return out;
|
|
7361
|
-
}
|
|
7362
|
-
function base64Decode2(s) {
|
|
7363
|
-
const clean = s.replace(/[^A-Za-z0-9+/=]/g, "");
|
|
7364
|
-
const len = clean.length;
|
|
7365
|
-
if (len % 4 !== 0) {
|
|
7366
|
-
throw new Error("invalid base64 input");
|
|
7367
|
-
}
|
|
7368
|
-
const padding = clean.endsWith("==") ? 2 : clean.endsWith("=") ? 1 : 0;
|
|
7369
|
-
const outLen = len / 4 * 3 - padding;
|
|
7370
|
-
const out = new Uint8Array(outLen);
|
|
7371
|
-
const lookup = {};
|
|
7372
|
-
const chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
|
|
7373
|
-
for (let i = 0; i < chars.length; i++) lookup[chars[i]] = i;
|
|
7374
|
-
let outIdx = 0;
|
|
7375
|
-
for (let i = 0; i < len; i += 4) {
|
|
7376
|
-
const v0 = lookup[clean[i]] ?? 0;
|
|
7377
|
-
const v1 = lookup[clean[i + 1]] ?? 0;
|
|
7378
|
-
const v2 = clean[i + 2] === "=" ? 0 : lookup[clean[i + 2]] ?? 0;
|
|
7379
|
-
const v3 = clean[i + 3] === "=" ? 0 : lookup[clean[i + 3]] ?? 0;
|
|
7380
|
-
const b0 = v0 << 2 | v1 >> 4;
|
|
7381
|
-
const b1 = (v1 & 15) << 4 | v2 >> 2;
|
|
7382
|
-
const b2 = (v2 & 3) << 6 | v3;
|
|
7383
|
-
if (outIdx < outLen) out[outIdx++] = b0;
|
|
7384
|
-
if (outIdx < outLen) out[outIdx++] = b1;
|
|
7385
|
-
if (outIdx < outLen) out[outIdx++] = b2;
|
|
7386
|
-
}
|
|
7387
|
-
return out;
|
|
7388
|
-
}
|
|
7389
|
-
function utf8Encode(s) {
|
|
7390
|
-
return new TextEncoder().encode(s);
|
|
7391
|
-
}
|
|
7392
|
-
function utf8Decode(b) {
|
|
7393
|
-
return new TextDecoder().decode(b);
|
|
7394
|
-
}
|
|
7395
|
-
function canonicalHashHex(sha256, value) {
|
|
7396
|
-
const canonical = canonicalize(value);
|
|
7397
|
-
return hexEncode2(sha256(utf8Encode(canonical)));
|
|
7398
|
-
}
|
|
7399
|
-
|
|
7400
7514
|
// src/encryption/types.ts
|
|
7401
7515
|
var DEFAULT_ENCRYPTION_ALG = "x25519-aes256gcm/v1";
|
|
7402
7516
|
var ENVELOPE_VERSION = 1;
|
|
@@ -7606,7 +7720,7 @@ function encryptToNetwork(crypto2, input) {
|
|
|
7606
7720
|
const symmetricKey = crypto2.randomBytes(32);
|
|
7607
7721
|
const ciphertext = crypto2.authEncrypt(symmetricKey, input.plaintext, input.aad);
|
|
7608
7722
|
const wrapped = crypto2.sealToNetworkKey(input.networkPublicKey, symmetricKey);
|
|
7609
|
-
const encryptedSymmetricKey =
|
|
7723
|
+
const encryptedSymmetricKey = base64Encode(wrapped);
|
|
7610
7724
|
const encryptedSymmetricKeyHash = canonicalHashHex(
|
|
7611
7725
|
crypto2.sha256,
|
|
7612
7726
|
encryptedSymmetricKey
|
|
@@ -7618,8 +7732,8 @@ function encryptToNetwork(crypto2, input) {
|
|
|
7618
7732
|
keyVersion,
|
|
7619
7733
|
encryptedSymmetricKey,
|
|
7620
7734
|
encryptedSymmetricKeyHash,
|
|
7621
|
-
ciphertext:
|
|
7622
|
-
...input.aad !== void 0 ? { aad:
|
|
7735
|
+
ciphertext: base64Encode(ciphertext),
|
|
7736
|
+
...input.aad !== void 0 ? { aad: base64Encode(input.aad) } : {},
|
|
7623
7737
|
...input.metadata !== void 0 ? { metadata: input.metadata } : {}
|
|
7624
7738
|
};
|
|
7625
7739
|
return { envelope, symmetricKey };
|
|
@@ -7693,8 +7807,8 @@ function validateEnvelope(crypto2, envelope) {
|
|
|
7693
7807
|
return { ok: true, data: e };
|
|
7694
7808
|
}
|
|
7695
7809
|
function decryptEnvelopeWithKey(crypto2, envelope, symmetricKey) {
|
|
7696
|
-
const ciphertext =
|
|
7697
|
-
const aad = envelope.aad !== void 0 ?
|
|
7810
|
+
const ciphertext = base64Decode(envelope.ciphertext);
|
|
7811
|
+
const aad = envelope.aad !== void 0 ? base64Decode(envelope.aad) : void 0;
|
|
7698
7812
|
return crypto2.authDecrypt(symmetricKey, ciphertext, aad);
|
|
7699
7813
|
}
|
|
7700
7814
|
|
|
@@ -7712,7 +7826,7 @@ function buildCanonicalDecryptRequest(input) {
|
|
|
7712
7826
|
return { canonicalBody, bodyHash, receiverPublicKeyHash };
|
|
7713
7827
|
}
|
|
7714
7828
|
function buildDecryptFacts(input) {
|
|
7715
|
-
const bodyHash = input.canonicalBody !== void 0 ?
|
|
7829
|
+
const bodyHash = input.canonicalBody !== void 0 ? hexEncode(input.crypto.sha256(utf8Encode(input.canonicalBody))) : canonicalHashHex(
|
|
7716
7830
|
input.crypto.sha256,
|
|
7717
7831
|
input.body
|
|
7718
7832
|
);
|
|
@@ -7995,7 +8109,7 @@ function verifyDecryptResponse(input) {
|
|
|
7995
8109
|
})
|
|
7996
8110
|
};
|
|
7997
8111
|
}
|
|
7998
|
-
const expectedRequestHash =
|
|
8112
|
+
const expectedRequestHash = hexEncode(
|
|
7999
8113
|
crypto2.sha256(utf8Encode(`${invocationCid}${requestBodyHash}`))
|
|
8000
8114
|
);
|
|
8001
8115
|
if (response.requestHash !== expectedRequestHash) {
|
|
@@ -8019,7 +8133,7 @@ function verifyDecryptResponse(input) {
|
|
|
8019
8133
|
const signedBytes = new TextEncoder().encode(
|
|
8020
8134
|
canonicalSignedResponse(response)
|
|
8021
8135
|
);
|
|
8022
|
-
const signatureBytes =
|
|
8136
|
+
const signatureBytes = base64Decode(response.nodeSignature);
|
|
8023
8137
|
if (!crypto2.verifyNodeSignature(response.nodeId, signedBytes, signatureBytes)) {
|
|
8024
8138
|
return {
|
|
8025
8139
|
ok: false,
|
|
@@ -8031,7 +8145,7 @@ function verifyDecryptResponse(input) {
|
|
|
8031
8145
|
return { ok: true, data: response };
|
|
8032
8146
|
}
|
|
8033
8147
|
function openWrappedKey(crypto2, receiverPrivateKey, response) {
|
|
8034
|
-
const wrapped =
|
|
8148
|
+
const wrapped = base64Decode(response.wrappedKey);
|
|
8035
8149
|
return crypto2.openWithReceiverKey(receiverPrivateKey, wrapped);
|
|
8036
8150
|
}
|
|
8037
8151
|
|
|
@@ -8075,7 +8189,7 @@ var EncryptionService = class extends BaseService {
|
|
|
8075
8189
|
const usable = ensureNetworkUsableForDecrypt(discovered.data);
|
|
8076
8190
|
if (!usable.ok) return usable;
|
|
8077
8191
|
const descriptor = usable.data;
|
|
8078
|
-
const networkPublicKey =
|
|
8192
|
+
const networkPublicKey = base64Decode(descriptor.publicEncryptionKey);
|
|
8079
8193
|
const result = encryptToNetwork(this.crypto, {
|
|
8080
8194
|
// Discovery returns the node's canonical network identifier. Persist
|
|
8081
8195
|
// that spelling in the envelope so a later response can be compared
|
|
@@ -8103,7 +8217,7 @@ var EncryptionService = class extends BaseService {
|
|
|
8103
8217
|
this.assertActive();
|
|
8104
8218
|
const validated = validateEnvelope(this.crypto, envelope);
|
|
8105
8219
|
if (!validated.ok) return validated;
|
|
8106
|
-
if (options?.aad !== void 0 && validated.data.aad !==
|
|
8220
|
+
if (options?.aad !== void 0 && validated.data.aad !== base64Encode(options.aad)) {
|
|
8107
8221
|
return encErr(
|
|
8108
8222
|
encryptionError({
|
|
8109
8223
|
code: "INVALID_INPUT",
|
|
@@ -8131,7 +8245,7 @@ var EncryptionService = class extends BaseService {
|
|
|
8131
8245
|
);
|
|
8132
8246
|
}
|
|
8133
8247
|
const receiverKey = generateRandomReceiverKey({ crypto: this.crypto });
|
|
8134
|
-
const receiverPublicKey =
|
|
8248
|
+
const receiverPublicKey = base64Encode(receiverKey.publicKey);
|
|
8135
8249
|
const receiverPublicKeyHash = canonicalHashHex(
|
|
8136
8250
|
this.crypto.sha256,
|
|
8137
8251
|
receiverPublicKey
|
|
@@ -8307,8 +8421,8 @@ export {
|
|
|
8307
8421
|
authRequiredError,
|
|
8308
8422
|
authUnauthorizedError,
|
|
8309
8423
|
authorizationVerdictOf,
|
|
8310
|
-
|
|
8311
|
-
|
|
8424
|
+
base64Decode,
|
|
8425
|
+
base64Encode,
|
|
8312
8426
|
buildCanonicalDecryptRequest,
|
|
8313
8427
|
buildDecryptAttenuation,
|
|
8314
8428
|
buildDecryptFacts,
|
|
@@ -8338,7 +8452,7 @@ export {
|
|
|
8338
8452
|
generateRandomReceiverKey,
|
|
8339
8453
|
getTinyCloudDebugLogs,
|
|
8340
8454
|
hexDecode,
|
|
8341
|
-
|
|
8455
|
+
hexEncode,
|
|
8342
8456
|
installTinyCloudDebugGlobals,
|
|
8343
8457
|
isNetworkId,
|
|
8344
8458
|
isStorageFullError,
|