@tinycloud/sdk-services 2.7.0-beta.3 → 2.7.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-BwBU5CQ4.d.cts +158 -0
- package/dist/BaseService-CnPTKR5G.d.ts +158 -0
- package/dist/encryption/index.cjs +46 -6
- package/dist/encryption/index.cjs.map +1 -1
- package/dist/encryption/index.d.cts +9 -1
- package/dist/encryption/index.d.ts +9 -1
- package/dist/encryption/index.js +45 -6
- package/dist/encryption/index.js.map +1 -1
- package/dist/index.cjs +299 -21
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +58 -9
- package/dist/index.d.ts +58 -9
- package/dist/index.js +293 -18
- package/dist/index.js.map +1 -1
- package/dist/internal/decrypt-transport-response-error.cjs +38 -0
- package/dist/internal/decrypt-transport-response-error.cjs.map +1 -0
- package/dist/internal/decrypt-transport-response-error.d.cts +10 -0
- package/dist/internal/decrypt-transport-response-error.d.ts +10 -0
- package/dist/internal/decrypt-transport-response-error.js +13 -0
- package/dist/internal/decrypt-transport-response-error.js.map +1 -0
- package/dist/kv/index.cjs +46 -4
- package/dist/kv/index.cjs.map +1 -1
- package/dist/kv/index.d.cts +2 -1
- package/dist/kv/index.d.ts +2 -1
- package/dist/kv/index.js +46 -4
- package/dist/kv/index.js.map +1 -1
- package/dist/sql/index.cjs +6 -2
- package/dist/sql/index.cjs.map +1 -1
- package/dist/sql/index.d.cts +2 -1
- package/dist/sql/index.d.ts +2 -1
- package/dist/sql/index.js +6 -2
- package/dist/sql/index.js.map +1 -1
- package/dist/{BaseService-C-EsEeGo.d.cts → types-DAY1O1kW.d.cts} +12 -156
- package/dist/{BaseService-C-EsEeGo.d.ts → types-DAY1O1kW.d.ts} +12 -156
- package/package.json +11 -3
package/dist/index.cjs
CHANGED
|
@@ -18,8 +18,8 @@ var __copyProps = (to, from, except, desc) => {
|
|
|
18
18
|
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
19
|
|
|
20
20
|
// src/index.ts
|
|
21
|
-
var
|
|
22
|
-
__export(
|
|
21
|
+
var src_exports = {};
|
|
22
|
+
__export(src_exports, {
|
|
23
23
|
BaseService: () => BaseService,
|
|
24
24
|
DECRYPT_ACTION: () => DECRYPT_ACTION,
|
|
25
25
|
DECRYPT_FACT_TYPE: () => DECRYPT_FACT_TYPE,
|
|
@@ -29,6 +29,7 @@ __export(index_exports, {
|
|
|
29
29
|
DEFAULT_SIGNED_READ_URL_EXPIRY_MS: () => DEFAULT_SIGNED_READ_URL_EXPIRY_MS,
|
|
30
30
|
DataVaultService: () => DataVaultService,
|
|
31
31
|
DatabaseHandle: () => DatabaseHandle,
|
|
32
|
+
DecryptTransportResponseError: () => import_decrypt_transport_response_error3.DecryptTransportResponseError,
|
|
32
33
|
DuckDbAction: () => DuckDbAction,
|
|
33
34
|
DuckDbDatabaseHandle: () => DuckDbDatabaseHandle,
|
|
34
35
|
DuckDbService: () => DuckDbService,
|
|
@@ -112,6 +113,8 @@ __export(index_exports, {
|
|
|
112
113
|
openWrappedKey: () => openWrappedKey,
|
|
113
114
|
parseAuthError: () => parseAuthError,
|
|
114
115
|
parseNetworkId: () => parseNetworkId,
|
|
116
|
+
parsePermissionHint: () => parsePermissionHint,
|
|
117
|
+
parsePermissionHintFromErrorText: () => parsePermissionHintFromErrorText,
|
|
115
118
|
permissionDeniedError: () => permissionDeniedError,
|
|
116
119
|
resolveSecretListPrefix: () => resolveSecretListPrefix,
|
|
117
120
|
resolveSecretPath: () => resolveSecretPath,
|
|
@@ -134,7 +137,7 @@ __export(index_exports, {
|
|
|
134
137
|
verifyDecryptResponse: () => verifyDecryptResponse,
|
|
135
138
|
wrapError: () => wrapError
|
|
136
139
|
});
|
|
137
|
-
module.exports = __toCommonJS(
|
|
140
|
+
module.exports = __toCommonJS(src_exports);
|
|
138
141
|
|
|
139
142
|
// src/types.ts
|
|
140
143
|
var ErrorCodes = {
|
|
@@ -205,6 +208,63 @@ function serviceError(code, message, service, options) {
|
|
|
205
208
|
};
|
|
206
209
|
}
|
|
207
210
|
|
|
211
|
+
// src/diagnostics.ts
|
|
212
|
+
var REDACTED = "[REDACTED]";
|
|
213
|
+
var SAFE_NUMBER_FIELDS = /* @__PURE__ */ new Set([
|
|
214
|
+
"duration",
|
|
215
|
+
"durationMs",
|
|
216
|
+
"endedAt",
|
|
217
|
+
"startedAt",
|
|
218
|
+
"status",
|
|
219
|
+
"timestamp"
|
|
220
|
+
]);
|
|
221
|
+
var SAFE_BOOLEAN_FIELDS = /* @__PURE__ */ new Set(["authenticated", "ok", "persisted"]);
|
|
222
|
+
function finiteNumber(value) {
|
|
223
|
+
return typeof value === "number" && Number.isFinite(value) ? value : void 0;
|
|
224
|
+
}
|
|
225
|
+
function isBinaryData(value) {
|
|
226
|
+
try {
|
|
227
|
+
return Array.isArray(value) || ArrayBuffer.isView(value) || value instanceof ArrayBuffer;
|
|
228
|
+
} catch {
|
|
229
|
+
return true;
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
function read(value, key) {
|
|
233
|
+
try {
|
|
234
|
+
return value[key];
|
|
235
|
+
} catch {
|
|
236
|
+
return REDACTED;
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
function projectDiagnosticError(error) {
|
|
240
|
+
if (typeof error !== "object" || error === null) return {};
|
|
241
|
+
const status = finiteNumber(read(error, "status"));
|
|
242
|
+
return status !== void 0 && status >= 100 && status <= 599 ? { status } : {};
|
|
243
|
+
}
|
|
244
|
+
function projectDiagnosticData(value) {
|
|
245
|
+
if (value === null || value === void 0) return value;
|
|
246
|
+
if (typeof value !== "object") return REDACTED;
|
|
247
|
+
try {
|
|
248
|
+
if (isBinaryData(value)) return REDACTED;
|
|
249
|
+
const projected = {};
|
|
250
|
+
for (const key of SAFE_NUMBER_FIELDS) {
|
|
251
|
+
const number = finiteNumber(read(value, key));
|
|
252
|
+
if (number !== void 0) projected[key] = number;
|
|
253
|
+
}
|
|
254
|
+
for (const key of SAFE_BOOLEAN_FIELDS) {
|
|
255
|
+
const boolean = read(value, key);
|
|
256
|
+
if (typeof boolean === "boolean") projected[key] = boolean;
|
|
257
|
+
}
|
|
258
|
+
const url = read(value, "url");
|
|
259
|
+
if (url !== void 0) projected.url = REDACTED;
|
|
260
|
+
const error = read(value, "error");
|
|
261
|
+
if (error !== void 0) projected.error = projectDiagnosticError(error);
|
|
262
|
+
return projected;
|
|
263
|
+
} catch {
|
|
264
|
+
return REDACTED;
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
|
|
208
268
|
// src/debug.ts
|
|
209
269
|
var DEBUG_FLAG = "TinyCloud_debug";
|
|
210
270
|
var MAX_EVENTS = 1e3;
|
|
@@ -363,6 +423,8 @@ var TinyCloudDebugLogger = class {
|
|
|
363
423
|
record(event) {
|
|
364
424
|
const debugEvent = {
|
|
365
425
|
...event,
|
|
426
|
+
...event.data === void 0 ? {} : { data: projectDiagnosticData(event.data) },
|
|
427
|
+
...event.message === void 0 ? {} : { message: "[REDACTED]" },
|
|
366
428
|
sequence: ++this.sequence
|
|
367
429
|
};
|
|
368
430
|
this.events.push(debugEvent);
|
|
@@ -642,6 +704,7 @@ var ServiceContext = class {
|
|
|
642
704
|
this._services = /* @__PURE__ */ new Map();
|
|
643
705
|
this._eventHandlers = /* @__PURE__ */ new Map();
|
|
644
706
|
this._abortController = new AbortController();
|
|
707
|
+
this._retired = false;
|
|
645
708
|
this._invoke = this.wrapInvoke(config.invoke);
|
|
646
709
|
this._invokeAny = config.invokeAny ? this.wrapInvokeAny(config.invokeAny) : void 0;
|
|
647
710
|
this._fetch = this.wrapFetch(config.fetch ?? globalThis.fetch.bind(globalThis));
|
|
@@ -661,13 +724,13 @@ var ServiceContext = class {
|
|
|
661
724
|
* Get the current session.
|
|
662
725
|
*/
|
|
663
726
|
get session() {
|
|
664
|
-
return this._session;
|
|
727
|
+
return this._retired ? null : this._session;
|
|
665
728
|
}
|
|
666
729
|
/**
|
|
667
730
|
* Check if the context has an authenticated session.
|
|
668
731
|
*/
|
|
669
732
|
get isAuthenticated() {
|
|
670
|
-
return this._session !== null;
|
|
733
|
+
return !this._retired && this._session !== null;
|
|
671
734
|
}
|
|
672
735
|
/**
|
|
673
736
|
* Update the session and notify all registered services.
|
|
@@ -675,6 +738,7 @@ var ServiceContext = class {
|
|
|
675
738
|
* @param session - New session or null to clear
|
|
676
739
|
*/
|
|
677
740
|
setSession(session) {
|
|
741
|
+
this.assertActive();
|
|
678
742
|
this._session = session;
|
|
679
743
|
this.emit("session.changed", { authenticated: session !== null });
|
|
680
744
|
for (const service of this._services.values()) {
|
|
@@ -688,24 +752,28 @@ var ServiceContext = class {
|
|
|
688
752
|
* Get the invoke function for WASM operations.
|
|
689
753
|
*/
|
|
690
754
|
get invoke() {
|
|
755
|
+
this.assertActive();
|
|
691
756
|
return this._invoke;
|
|
692
757
|
}
|
|
693
758
|
/**
|
|
694
759
|
* Get the multi-resource invoke function when available.
|
|
695
760
|
*/
|
|
696
761
|
get invokeAny() {
|
|
762
|
+
this.assertActive();
|
|
697
763
|
return this._invokeAny;
|
|
698
764
|
}
|
|
699
765
|
/**
|
|
700
766
|
* Get the fetch function for HTTP requests.
|
|
701
767
|
*/
|
|
702
768
|
get fetch() {
|
|
769
|
+
this.assertActive();
|
|
703
770
|
return this._fetch;
|
|
704
771
|
}
|
|
705
772
|
/**
|
|
706
773
|
* Get the list of TinyCloud host URLs.
|
|
707
774
|
*/
|
|
708
775
|
get hosts() {
|
|
776
|
+
this.assertActive();
|
|
709
777
|
return this._hosts;
|
|
710
778
|
}
|
|
711
779
|
// ============================================================
|
|
@@ -750,7 +818,7 @@ var ServiceContext = class {
|
|
|
750
818
|
tinyCloudDebugLogger.log(event, data);
|
|
751
819
|
if (this._telemetryEnabled && this._telemetryHandler) {
|
|
752
820
|
try {
|
|
753
|
-
this._telemetryHandler(event, data);
|
|
821
|
+
this._telemetryHandler(event, projectDiagnosticData(data));
|
|
754
822
|
} catch (error) {
|
|
755
823
|
console.error(`Error in telemetry handler for "${event}":`, error);
|
|
756
824
|
}
|
|
@@ -809,21 +877,45 @@ var ServiceContext = class {
|
|
|
809
877
|
get abortSignal() {
|
|
810
878
|
return this._abortController.signal;
|
|
811
879
|
}
|
|
880
|
+
/**
|
|
881
|
+
* Permanently retire this graph after its owner installs a replacement.
|
|
882
|
+
* Unlike `abort()`, retirement never creates a fresh controller, so captured
|
|
883
|
+
* services cannot resume calls under a superseded session.
|
|
884
|
+
*/
|
|
885
|
+
retire() {
|
|
886
|
+
if (this._retired) return;
|
|
887
|
+
this._retired = true;
|
|
888
|
+
this._session = null;
|
|
889
|
+
this._abortController.abort();
|
|
890
|
+
for (const service of this._services.values()) {
|
|
891
|
+
try {
|
|
892
|
+
service.onSignOut();
|
|
893
|
+
} catch (error) {
|
|
894
|
+
console.error("Error retiring service:", error);
|
|
895
|
+
}
|
|
896
|
+
}
|
|
897
|
+
}
|
|
812
898
|
/**
|
|
813
899
|
* Abort all pending operations and notify services.
|
|
814
900
|
* Creates a new AbortController for future operations.
|
|
815
901
|
*/
|
|
816
902
|
abort() {
|
|
903
|
+
if (this._retired) return;
|
|
817
904
|
this._abortController.abort();
|
|
818
905
|
this._abortController = new AbortController();
|
|
819
906
|
for (const service of this._services.values()) {
|
|
820
|
-
|
|
907
|
+
try {
|
|
908
|
+
service.onSignOut();
|
|
909
|
+
} catch (error) {
|
|
910
|
+
console.error("Error signing out service:", error);
|
|
911
|
+
}
|
|
821
912
|
}
|
|
822
913
|
}
|
|
823
914
|
/**
|
|
824
915
|
* Sign out - abort operations and clear session.
|
|
825
916
|
*/
|
|
826
917
|
signOut() {
|
|
918
|
+
if (this._retired) return;
|
|
827
919
|
this.abort();
|
|
828
920
|
this.setSession(null);
|
|
829
921
|
this.emit("session.expired", {});
|
|
@@ -839,6 +931,7 @@ var ServiceContext = class {
|
|
|
839
931
|
}
|
|
840
932
|
wrapInvoke(invoke) {
|
|
841
933
|
return (session, service, path, action, facts) => {
|
|
934
|
+
this.assertActive();
|
|
842
935
|
if (!tinyCloudDebugLogger.isEnabled()) {
|
|
843
936
|
return invoke(session, service, path, action, facts);
|
|
844
937
|
}
|
|
@@ -858,8 +951,14 @@ var ServiceContext = class {
|
|
|
858
951
|
}
|
|
859
952
|
};
|
|
860
953
|
}
|
|
954
|
+
assertActive() {
|
|
955
|
+
if (this._retired) {
|
|
956
|
+
throw new Error("Service graph has been retired by session replacement.");
|
|
957
|
+
}
|
|
958
|
+
}
|
|
861
959
|
wrapInvokeAny(invokeAny) {
|
|
862
960
|
return (session, entries, facts) => {
|
|
961
|
+
this.assertActive();
|
|
863
962
|
if (!tinyCloudDebugLogger.isEnabled()) {
|
|
864
963
|
return invokeAny(session, entries, facts);
|
|
865
964
|
}
|
|
@@ -879,20 +978,25 @@ var ServiceContext = class {
|
|
|
879
978
|
}
|
|
880
979
|
wrapFetch(fetchFn) {
|
|
881
980
|
return async (url, init) => {
|
|
981
|
+
this.assertActive();
|
|
982
|
+
const request = {
|
|
983
|
+
...init,
|
|
984
|
+
signal: this.combineSignals(init?.signal)
|
|
985
|
+
};
|
|
882
986
|
if (!tinyCloudDebugLogger.isEnabled()) {
|
|
883
|
-
return fetchFn(url,
|
|
987
|
+
return fetchFn(url, request);
|
|
884
988
|
}
|
|
885
989
|
const timer = tinyCloudDebugLogger.startTimer("sdk.fetch", {
|
|
886
990
|
url,
|
|
887
|
-
method:
|
|
888
|
-
init
|
|
991
|
+
method: request.method ?? "GET",
|
|
992
|
+
init: request
|
|
889
993
|
});
|
|
890
994
|
try {
|
|
891
|
-
const response = await fetchFn(url,
|
|
995
|
+
const response = await fetchFn(url, request);
|
|
892
996
|
timer.stop({
|
|
893
997
|
ok: response.ok,
|
|
894
998
|
url,
|
|
895
|
-
method:
|
|
999
|
+
method: request.method ?? "GET",
|
|
896
1000
|
status: response.status,
|
|
897
1001
|
statusText: response.statusText
|
|
898
1002
|
});
|
|
@@ -901,16 +1005,70 @@ var ServiceContext = class {
|
|
|
901
1005
|
timer.stop({
|
|
902
1006
|
ok: false,
|
|
903
1007
|
url,
|
|
904
|
-
method:
|
|
1008
|
+
method: request.method ?? "GET",
|
|
905
1009
|
error
|
|
906
1010
|
});
|
|
907
1011
|
throw error;
|
|
908
1012
|
}
|
|
909
1013
|
};
|
|
910
1014
|
}
|
|
1015
|
+
combineSignals(signal) {
|
|
1016
|
+
if (!signal) return this._abortController.signal;
|
|
1017
|
+
const combined = new AbortController();
|
|
1018
|
+
const abort = (source) => combined.abort(source.reason);
|
|
1019
|
+
if (signal.aborted) {
|
|
1020
|
+
abort(signal);
|
|
1021
|
+
} else if (this._abortController.signal.aborted) {
|
|
1022
|
+
abort(this._abortController.signal);
|
|
1023
|
+
} else {
|
|
1024
|
+
signal.addEventListener("abort", () => abort(signal), { once: true });
|
|
1025
|
+
this._abortController.signal.addEventListener(
|
|
1026
|
+
"abort",
|
|
1027
|
+
() => abort(this._abortController.signal),
|
|
1028
|
+
{ once: true }
|
|
1029
|
+
);
|
|
1030
|
+
}
|
|
1031
|
+
return combined.signal;
|
|
1032
|
+
}
|
|
911
1033
|
};
|
|
912
1034
|
|
|
913
1035
|
// src/errors.ts
|
|
1036
|
+
function parsePermissionHint(value) {
|
|
1037
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) return void 0;
|
|
1038
|
+
const candidate = value;
|
|
1039
|
+
const keys = Object.keys(candidate).sort();
|
|
1040
|
+
if (keys.some((key) => !["actions", "path", "service", "space"].includes(key))) return void 0;
|
|
1041
|
+
if (candidate.service !== "tinycloud.kv" && candidate.service !== "tinycloud.encryption") return void 0;
|
|
1042
|
+
if (typeof candidate.path !== "string" || candidate.path.length === 0 || candidate.path.includes("*")) return void 0;
|
|
1043
|
+
if (candidate.service === "tinycloud.kv" && (typeof candidate.space !== "string" || candidate.space.length === 0 || !candidate.path.startsWith("vault/") || candidate.path.endsWith("/"))) {
|
|
1044
|
+
return void 0;
|
|
1045
|
+
}
|
|
1046
|
+
if (candidate.service === "tinycloud.encryption" && (candidate.space !== void 0 || !candidate.path.startsWith("urn:tinycloud:encryption:") || candidate.path.endsWith(":"))) {
|
|
1047
|
+
return void 0;
|
|
1048
|
+
}
|
|
1049
|
+
if (!Array.isArray(candidate.actions) || candidate.actions.length !== 1 || typeof candidate.actions[0] !== "string" || candidate.actions[0].includes("*")) return void 0;
|
|
1050
|
+
const expectedAction = candidate.service === "tinycloud.kv" ? "tinycloud.kv/get" : "tinycloud.encryption/decrypt";
|
|
1051
|
+
if (candidate.actions[0] !== expectedAction) return void 0;
|
|
1052
|
+
const space = typeof candidate.space === "string" ? candidate.space : void 0;
|
|
1053
|
+
return {
|
|
1054
|
+
service: candidate.service,
|
|
1055
|
+
...space === void 0 ? {} : { space },
|
|
1056
|
+
path: candidate.path,
|
|
1057
|
+
actions: [candidate.actions[0]]
|
|
1058
|
+
};
|
|
1059
|
+
}
|
|
1060
|
+
function parsePermissionHintFromErrorText(text) {
|
|
1061
|
+
try {
|
|
1062
|
+
const parsed = JSON.parse(text);
|
|
1063
|
+
if (typeof parsed !== "object" || parsed === null) return void 0;
|
|
1064
|
+
const root = parsed;
|
|
1065
|
+
const nested = root.error;
|
|
1066
|
+
const nestedRecord = typeof nested === "object" && nested !== null ? nested : void 0;
|
|
1067
|
+
return parsePermissionHint(root.permissionHint) ?? parsePermissionHint(nestedRecord?.permissionHint);
|
|
1068
|
+
} catch {
|
|
1069
|
+
return void 0;
|
|
1070
|
+
}
|
|
1071
|
+
}
|
|
914
1072
|
function authRequiredError(service) {
|
|
915
1073
|
return {
|
|
916
1074
|
code: ErrorCodes.AUTH_REQUIRED,
|
|
@@ -1061,7 +1219,7 @@ var BaseService = class {
|
|
|
1061
1219
|
* Combines the service-level abort with context-level abort.
|
|
1062
1220
|
*/
|
|
1063
1221
|
get abortSignal() {
|
|
1064
|
-
return this.
|
|
1222
|
+
return this.combineSignals();
|
|
1065
1223
|
}
|
|
1066
1224
|
/**
|
|
1067
1225
|
* Check if the service is authenticated.
|
|
@@ -1177,7 +1335,11 @@ var BaseService = class {
|
|
|
1177
1335
|
*/
|
|
1178
1336
|
combineSignals(...signals) {
|
|
1179
1337
|
const controller = new AbortController();
|
|
1180
|
-
const allSignals = [
|
|
1338
|
+
const allSignals = [
|
|
1339
|
+
this.abortController.signal,
|
|
1340
|
+
this.context?.abortSignal,
|
|
1341
|
+
...signals.filter(Boolean)
|
|
1342
|
+
].filter(Boolean);
|
|
1181
1343
|
for (const signal of allSignals) {
|
|
1182
1344
|
if (signal.aborted) {
|
|
1183
1345
|
controller.abort(signal.reason);
|
|
@@ -1650,13 +1812,15 @@ var KVService = class extends BaseService {
|
|
|
1650
1812
|
options?.signal
|
|
1651
1813
|
);
|
|
1652
1814
|
if (!response.ok) {
|
|
1653
|
-
if (response.status === 401) {
|
|
1815
|
+
if (response.status === 401 || response.status === 403) {
|
|
1654
1816
|
const errorText2 = await response.text();
|
|
1655
1817
|
const { resource, action } = parseAuthError(errorText2);
|
|
1818
|
+
const permissionHint = parsePermissionHintFromErrorText(errorText2);
|
|
1656
1819
|
return err(authUnauthorizedError("kv", errorText2, {
|
|
1657
1820
|
status: response.status,
|
|
1658
1821
|
...action && { requiredAction: action },
|
|
1659
|
-
...resource && { resource }
|
|
1822
|
+
...resource && { resource },
|
|
1823
|
+
...permissionHint === void 0 ? {} : { permissionHint }
|
|
1660
1824
|
}));
|
|
1661
1825
|
}
|
|
1662
1826
|
if (response.status === 404) {
|
|
@@ -3882,6 +4046,9 @@ function unwrapKVData(value) {
|
|
|
3882
4046
|
}
|
|
3883
4047
|
return value;
|
|
3884
4048
|
}
|
|
4049
|
+
function hasHttpResponse(error) {
|
|
4050
|
+
return typeof error.meta?.status === "number";
|
|
4051
|
+
}
|
|
3885
4052
|
function isUnlockSigner(signer) {
|
|
3886
4053
|
return typeof signer === "object" && signer !== null && typeof signer.signMessage === "function";
|
|
3887
4054
|
}
|
|
@@ -4255,6 +4422,80 @@ var DataVaultService = class extends BaseService {
|
|
|
4255
4422
|
});
|
|
4256
4423
|
}
|
|
4257
4424
|
}
|
|
4425
|
+
/**
|
|
4426
|
+
* Read a network-encrypted entry while preserving safe failure phases.
|
|
4427
|
+
*
|
|
4428
|
+
* Unlike {@link getNetworkEncrypted}, this is intentionally message-free so
|
|
4429
|
+
* an operation layer can distinguish an authorized absence from a failed
|
|
4430
|
+
* read without receiving node, envelope, plaintext, or key material.
|
|
4431
|
+
*/
|
|
4432
|
+
async readNetworkEncrypted(key, options) {
|
|
4433
|
+
const config = this.networkEncryption;
|
|
4434
|
+
if (!config || !this.requireAuth()) {
|
|
4435
|
+
return { status: "read_failed" };
|
|
4436
|
+
}
|
|
4437
|
+
let valueResult;
|
|
4438
|
+
try {
|
|
4439
|
+
valueResult = await this.tc.kv.get(`vault/${key}`, { raw: true });
|
|
4440
|
+
} catch {
|
|
4441
|
+
return { status: "node_unreachable" };
|
|
4442
|
+
}
|
|
4443
|
+
if (!valueResult.ok) {
|
|
4444
|
+
const permissionHint = parsePermissionHint(valueResult.error.meta?.permissionHint);
|
|
4445
|
+
if (valueResult.error.code === "AUTH_UNAUTHORIZED" && permissionHint !== void 0) {
|
|
4446
|
+
return { status: "permission_required", hint: permissionHint };
|
|
4447
|
+
}
|
|
4448
|
+
if ((valueResult.error.code === "NOT_FOUND" || valueResult.error.code === "KV_NOT_FOUND") && !hasHttpResponse(valueResult.error)) {
|
|
4449
|
+
return { status: "not_found" };
|
|
4450
|
+
}
|
|
4451
|
+
if (valueResult.error.code === "NETWORK_ERROR" || valueResult.error.code === "TIMEOUT" || valueResult.error.code === "ABORTED") {
|
|
4452
|
+
return hasHttpResponse(valueResult.error) ? { status: "read_failed" } : { status: "node_unreachable" };
|
|
4453
|
+
}
|
|
4454
|
+
return { status: "read_failed" };
|
|
4455
|
+
}
|
|
4456
|
+
let envelope;
|
|
4457
|
+
try {
|
|
4458
|
+
const rawEnvelope = unwrapKVData(valueResult.data);
|
|
4459
|
+
envelope = typeof rawEnvelope === "string" ? JSON.parse(rawEnvelope) : rawEnvelope;
|
|
4460
|
+
} catch {
|
|
4461
|
+
return { status: "corrupt_envelope" };
|
|
4462
|
+
}
|
|
4463
|
+
let plaintextResult;
|
|
4464
|
+
try {
|
|
4465
|
+
const proof = await this.decryptCapabilityProof();
|
|
4466
|
+
plaintextResult = await config.service.decryptEnvelope(envelope, proof);
|
|
4467
|
+
} catch {
|
|
4468
|
+
return { status: "decrypt_failed" };
|
|
4469
|
+
}
|
|
4470
|
+
if (!plaintextResult.ok) {
|
|
4471
|
+
const permissionHint = parsePermissionHint(
|
|
4472
|
+
plaintextResult.error.permissionHint
|
|
4473
|
+
);
|
|
4474
|
+
if (plaintextResult.error.code === "DECRYPT_DENIED" && permissionHint !== void 0) {
|
|
4475
|
+
return { status: "permission_required", hint: permissionHint };
|
|
4476
|
+
}
|
|
4477
|
+
if (plaintextResult.error.code === "INVALID_ENVELOPE") {
|
|
4478
|
+
return { status: "corrupt_envelope" };
|
|
4479
|
+
}
|
|
4480
|
+
if (plaintextResult.error.code === "TRANSPORT_ERROR") {
|
|
4481
|
+
return { status: "node_unreachable" };
|
|
4482
|
+
}
|
|
4483
|
+
return { status: "decrypt_failed" };
|
|
4484
|
+
}
|
|
4485
|
+
try {
|
|
4486
|
+
const metadata = envelope.metadata ?? {};
|
|
4487
|
+
const contentType = metadata[VaultHeaders.CONTENT_TYPE] ?? "application/json";
|
|
4488
|
+
const keyId = metadata[VaultHeaders.KEY_ID] ?? envelope.encryptedSymmetricKeyHash.slice(0, 16);
|
|
4489
|
+
const value = this.deserializeValue(
|
|
4490
|
+
plaintextResult.data,
|
|
4491
|
+
contentType,
|
|
4492
|
+
options
|
|
4493
|
+
);
|
|
4494
|
+
return { status: "ok", entry: { value, metadata, keyId } };
|
|
4495
|
+
} catch {
|
|
4496
|
+
return { status: "invalid_payload" };
|
|
4497
|
+
}
|
|
4498
|
+
}
|
|
4258
4499
|
async headNetworkEncrypted(key) {
|
|
4259
4500
|
if (!this.requireAuth()) {
|
|
4260
4501
|
return vaultError({
|
|
@@ -6082,6 +6323,8 @@ function openWrappedKey(crypto2, receiverPrivateKey, response) {
|
|
|
6082
6323
|
}
|
|
6083
6324
|
|
|
6084
6325
|
// src/encryption/EncryptionService.ts
|
|
6326
|
+
var import_decrypt_transport_response_error = require("@tinycloud/sdk-services/internal/decrypt-transport-response-error");
|
|
6327
|
+
var import_decrypt_transport_response_error2 = require("@tinycloud/sdk-services/internal/decrypt-transport-response-error");
|
|
6085
6328
|
function encOk(data) {
|
|
6086
6329
|
return { ok: true, data };
|
|
6087
6330
|
}
|
|
@@ -6100,17 +6343,20 @@ var EncryptionService = class extends BaseService {
|
|
|
6100
6343
|
return this._config.crypto;
|
|
6101
6344
|
}
|
|
6102
6345
|
async discoverNetwork(identifier, ownerDid) {
|
|
6346
|
+
this.assertActive();
|
|
6103
6347
|
const result = await discoverNetwork({
|
|
6104
6348
|
identifier,
|
|
6105
6349
|
...ownerDid !== void 0 ? { ownerDid } : {},
|
|
6106
6350
|
...this._config.node !== void 0 ? { node: this._config.node } : {},
|
|
6107
6351
|
...this._config.wellKnown !== void 0 ? { wellKnown: this._config.wellKnown } : {}
|
|
6108
6352
|
});
|
|
6353
|
+
this.assertActive();
|
|
6109
6354
|
if (!result.ok) return result;
|
|
6110
6355
|
return encOk(result.data.descriptor);
|
|
6111
6356
|
}
|
|
6112
6357
|
async encryptToNetwork(networkId, plaintext, options) {
|
|
6113
6358
|
try {
|
|
6359
|
+
this.assertActive();
|
|
6114
6360
|
const discovered = await this.discoverNetwork(networkId);
|
|
6115
6361
|
if (!discovered.ok) return discovered;
|
|
6116
6362
|
const usable = ensureNetworkUsableForDecrypt(discovered.data);
|
|
@@ -6138,6 +6384,7 @@ var EncryptionService = class extends BaseService {
|
|
|
6138
6384
|
}
|
|
6139
6385
|
async decryptEnvelope(envelope, capabilityProof, options) {
|
|
6140
6386
|
try {
|
|
6387
|
+
this.assertActive();
|
|
6141
6388
|
const validated = validateEnvelope(this.crypto, envelope);
|
|
6142
6389
|
if (!validated.ok) return validated;
|
|
6143
6390
|
if (options?.aad !== void 0 && validated.data.aad !== base64Encode2(options.aad)) {
|
|
@@ -6203,16 +6450,38 @@ var EncryptionService = class extends BaseService {
|
|
|
6203
6450
|
facts,
|
|
6204
6451
|
proof: capabilityProof
|
|
6205
6452
|
});
|
|
6206
|
-
|
|
6453
|
+
this.assertActive();
|
|
6454
|
+
if (!built.ok) {
|
|
6455
|
+
if (built.error.code !== "TRANSPORT_ERROR") return built;
|
|
6456
|
+
return encErr(
|
|
6457
|
+
encryptionError({
|
|
6458
|
+
code: "INVALID_INPUT",
|
|
6459
|
+
message: "Unable to build decrypt request"
|
|
6460
|
+
})
|
|
6461
|
+
);
|
|
6462
|
+
}
|
|
6207
6463
|
let response;
|
|
6208
6464
|
try {
|
|
6209
6465
|
response = await this._config.transport.postDecrypt({
|
|
6210
6466
|
targetNode,
|
|
6211
6467
|
networkId: envelope.networkId,
|
|
6212
6468
|
authorization: built.data.authorization,
|
|
6213
|
-
canonicalBody: built.data.canonicalBody
|
|
6469
|
+
canonicalBody: built.data.canonicalBody,
|
|
6470
|
+
signal: this.abortSignal
|
|
6214
6471
|
});
|
|
6472
|
+
this.assertActive();
|
|
6215
6473
|
} catch (error) {
|
|
6474
|
+
if (error instanceof import_decrypt_transport_response_error.DecryptTransportResponseError) {
|
|
6475
|
+
return encErr(
|
|
6476
|
+
encryptionError(
|
|
6477
|
+
error.status === 401 || error.status === 403 ? {
|
|
6478
|
+
code: "DECRYPT_DENIED",
|
|
6479
|
+
message: "Node denied decrypt request",
|
|
6480
|
+
...error.permissionHint === void 0 ? {} : { permissionHint: error.permissionHint }
|
|
6481
|
+
} : { code: "INVALID_RESPONSE", message: "Node decrypt request failed" }
|
|
6482
|
+
)
|
|
6483
|
+
);
|
|
6484
|
+
}
|
|
6216
6485
|
return encErr(
|
|
6217
6486
|
encryptionError({
|
|
6218
6487
|
code: "TRANSPORT_ERROR",
|
|
@@ -6243,14 +6512,20 @@ var EncryptionService = class extends BaseService {
|
|
|
6243
6512
|
} catch (error) {
|
|
6244
6513
|
return encErr(
|
|
6245
6514
|
encryptionError({
|
|
6246
|
-
code: "
|
|
6247
|
-
|
|
6515
|
+
code: "INVALID_RESPONSE",
|
|
6516
|
+
message: "Local decryption failed"
|
|
6248
6517
|
})
|
|
6249
6518
|
);
|
|
6250
6519
|
}
|
|
6251
6520
|
}
|
|
6521
|
+
assertActive() {
|
|
6522
|
+
this._config.assertActive?.();
|
|
6523
|
+
}
|
|
6252
6524
|
};
|
|
6253
6525
|
EncryptionService.serviceName = "encryption";
|
|
6526
|
+
|
|
6527
|
+
// src/encryption/index.ts
|
|
6528
|
+
var import_decrypt_transport_response_error3 = require("@tinycloud/sdk-services/internal/decrypt-transport-response-error");
|
|
6254
6529
|
// Annotate the CommonJS export names for ESM import in node:
|
|
6255
6530
|
0 && (module.exports = {
|
|
6256
6531
|
BaseService,
|
|
@@ -6262,6 +6537,7 @@ EncryptionService.serviceName = "encryption";
|
|
|
6262
6537
|
DEFAULT_SIGNED_READ_URL_EXPIRY_MS,
|
|
6263
6538
|
DataVaultService,
|
|
6264
6539
|
DatabaseHandle,
|
|
6540
|
+
DecryptTransportResponseError,
|
|
6265
6541
|
DuckDbAction,
|
|
6266
6542
|
DuckDbDatabaseHandle,
|
|
6267
6543
|
DuckDbService,
|
|
@@ -6345,6 +6621,8 @@ EncryptionService.serviceName = "encryption";
|
|
|
6345
6621
|
openWrappedKey,
|
|
6346
6622
|
parseAuthError,
|
|
6347
6623
|
parseNetworkId,
|
|
6624
|
+
parsePermissionHint,
|
|
6625
|
+
parsePermissionHintFromErrorText,
|
|
6348
6626
|
permissionDeniedError,
|
|
6349
6627
|
resolveSecretListPrefix,
|
|
6350
6628
|
resolveSecretPath,
|