@tinycloud/sdk-core 2.1.0-beta.0 → 2.1.0-beta.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +559 -13
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +579 -73
- package/dist/index.d.ts +579 -73
- package/dist/index.js +531 -13
- package/dist/index.js.map +1 -1
- package/package.json +4 -2
package/dist/index.js
CHANGED
|
@@ -493,6 +493,16 @@ var DelegationApiResponseSchema = z3.object({
|
|
|
493
493
|
/** CID of the created delegation */
|
|
494
494
|
cid: z3.string().optional()
|
|
495
495
|
});
|
|
496
|
+
var DelegatedResourceSchema = z3.object({
|
|
497
|
+
/** Short-form service name, e.g. "kv", "sql", "duckdb", "capabilities", "hooks". */
|
|
498
|
+
service: z3.string(),
|
|
499
|
+
/** Full space id string, e.g. "tinycloud:pkh:eip155:1:0x....:default". */
|
|
500
|
+
space: z3.string(),
|
|
501
|
+
/** Resource path; empty string when the resource URI had no path segment. */
|
|
502
|
+
path: z3.string(),
|
|
503
|
+
/** Full-URN ability strings, e.g. ["tinycloud.kv/get", "tinycloud.kv/put"]. */
|
|
504
|
+
actions: z3.array(z3.string())
|
|
505
|
+
});
|
|
496
506
|
var CreateDelegationWasmParamsSchema = z3.object({
|
|
497
507
|
/** The session containing delegation credentials */
|
|
498
508
|
session: z3.unknown().refine(
|
|
@@ -503,10 +513,23 @@ var CreateDelegationWasmParamsSchema = z3.object({
|
|
|
503
513
|
delegateDID: z3.string(),
|
|
504
514
|
/** Space ID this delegation applies to */
|
|
505
515
|
spaceId: z3.string(),
|
|
506
|
-
/**
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
516
|
+
/**
|
|
517
|
+
* Multi-resource abilities map: short-service → path → full-URN actions.
|
|
518
|
+
* Matches the shape accepted by `prepareSession`.
|
|
519
|
+
*
|
|
520
|
+
* Example:
|
|
521
|
+
* ```
|
|
522
|
+
* {
|
|
523
|
+
* kv: {
|
|
524
|
+
* "com.listen.app/": ["tinycloud.kv/get", "tinycloud.kv/put"]
|
|
525
|
+
* },
|
|
526
|
+
* sql: {
|
|
527
|
+
* "com.listen.app/data.sqlite": ["tinycloud.sql/read"]
|
|
528
|
+
* }
|
|
529
|
+
* }
|
|
530
|
+
* ```
|
|
531
|
+
*/
|
|
532
|
+
abilities: z3.record(z3.string(), z3.record(z3.string(), z3.array(z3.string()))),
|
|
510
533
|
/** Expiration time in seconds since Unix epoch */
|
|
511
534
|
expirationSecs: z3.number(),
|
|
512
535
|
/** Optional not-before time in seconds since Unix epoch */
|
|
@@ -519,12 +542,13 @@ var CreateDelegationWasmResultSchema = z3.object({
|
|
|
519
542
|
cid: z3.string(),
|
|
520
543
|
/** DID of the delegate */
|
|
521
544
|
delegateDID: z3.string(),
|
|
522
|
-
/** Resource path the delegation grants access to */
|
|
523
|
-
path: z3.string(),
|
|
524
|
-
/** Actions the delegation authorizes */
|
|
525
|
-
actions: z3.array(z3.string()),
|
|
526
545
|
/** Expiration time */
|
|
527
|
-
expiry: z3.coerce.date()
|
|
546
|
+
expiry: z3.coerce.date(),
|
|
547
|
+
/**
|
|
548
|
+
* All (service, space, path, actions) entries granted by this delegation.
|
|
549
|
+
* Always non-empty on success.
|
|
550
|
+
*/
|
|
551
|
+
resources: z3.array(DelegatedResourceSchema)
|
|
528
552
|
});
|
|
529
553
|
|
|
530
554
|
// src/spaces/spaces.schema.ts
|
|
@@ -2631,7 +2655,352 @@ function validateEncodedShareData(data) {
|
|
|
2631
2655
|
return { ok: true, data: result.data };
|
|
2632
2656
|
}
|
|
2633
2657
|
|
|
2658
|
+
// src/manifest.ts
|
|
2659
|
+
import ms from "ms";
|
|
2660
|
+
var ManifestValidationError = class extends Error {
|
|
2661
|
+
constructor(message) {
|
|
2662
|
+
super(`Manifest validation failed: ${message}`);
|
|
2663
|
+
this.name = "ManifestValidationError";
|
|
2664
|
+
}
|
|
2665
|
+
};
|
|
2666
|
+
var DEFAULT_EXPIRY = "30d";
|
|
2667
|
+
var DEFAULT_DEFAULTS = true;
|
|
2668
|
+
var SERVICE_SHORT_TO_LONG = Object.freeze({
|
|
2669
|
+
kv: "tinycloud.kv",
|
|
2670
|
+
sql: "tinycloud.sql",
|
|
2671
|
+
duckdb: "tinycloud.duckdb",
|
|
2672
|
+
capabilities: "tinycloud.capabilities",
|
|
2673
|
+
hooks: "tinycloud.hooks"
|
|
2674
|
+
});
|
|
2675
|
+
var SERVICE_LONG_TO_SHORT = Object.freeze(
|
|
2676
|
+
Object.fromEntries(
|
|
2677
|
+
Object.entries(SERVICE_SHORT_TO_LONG).map(([s, l]) => [l, s])
|
|
2678
|
+
)
|
|
2679
|
+
);
|
|
2680
|
+
var DEFAULT_STANDARD_ENTRIES = [
|
|
2681
|
+
{
|
|
2682
|
+
service: "tinycloud.kv",
|
|
2683
|
+
space: "default",
|
|
2684
|
+
path: "/",
|
|
2685
|
+
actions: ["get", "put", "del", "list", "metadata"]
|
|
2686
|
+
},
|
|
2687
|
+
{
|
|
2688
|
+
service: "tinycloud.sql",
|
|
2689
|
+
space: "default",
|
|
2690
|
+
path: "/",
|
|
2691
|
+
actions: ["read", "write"]
|
|
2692
|
+
},
|
|
2693
|
+
{
|
|
2694
|
+
service: "tinycloud.capabilities",
|
|
2695
|
+
space: "default",
|
|
2696
|
+
path: "/",
|
|
2697
|
+
actions: ["read"]
|
|
2698
|
+
}
|
|
2699
|
+
];
|
|
2700
|
+
var DEFAULT_ADMIN_ENTRIES = [
|
|
2701
|
+
{
|
|
2702
|
+
service: "tinycloud.kv",
|
|
2703
|
+
space: "default",
|
|
2704
|
+
path: "/",
|
|
2705
|
+
actions: ["get", "put", "del", "list", "metadata"]
|
|
2706
|
+
},
|
|
2707
|
+
{
|
|
2708
|
+
service: "tinycloud.sql",
|
|
2709
|
+
space: "default",
|
|
2710
|
+
path: "/",
|
|
2711
|
+
actions: ["read", "write", "ddl"]
|
|
2712
|
+
},
|
|
2713
|
+
{
|
|
2714
|
+
service: "tinycloud.capabilities",
|
|
2715
|
+
space: "default",
|
|
2716
|
+
path: "/",
|
|
2717
|
+
actions: ["read", "admin"]
|
|
2718
|
+
}
|
|
2719
|
+
];
|
|
2720
|
+
var DEFAULT_ALL_ENTRIES = [
|
|
2721
|
+
{
|
|
2722
|
+
service: "tinycloud.kv",
|
|
2723
|
+
space: "default",
|
|
2724
|
+
path: "/",
|
|
2725
|
+
actions: ["get", "put", "del", "list", "metadata"]
|
|
2726
|
+
},
|
|
2727
|
+
{
|
|
2728
|
+
service: "tinycloud.sql",
|
|
2729
|
+
space: "default",
|
|
2730
|
+
path: "/",
|
|
2731
|
+
actions: ["read", "write", "ddl"]
|
|
2732
|
+
},
|
|
2733
|
+
{
|
|
2734
|
+
service: "tinycloud.duckdb",
|
|
2735
|
+
space: "default",
|
|
2736
|
+
path: "/",
|
|
2737
|
+
actions: ["read", "write"]
|
|
2738
|
+
},
|
|
2739
|
+
{
|
|
2740
|
+
service: "tinycloud.capabilities",
|
|
2741
|
+
space: "default",
|
|
2742
|
+
path: "/",
|
|
2743
|
+
actions: ["read", "admin"]
|
|
2744
|
+
}
|
|
2745
|
+
];
|
|
2746
|
+
function parseExpiry(duration) {
|
|
2747
|
+
if (typeof duration !== "string" || duration.length === 0) {
|
|
2748
|
+
throw new ManifestValidationError(
|
|
2749
|
+
`expiry must be a non-empty duration string (got ${JSON.stringify(duration)})`
|
|
2750
|
+
);
|
|
2751
|
+
}
|
|
2752
|
+
const parsed = ms(
|
|
2753
|
+
duration
|
|
2754
|
+
);
|
|
2755
|
+
if (typeof parsed !== "number" || !Number.isFinite(parsed) || parsed <= 0) {
|
|
2756
|
+
throw new ManifestValidationError(
|
|
2757
|
+
`invalid expiry duration: ${JSON.stringify(duration)}`
|
|
2758
|
+
);
|
|
2759
|
+
}
|
|
2760
|
+
return parsed;
|
|
2761
|
+
}
|
|
2762
|
+
function expandActionShortNames(service, actions) {
|
|
2763
|
+
return actions.map((a) => {
|
|
2764
|
+
if (a.includes("/")) {
|
|
2765
|
+
return a;
|
|
2766
|
+
}
|
|
2767
|
+
return `${service}/${a}`;
|
|
2768
|
+
});
|
|
2769
|
+
}
|
|
2770
|
+
function applyPrefix(prefix, path, skipPrefix) {
|
|
2771
|
+
if (skipPrefix) {
|
|
2772
|
+
return path;
|
|
2773
|
+
}
|
|
2774
|
+
if (prefix === "") {
|
|
2775
|
+
return path;
|
|
2776
|
+
}
|
|
2777
|
+
if (path.startsWith("/")) {
|
|
2778
|
+
return `${prefix}${path}`;
|
|
2779
|
+
}
|
|
2780
|
+
return `${prefix}/${path}`;
|
|
2781
|
+
}
|
|
2782
|
+
async function loadManifest(url) {
|
|
2783
|
+
const fetchFn = globalThis.fetch;
|
|
2784
|
+
if (typeof fetchFn !== "function") {
|
|
2785
|
+
throw new ManifestValidationError(
|
|
2786
|
+
"loadManifest requires a global fetch; pass the manifest object directly on runtimes without fetch"
|
|
2787
|
+
);
|
|
2788
|
+
}
|
|
2789
|
+
const res = await fetchFn(url);
|
|
2790
|
+
if (!res.ok) {
|
|
2791
|
+
throw new ManifestValidationError(
|
|
2792
|
+
`failed to fetch manifest from ${url}: HTTP ${res.status}`
|
|
2793
|
+
);
|
|
2794
|
+
}
|
|
2795
|
+
const json = await res.json();
|
|
2796
|
+
return validateManifest(json);
|
|
2797
|
+
}
|
|
2798
|
+
function validateManifest(input) {
|
|
2799
|
+
if (input === null || typeof input !== "object") {
|
|
2800
|
+
throw new ManifestValidationError("manifest must be an object");
|
|
2801
|
+
}
|
|
2802
|
+
const m = input;
|
|
2803
|
+
if (typeof m.id !== "string" || m.id.length === 0) {
|
|
2804
|
+
throw new ManifestValidationError("manifest.id is required and must be a non-empty string");
|
|
2805
|
+
}
|
|
2806
|
+
if (typeof m.name !== "string" || m.name.length === 0) {
|
|
2807
|
+
throw new ManifestValidationError("manifest.name is required and must be a non-empty string");
|
|
2808
|
+
}
|
|
2809
|
+
if (m.expiry !== void 0) {
|
|
2810
|
+
parseExpiry(m.expiry);
|
|
2811
|
+
}
|
|
2812
|
+
if (m.permissions !== void 0) {
|
|
2813
|
+
if (!Array.isArray(m.permissions)) {
|
|
2814
|
+
throw new ManifestValidationError("manifest.permissions must be an array");
|
|
2815
|
+
}
|
|
2816
|
+
m.permissions.forEach(
|
|
2817
|
+
(p, i) => validatePermissionEntry(p, `permissions[${i}]`)
|
|
2818
|
+
);
|
|
2819
|
+
}
|
|
2820
|
+
if (m.delegations !== void 0) {
|
|
2821
|
+
if (!Array.isArray(m.delegations)) {
|
|
2822
|
+
throw new ManifestValidationError("manifest.delegations must be an array");
|
|
2823
|
+
}
|
|
2824
|
+
m.delegations.forEach((d, i) => {
|
|
2825
|
+
if (typeof d?.to !== "string" || d.to.length === 0) {
|
|
2826
|
+
throw new ManifestValidationError(
|
|
2827
|
+
`delegations[${i}].to is required and must be a non-empty DID string`
|
|
2828
|
+
);
|
|
2829
|
+
}
|
|
2830
|
+
if (d.expiry !== void 0) {
|
|
2831
|
+
parseExpiry(d.expiry);
|
|
2832
|
+
}
|
|
2833
|
+
if (!Array.isArray(d.permissions)) {
|
|
2834
|
+
throw new ManifestValidationError(
|
|
2835
|
+
`delegations[${i}].permissions must be an array`
|
|
2836
|
+
);
|
|
2837
|
+
}
|
|
2838
|
+
d.permissions.forEach(
|
|
2839
|
+
(p, j) => validatePermissionEntry(p, `delegations[${i}].permissions[${j}]`)
|
|
2840
|
+
);
|
|
2841
|
+
});
|
|
2842
|
+
}
|
|
2843
|
+
return m;
|
|
2844
|
+
}
|
|
2845
|
+
function validatePermissionEntry(p, path) {
|
|
2846
|
+
if (p === null || typeof p !== "object") {
|
|
2847
|
+
throw new ManifestValidationError(`${path} must be an object`);
|
|
2848
|
+
}
|
|
2849
|
+
const entry = p;
|
|
2850
|
+
if (typeof entry.service !== "string" || entry.service.length === 0) {
|
|
2851
|
+
throw new ManifestValidationError(`${path}.service is required`);
|
|
2852
|
+
}
|
|
2853
|
+
if (typeof entry.space !== "string" || entry.space.length === 0) {
|
|
2854
|
+
throw new ManifestValidationError(`${path}.space is required`);
|
|
2855
|
+
}
|
|
2856
|
+
if (typeof entry.path !== "string") {
|
|
2857
|
+
throw new ManifestValidationError(
|
|
2858
|
+
`${path}.path is required (use "" or "/" for root)`
|
|
2859
|
+
);
|
|
2860
|
+
}
|
|
2861
|
+
if (!Array.isArray(entry.actions) || entry.actions.length === 0) {
|
|
2862
|
+
throw new ManifestValidationError(
|
|
2863
|
+
`${path}.actions must be a non-empty array`
|
|
2864
|
+
);
|
|
2865
|
+
}
|
|
2866
|
+
if (entry.expiry !== void 0) {
|
|
2867
|
+
parseExpiry(entry.expiry);
|
|
2868
|
+
}
|
|
2869
|
+
}
|
|
2870
|
+
function normalizeDefaults(value) {
|
|
2871
|
+
if (value === void 0) {
|
|
2872
|
+
return DEFAULT_DEFAULTS;
|
|
2873
|
+
}
|
|
2874
|
+
if (typeof value === "boolean") {
|
|
2875
|
+
return value;
|
|
2876
|
+
}
|
|
2877
|
+
if (typeof value !== "string") {
|
|
2878
|
+
return true;
|
|
2879
|
+
}
|
|
2880
|
+
const normalized = value.trim().toLowerCase();
|
|
2881
|
+
if (normalized === "admin" || normalized === "all") {
|
|
2882
|
+
return normalized;
|
|
2883
|
+
}
|
|
2884
|
+
return true;
|
|
2885
|
+
}
|
|
2886
|
+
function defaultEntriesForTier(tier) {
|
|
2887
|
+
if (tier === false) {
|
|
2888
|
+
return [];
|
|
2889
|
+
}
|
|
2890
|
+
const source = tier === "admin" ? DEFAULT_ADMIN_ENTRIES : tier === "all" ? DEFAULT_ALL_ENTRIES : DEFAULT_STANDARD_ENTRIES;
|
|
2891
|
+
return source.map((e) => ({
|
|
2892
|
+
service: e.service,
|
|
2893
|
+
space: e.space,
|
|
2894
|
+
path: e.path,
|
|
2895
|
+
actions: [...e.actions]
|
|
2896
|
+
}));
|
|
2897
|
+
}
|
|
2898
|
+
function resolveManifest(input) {
|
|
2899
|
+
const manifest = validateManifest(input);
|
|
2900
|
+
const prefix = manifest.prefix !== void 0 ? manifest.prefix : manifest.id;
|
|
2901
|
+
const expiryMs = parseExpiry(manifest.expiry ?? DEFAULT_EXPIRY);
|
|
2902
|
+
const includePublicSpace = manifest.includePublicSpace ?? true;
|
|
2903
|
+
const tier = normalizeDefaults(manifest.defaults);
|
|
2904
|
+
const defaultEntries = defaultEntriesForTier(tier);
|
|
2905
|
+
const explicitEntries = manifest.permissions ?? [];
|
|
2906
|
+
const allEntries = [...defaultEntries, ...explicitEntries];
|
|
2907
|
+
const resources = allEntries.map(
|
|
2908
|
+
(entry) => resolveEntry(entry, prefix, expiryMs)
|
|
2909
|
+
);
|
|
2910
|
+
const additionalDelegates = (manifest.delegations ?? []).map((d) => ({
|
|
2911
|
+
did: d.to,
|
|
2912
|
+
name: d.name,
|
|
2913
|
+
expiryMs: parseExpiry(d.expiry ?? manifest.expiry ?? DEFAULT_EXPIRY),
|
|
2914
|
+
permissions: d.permissions.map(
|
|
2915
|
+
(entry) => resolveEntry(
|
|
2916
|
+
entry,
|
|
2917
|
+
prefix,
|
|
2918
|
+
parseExpiry(d.expiry ?? manifest.expiry ?? DEFAULT_EXPIRY)
|
|
2919
|
+
)
|
|
2920
|
+
)
|
|
2921
|
+
}));
|
|
2922
|
+
return {
|
|
2923
|
+
id: manifest.id,
|
|
2924
|
+
resources,
|
|
2925
|
+
expiryMs,
|
|
2926
|
+
includePublicSpace,
|
|
2927
|
+
additionalDelegates
|
|
2928
|
+
};
|
|
2929
|
+
}
|
|
2930
|
+
function resolveEntry(entry, prefix, _inheritedExpiryMs) {
|
|
2931
|
+
const resolvedPath = applyPrefix(
|
|
2932
|
+
prefix,
|
|
2933
|
+
entry.path,
|
|
2934
|
+
entry.skipPrefix === true
|
|
2935
|
+
);
|
|
2936
|
+
const resolvedActions = expandActionShortNames(entry.service, entry.actions);
|
|
2937
|
+
const entryExpiryMs = entry.expiry !== void 0 ? parseExpiry(entry.expiry) : void 0;
|
|
2938
|
+
return {
|
|
2939
|
+
service: entry.service,
|
|
2940
|
+
space: entry.space,
|
|
2941
|
+
path: resolvedPath,
|
|
2942
|
+
actions: resolvedActions,
|
|
2943
|
+
// Only populate `expiryMs` when the entry had its own expiry override.
|
|
2944
|
+
// When absent, callers use the parent (delegation or manifest) expiry
|
|
2945
|
+
// which is carried on ResolvedDelegate.expiryMs / ResolvedCapabilities.expiryMs.
|
|
2946
|
+
...entryExpiryMs !== void 0 ? { expiryMs: entryExpiryMs } : {}
|
|
2947
|
+
};
|
|
2948
|
+
}
|
|
2949
|
+
function resourceCapabilitiesToAbilitiesMap(resources) {
|
|
2950
|
+
const out = {};
|
|
2951
|
+
for (const r of resources) {
|
|
2952
|
+
const shortService = SERVICE_LONG_TO_SHORT[r.service];
|
|
2953
|
+
if (shortService === void 0) {
|
|
2954
|
+
throw new ManifestValidationError(
|
|
2955
|
+
`unknown service '${r.service}' \u2014 no short-form mapping. Known services: ${Object.keys(SERVICE_LONG_TO_SHORT).join(", ")}`
|
|
2956
|
+
);
|
|
2957
|
+
}
|
|
2958
|
+
if (out[shortService] === void 0) {
|
|
2959
|
+
out[shortService] = {};
|
|
2960
|
+
}
|
|
2961
|
+
const pathsMap = out[shortService];
|
|
2962
|
+
const existing = pathsMap[r.path];
|
|
2963
|
+
if (existing === void 0) {
|
|
2964
|
+
pathsMap[r.path] = [...r.actions];
|
|
2965
|
+
} else {
|
|
2966
|
+
const seen = new Set(existing);
|
|
2967
|
+
for (const action of r.actions) {
|
|
2968
|
+
if (!seen.has(action)) {
|
|
2969
|
+
existing.push(action);
|
|
2970
|
+
seen.add(action);
|
|
2971
|
+
}
|
|
2972
|
+
}
|
|
2973
|
+
}
|
|
2974
|
+
}
|
|
2975
|
+
return out;
|
|
2976
|
+
}
|
|
2977
|
+
function manifestAbilitiesUnion(resolved) {
|
|
2978
|
+
const all = [...resolved.resources];
|
|
2979
|
+
for (const delegate of resolved.additionalDelegates) {
|
|
2980
|
+
for (const perm of delegate.permissions) {
|
|
2981
|
+
all.push(perm);
|
|
2982
|
+
}
|
|
2983
|
+
}
|
|
2984
|
+
return resourceCapabilitiesToAbilitiesMap(all);
|
|
2985
|
+
}
|
|
2986
|
+
|
|
2634
2987
|
// src/delegations/SharingService.ts
|
|
2988
|
+
function inferShortServiceFromActionUrns(actions) {
|
|
2989
|
+
let short;
|
|
2990
|
+
for (const action of actions) {
|
|
2991
|
+
const slash = action.indexOf("/");
|
|
2992
|
+
if (slash === -1) return void 0;
|
|
2993
|
+
const longService = action.slice(0, slash);
|
|
2994
|
+
const candidate = SERVICE_LONG_TO_SHORT[longService];
|
|
2995
|
+
if (candidate === void 0) return void 0;
|
|
2996
|
+
if (short === void 0) {
|
|
2997
|
+
short = candidate;
|
|
2998
|
+
} else if (short !== candidate) {
|
|
2999
|
+
return void 0;
|
|
3000
|
+
}
|
|
3001
|
+
}
|
|
3002
|
+
return short;
|
|
3003
|
+
}
|
|
2635
3004
|
var DEFAULT_READ_ACTIONS = ["tinycloud.kv/get", "tinycloud.kv/metadata"];
|
|
2636
3005
|
var DEFAULT_EXPIRY_MS = 24 * 60 * 60 * 1e3;
|
|
2637
3006
|
var BASE64_PREFIX = "tc1:";
|
|
@@ -2975,12 +3344,34 @@ var SharingService = class {
|
|
|
2975
3344
|
}
|
|
2976
3345
|
if (this.createDelegationWasmFn) {
|
|
2977
3346
|
try {
|
|
3347
|
+
if (actions.length === 0) {
|
|
3348
|
+
return {
|
|
3349
|
+
ok: false,
|
|
3350
|
+
error: createError2(
|
|
3351
|
+
DelegationErrorCodes.VALIDATION_ERROR,
|
|
3352
|
+
"createDelegation requires at least one action"
|
|
3353
|
+
)
|
|
3354
|
+
};
|
|
3355
|
+
}
|
|
3356
|
+
const shortService = inferShortServiceFromActionUrns(actions);
|
|
3357
|
+
if (shortService === void 0) {
|
|
3358
|
+
return {
|
|
3359
|
+
ok: false,
|
|
3360
|
+
error: createError2(
|
|
3361
|
+
DelegationErrorCodes.VALIDATION_ERROR,
|
|
3362
|
+
`createDelegation: cannot infer service from actions ${JSON.stringify(actions)} \u2014 expected full URNs like "tinycloud.kv/get"`
|
|
3363
|
+
)
|
|
3364
|
+
};
|
|
3365
|
+
}
|
|
2978
3366
|
const wasmResult = this.createDelegationWasmFn({
|
|
2979
3367
|
session: this.session,
|
|
2980
3368
|
delegateDID,
|
|
2981
3369
|
spaceId: this.session.spaceId,
|
|
2982
|
-
|
|
2983
|
-
|
|
3370
|
+
abilities: {
|
|
3371
|
+
[shortService]: {
|
|
3372
|
+
[path]: [...actions]
|
|
3373
|
+
}
|
|
3374
|
+
},
|
|
2984
3375
|
expirationSecs: Math.floor(expiry.getTime() / 1e3)
|
|
2985
3376
|
});
|
|
2986
3377
|
const registerRes = await this.fetchFn(`${this.host}/delegate`, {
|
|
@@ -2999,12 +3390,22 @@ var SharingService = class {
|
|
|
2999
3390
|
)
|
|
3000
3391
|
};
|
|
3001
3392
|
}
|
|
3393
|
+
if (wasmResult.resources.length === 0) {
|
|
3394
|
+
return {
|
|
3395
|
+
ok: false,
|
|
3396
|
+
error: createError2(
|
|
3397
|
+
DelegationErrorCodes.CREATION_FAILED,
|
|
3398
|
+
"createDelegation WASM returned empty resources array for a single-entry request"
|
|
3399
|
+
)
|
|
3400
|
+
};
|
|
3401
|
+
}
|
|
3402
|
+
const primary = wasmResult.resources[0];
|
|
3002
3403
|
return {
|
|
3003
3404
|
cid: wasmResult.cid,
|
|
3004
3405
|
delegateDID: wasmResult.delegateDID,
|
|
3005
3406
|
spaceId: this.session.spaceId,
|
|
3006
|
-
path:
|
|
3007
|
-
actions:
|
|
3407
|
+
path: primary.path,
|
|
3408
|
+
actions: primary.actions,
|
|
3008
3409
|
expiry: wasmResult.expiry,
|
|
3009
3410
|
isRevoked: false,
|
|
3010
3411
|
authHeader: wasmResult.delegation,
|
|
@@ -3733,11 +4134,112 @@ async function checkNodeInfo(host, sdkProtocol, fetchFn = globalThis.fetch.bind(
|
|
|
3733
4134
|
quotaUrl: data.quota_url
|
|
3734
4135
|
};
|
|
3735
4136
|
}
|
|
4137
|
+
|
|
4138
|
+
// src/capabilities.ts
|
|
4139
|
+
var PermissionNotInManifestError = class extends Error {
|
|
4140
|
+
constructor(missing, granted) {
|
|
4141
|
+
super(
|
|
4142
|
+
`Requested capabilities exceed current session. Missing ${missing.length} entries.`
|
|
4143
|
+
);
|
|
4144
|
+
this.name = "PermissionNotInManifestError";
|
|
4145
|
+
this.missing = missing;
|
|
4146
|
+
this.granted = granted;
|
|
4147
|
+
}
|
|
4148
|
+
};
|
|
4149
|
+
var SessionExpiredError = class extends Error {
|
|
4150
|
+
constructor(expiredAt) {
|
|
4151
|
+
super(`Session expired at ${expiredAt.toISOString()}`);
|
|
4152
|
+
this.name = "SessionExpiredError";
|
|
4153
|
+
this.expiredAt = expiredAt;
|
|
4154
|
+
}
|
|
4155
|
+
};
|
|
4156
|
+
function isCapabilitySubset(requested, granted) {
|
|
4157
|
+
const missing = [];
|
|
4158
|
+
for (const req of requested) {
|
|
4159
|
+
const match = granted.find((g) => canonicalizeEntryMatches(req, g));
|
|
4160
|
+
if (match === void 0) {
|
|
4161
|
+
missing.push(cloneEntry(req));
|
|
4162
|
+
continue;
|
|
4163
|
+
}
|
|
4164
|
+
}
|
|
4165
|
+
return { subset: missing.length === 0, missing };
|
|
4166
|
+
}
|
|
4167
|
+
function canonicalizeEntryMatches(requested, granted) {
|
|
4168
|
+
if (requested.service !== granted.service) {
|
|
4169
|
+
return false;
|
|
4170
|
+
}
|
|
4171
|
+
if (requested.space !== granted.space) {
|
|
4172
|
+
return false;
|
|
4173
|
+
}
|
|
4174
|
+
if (!pathContains(granted.path, requested.path)) {
|
|
4175
|
+
return false;
|
|
4176
|
+
}
|
|
4177
|
+
const reqActions = new Set(
|
|
4178
|
+
expandActionShortNames(requested.service, requested.actions)
|
|
4179
|
+
);
|
|
4180
|
+
const grantedActions = new Set(
|
|
4181
|
+
expandActionShortNames(granted.service, granted.actions)
|
|
4182
|
+
);
|
|
4183
|
+
for (const a of reqActions) {
|
|
4184
|
+
if (!grantedActions.has(a)) {
|
|
4185
|
+
return false;
|
|
4186
|
+
}
|
|
4187
|
+
}
|
|
4188
|
+
return true;
|
|
4189
|
+
}
|
|
4190
|
+
function pathContains(grantedPath, requestedPath) {
|
|
4191
|
+
if (grantedPath === "" || grantedPath === "/") {
|
|
4192
|
+
return true;
|
|
4193
|
+
}
|
|
4194
|
+
if (grantedPath.endsWith("/")) {
|
|
4195
|
+
return requestedPath.startsWith(grantedPath);
|
|
4196
|
+
}
|
|
4197
|
+
return requestedPath === grantedPath;
|
|
4198
|
+
}
|
|
4199
|
+
function cloneEntry(entry) {
|
|
4200
|
+
return {
|
|
4201
|
+
service: entry.service,
|
|
4202
|
+
space: entry.space,
|
|
4203
|
+
path: entry.path,
|
|
4204
|
+
actions: [...entry.actions],
|
|
4205
|
+
...entry.skipPrefix !== void 0 ? { skipPrefix: entry.skipPrefix } : {},
|
|
4206
|
+
...entry.expiry !== void 0 ? { expiry: entry.expiry } : {}
|
|
4207
|
+
};
|
|
4208
|
+
}
|
|
4209
|
+
function parseRecapCapabilities(parseWasm, siwe) {
|
|
4210
|
+
const raw = parseWasm(siwe);
|
|
4211
|
+
if (!Array.isArray(raw)) {
|
|
4212
|
+
throw new Error(
|
|
4213
|
+
"parseRecapFromSiwe returned a non-array value; wasm binding may be out of sync"
|
|
4214
|
+
);
|
|
4215
|
+
}
|
|
4216
|
+
const normalized = raw.map((entry) => {
|
|
4217
|
+
const longService = SERVICE_SHORT_TO_LONG[entry.service] ?? // Unknown short names pass through. If the recap already contained a
|
|
4218
|
+
// long-form service (e.g. a future tinycloud-node version emits long
|
|
4219
|
+
// form directly), don't double-prefix.
|
|
4220
|
+
(entry.service.startsWith("tinycloud.") ? entry.service : `tinycloud.${entry.service}`);
|
|
4221
|
+
return {
|
|
4222
|
+
service: longService,
|
|
4223
|
+
space: entry.space,
|
|
4224
|
+
path: entry.path,
|
|
4225
|
+
actions: [...entry.actions]
|
|
4226
|
+
};
|
|
4227
|
+
});
|
|
4228
|
+
normalized.sort((a, b) => {
|
|
4229
|
+
if (a.space !== b.space) return a.space < b.space ? -1 : 1;
|
|
4230
|
+
if (a.service !== b.service) return a.service < b.service ? -1 : 1;
|
|
4231
|
+
if (a.path !== b.path) return a.path < b.path ? -1 : 1;
|
|
4232
|
+
return 0;
|
|
4233
|
+
});
|
|
4234
|
+
return normalized;
|
|
4235
|
+
}
|
|
3736
4236
|
export {
|
|
3737
4237
|
AutoApproveSpaceCreationHandler,
|
|
3738
4238
|
CapabilityKeyRegistry,
|
|
3739
4239
|
CapabilityKeyRegistryErrorCodes,
|
|
3740
4240
|
ClientSessionSchema,
|
|
4241
|
+
DEFAULT_DEFAULTS,
|
|
4242
|
+
DEFAULT_EXPIRY,
|
|
3741
4243
|
DataVaultService,
|
|
3742
4244
|
DatabaseHandle,
|
|
3743
4245
|
DelegationErrorCodes,
|
|
@@ -3749,11 +4251,16 @@ export {
|
|
|
3749
4251
|
ErrorCodes2 as ErrorCodes,
|
|
3750
4252
|
HooksService2 as HooksService,
|
|
3751
4253
|
KVService2 as KVService,
|
|
4254
|
+
ManifestValidationError,
|
|
4255
|
+
PermissionNotInManifestError,
|
|
3752
4256
|
PrefixedKVService,
|
|
3753
4257
|
ProtocolMismatchError,
|
|
4258
|
+
SERVICE_LONG_TO_SHORT,
|
|
4259
|
+
SERVICE_SHORT_TO_LONG,
|
|
3754
4260
|
SQLAction,
|
|
3755
4261
|
SQLService2 as SQLService,
|
|
3756
4262
|
ServiceContext2 as ServiceContext,
|
|
4263
|
+
SessionExpiredError,
|
|
3757
4264
|
SharingService,
|
|
3758
4265
|
SilentNotificationHandler,
|
|
3759
4266
|
SiweConfigSchema,
|
|
@@ -3767,6 +4274,7 @@ export {
|
|
|
3767
4274
|
VaultPublicSpaceKVActions,
|
|
3768
4275
|
VersionCheckError,
|
|
3769
4276
|
activateSessionWithHost,
|
|
4277
|
+
applyPrefix,
|
|
3770
4278
|
buildSpaceUri,
|
|
3771
4279
|
checkNodeInfo,
|
|
3772
4280
|
createCapabilityKeyRegistry,
|
|
@@ -3777,13 +4285,23 @@ export {
|
|
|
3777
4285
|
defaultSignStrategy,
|
|
3778
4286
|
defaultSpaceCreationHandler,
|
|
3779
4287
|
err4 as err,
|
|
4288
|
+
expandActionShortNames,
|
|
3780
4289
|
fetchPeerId,
|
|
4290
|
+
isCapabilitySubset,
|
|
4291
|
+
loadManifest,
|
|
3781
4292
|
makePublicSpaceId,
|
|
4293
|
+
manifestAbilitiesUnion,
|
|
4294
|
+
normalizeDefaults,
|
|
3782
4295
|
ok4 as ok,
|
|
4296
|
+
parseExpiry,
|
|
4297
|
+
parseRecapCapabilities,
|
|
3783
4298
|
parseSpaceUri,
|
|
4299
|
+
resolveManifest,
|
|
4300
|
+
resourceCapabilitiesToAbilitiesMap,
|
|
3784
4301
|
serviceError4 as serviceError,
|
|
3785
4302
|
submitHostDelegation,
|
|
3786
4303
|
validateClientSession,
|
|
4304
|
+
validateManifest,
|
|
3787
4305
|
validatePersistedSessionData
|
|
3788
4306
|
};
|
|
3789
4307
|
//# sourceMappingURL=index.js.map
|