@opendatalabs/vana-sdk 3.14.1 → 3.16.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.
Files changed (52) hide show
  1. package/README.md +52 -5
  2. package/dist/direct/access-request-client.cjs +31 -3
  3. package/dist/direct/access-request-client.cjs.map +1 -1
  4. package/dist/direct/access-request-client.d.ts +12 -1
  5. package/dist/direct/access-request-client.js +31 -3
  6. package/dist/direct/access-request-client.js.map +1 -1
  7. package/dist/direct/connect-flow.cjs +63 -6
  8. package/dist/direct/connect-flow.cjs.map +1 -1
  9. package/dist/direct/connect-flow.d.ts +30 -4
  10. package/dist/direct/connect-flow.js +63 -6
  11. package/dist/direct/connect-flow.js.map +1 -1
  12. package/dist/direct/controller.cjs +4 -1
  13. package/dist/direct/controller.cjs.map +1 -1
  14. package/dist/direct/controller.d.ts +11 -3
  15. package/dist/direct/controller.js +4 -1
  16. package/dist/direct/controller.js.map +1 -1
  17. package/dist/direct/types.cjs +28 -2
  18. package/dist/direct/types.cjs.map +1 -1
  19. package/dist/direct/types.d.ts +51 -0
  20. package/dist/direct/types.js +26 -1
  21. package/dist/direct/types.js.map +1 -1
  22. package/dist/direct/use-direct-vana-connect.cjs +3 -0
  23. package/dist/direct/use-direct-vana-connect.cjs.map +1 -1
  24. package/dist/direct/use-direct-vana-connect.js +3 -0
  25. package/dist/direct/use-direct-vana-connect.js.map +1 -1
  26. package/dist/direct/use-direct-vana-connect.test.d.ts +1 -0
  27. package/dist/index.browser.d.ts +1 -0
  28. package/dist/index.browser.js +167 -2
  29. package/dist/index.browser.js.map +3 -3
  30. package/dist/index.node.cjs +175 -2
  31. package/dist/index.node.cjs.map +3 -3
  32. package/dist/index.node.d.ts +2 -1
  33. package/dist/index.node.js +167 -2
  34. package/dist/index.node.js.map +3 -3
  35. package/dist/protocol/gateway.cjs +16 -2
  36. package/dist/protocol/gateway.cjs.map +1 -1
  37. package/dist/protocol/gateway.d.ts +2 -0
  38. package/dist/protocol/gateway.js +16 -2
  39. package/dist/protocol/gateway.js.map +1 -1
  40. package/dist/protocol/scope-actions.cjs +185 -0
  41. package/dist/protocol/scope-actions.cjs.map +1 -0
  42. package/dist/protocol/scope-actions.d.ts +145 -0
  43. package/dist/protocol/scope-actions.js +154 -0
  44. package/dist/protocol/scope-actions.js.map +1 -0
  45. package/dist/protocol/scope-actions.test.d.ts +1 -0
  46. package/dist/react.cjs.map +1 -1
  47. package/dist/react.d.ts +9 -2
  48. package/dist/react.js.map +1 -1
  49. package/dist/server.cjs.map +1 -1
  50. package/dist/server.d.ts +1 -1
  51. package/dist/server.js.map +1 -1
  52. package/package.json +1 -1
@@ -0,0 +1,185 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+ var scope_actions_exports = {};
20
+ __export(scope_actions_exports, {
21
+ InvalidScopeEntryError: () => InvalidScopeEntryError,
22
+ SCOPE_ACTIONS: () => SCOPE_ACTIONS,
23
+ formatScopeEntry: () => formatScopeEntry,
24
+ grantPermissions: () => grantPermissions,
25
+ hasAction: () => hasAction,
26
+ parseScopeEntry: () => parseScopeEntry,
27
+ permissionsToScopes: () => permissionsToScopes,
28
+ tryGrantPermissions: () => tryGrantPermissions
29
+ });
30
+ module.exports = __toCommonJS(scope_actions_exports);
31
+ var import_scopes = require("./scopes");
32
+ const SCOPE_ACTIONS = ["read", "write"];
33
+ class InvalidScopeEntryError extends Error {
34
+ /** The offending entry, verbatim (unknown because it may not be a string). */
35
+ entry;
36
+ constructor(entry, reason) {
37
+ super(`Invalid scope entry ${describeValue(entry)}: ${reason}`);
38
+ this.name = "InvalidScopeEntryError";
39
+ this.entry = entry;
40
+ }
41
+ }
42
+ const OPERATION_SEPARATOR = ":";
43
+ function describeValue(value) {
44
+ if (typeof value === "string") return JSON.stringify(value);
45
+ if (value === null) return "null";
46
+ return `[${typeof value}]`;
47
+ }
48
+ const OPERATION_BY_PREFIX = {
49
+ write: "write"
50
+ };
51
+ function assertScopePart(entry, scope) {
52
+ if (scope.length === 0) {
53
+ throw new InvalidScopeEntryError(entry, "scope part is empty");
54
+ }
55
+ if (scope.includes(OPERATION_SEPARATOR)) {
56
+ throw new InvalidScopeEntryError(
57
+ entry,
58
+ `scope part must not contain "${OPERATION_SEPARATOR}"`
59
+ );
60
+ }
61
+ }
62
+ function parseScopeEntry(entry) {
63
+ const raw = entry;
64
+ if (typeof raw !== "string") {
65
+ throw new InvalidScopeEntryError(raw, "entry must be a string");
66
+ }
67
+ const separatorIndex = entry.indexOf(OPERATION_SEPARATOR);
68
+ if (separatorIndex === -1) {
69
+ assertScopePart(entry, entry);
70
+ return { scope: entry, action: "read" };
71
+ }
72
+ const prefix = entry.slice(0, separatorIndex);
73
+ const scope = entry.slice(separatorIndex + 1);
74
+ const action = Object.hasOwn(OPERATION_BY_PREFIX, prefix) ? OPERATION_BY_PREFIX[prefix] : void 0;
75
+ if (action === void 0) {
76
+ throw new InvalidScopeEntryError(
77
+ entry,
78
+ `unknown operation "${prefix}" (known: ${Object.keys(OPERATION_BY_PREFIX).join(", ")}; read has no prefix)`
79
+ );
80
+ }
81
+ assertScopePart(entry, scope);
82
+ return { scope, action };
83
+ }
84
+ function formatScopeEntry(parsed) {
85
+ const { scope, action } = parsed;
86
+ assertScopePart(scope, scope);
87
+ if (action === "read") return scope;
88
+ const prefix = Object.entries(OPERATION_BY_PREFIX).find(
89
+ ([, candidate]) => candidate === action
90
+ )?.[0];
91
+ if (prefix === void 0) {
92
+ throw new InvalidScopeEntryError(
93
+ scope,
94
+ `unknown action ${describeValue(action)} (known: ${SCOPE_ACTIONS.join(", ")})`
95
+ );
96
+ }
97
+ return `${prefix}${OPERATION_SEPARATOR}${scope}`;
98
+ }
99
+ function compareScopes(a, b) {
100
+ if (a < b) return -1;
101
+ if (a > b) return 1;
102
+ return 0;
103
+ }
104
+ function sortActions(actions) {
105
+ const present = new Set(actions);
106
+ return SCOPE_ACTIONS.filter((action) => present.has(action));
107
+ }
108
+ function grantPermissions(scopes) {
109
+ const byScope = /* @__PURE__ */ new Map();
110
+ for (const entry of scopes) {
111
+ const { scope, action } = parseScopeEntry(entry);
112
+ let actions = byScope.get(scope);
113
+ if (actions === void 0) {
114
+ actions = /* @__PURE__ */ new Set();
115
+ byScope.set(scope, actions);
116
+ }
117
+ actions.add(action);
118
+ }
119
+ return [...byScope.keys()].sort(compareScopes).map((scope) => ({
120
+ scope,
121
+ actions: sortActions(byScope.get(scope) ?? [])
122
+ }));
123
+ }
124
+ function permissionsToScopes(permissions) {
125
+ const byScope = /* @__PURE__ */ new Map();
126
+ for (const { scope, actions } of permissions) {
127
+ let merged = byScope.get(scope);
128
+ if (merged === void 0) {
129
+ merged = /* @__PURE__ */ new Set();
130
+ byScope.set(scope, merged);
131
+ }
132
+ for (const action of actions) {
133
+ if (!SCOPE_ACTIONS.includes(action)) {
134
+ throw new InvalidScopeEntryError(
135
+ scope,
136
+ `unknown action ${describeValue(action)} (known: ${SCOPE_ACTIONS.join(", ")})`
137
+ );
138
+ }
139
+ merged.add(action);
140
+ }
141
+ }
142
+ const entries = [];
143
+ for (const scope of [...byScope.keys()].sort(compareScopes)) {
144
+ for (const action of sortActions(byScope.get(scope) ?? [])) {
145
+ entries.push(formatScopeEntry({ scope, action }));
146
+ }
147
+ }
148
+ return entries;
149
+ }
150
+ function hasAction(scopes, scope, action) {
151
+ if (scope.includes(OPERATION_SEPARATOR)) return false;
152
+ for (const entry of scopes) {
153
+ let parsed;
154
+ try {
155
+ parsed = parseScopeEntry(entry);
156
+ } catch (error) {
157
+ if (error instanceof InvalidScopeEntryError) continue;
158
+ throw error;
159
+ }
160
+ if (parsed.action === action && (0, import_scopes.scopeMatchesPattern)(scope, parsed.scope)) {
161
+ return true;
162
+ }
163
+ }
164
+ return false;
165
+ }
166
+ function tryGrantPermissions(scopes) {
167
+ try {
168
+ return grantPermissions(scopes);
169
+ } catch (error) {
170
+ if (error instanceof InvalidScopeEntryError) return void 0;
171
+ throw error;
172
+ }
173
+ }
174
+ // Annotate the CommonJS export names for ESM import in node:
175
+ 0 && (module.exports = {
176
+ InvalidScopeEntryError,
177
+ SCOPE_ACTIONS,
178
+ formatScopeEntry,
179
+ grantPermissions,
180
+ hasAction,
181
+ parseScopeEntry,
182
+ permissionsToScopes,
183
+ tryGrantPermissions
184
+ });
185
+ //# sourceMappingURL=scope-actions.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/protocol/scope-actions.ts"],"sourcesContent":["import { scopeMatchesPattern } from \"./scopes\";\n\n/**\n * Grant scope-entry grammar.\n *\n * A signed grant carries `scopes: string[]`. Each entry is\n * `[operation:]scope` - an optional lowercase ASCII operation prefix before\n * the first `:`, then a scope pattern (`*`, `{prefix}.*`, or an exact scope).\n * A missing prefix means read. `write:notes.entries` authorizes writing\n * `notes.entries` and nothing else; `notes.entries` authorizes reading it.\n *\n * The string form is the wire and storage detail: it is what the grantor\n * signs (EIP-712 `GrantRegistration.scopes`) and what the gateway stores\n * verbatim. The Personal Server is the sole interpreter, and this module is\n * the SDK-side mirror of that interpretation. Builders and consent UIs should\n * work with the grouped `{ scope, actions }` view (see\n * {@link grantPermissions}) and never construct or parse the strings by hand.\n *\n * Matching rules, pinned by the Personal Server policy\n * (personal-server-ts `packages/core/src/policy/data-write.ts` and\n * `data-read.ts`):\n * - the operation is compared exactly, case-sensitively;\n * - wildcards apply to the scope part only, via {@link scopeMatchesPattern};\n * - an entry whose operation is not recognised never authorizes anything.\n * The parser fails closed on it (throws) rather than treating it as read;\n * the matcher ({@link hasAction}) skips it, which is how the Personal\n * Server treats an entry it does not understand.\n */\n\n/** Operations the grammar defines today, in canonical (output) order. */\nexport const SCOPE_ACTIONS = [\"read\", \"write\"] as const;\n\n/** An operation a grant entry can authorize over a scope. */\nexport type ScopeAction = (typeof SCOPE_ACTIONS)[number];\n\n/** One grant entry, split into its operation and scope pattern. */\nexport interface ParsedScopeEntry {\n scope: string;\n action: ScopeAction;\n}\n\n/**\n * The grouped view of a grant's scope entries: one row per scope pattern\n * with every operation the grant authorizes over it.\n */\nexport interface GrantPermission {\n scope: string;\n actions: ScopeAction[];\n}\n\n/**\n * Thrown when a scope entry does not fit the grammar - an unknown or\n * malformed operation prefix, or an empty scope part.\n */\nexport class InvalidScopeEntryError extends Error {\n /** The offending entry, verbatim (unknown because it may not be a string). */\n readonly entry: unknown;\n\n constructor(entry: unknown, reason: string) {\n super(`Invalid scope entry ${describeValue(entry)}: ${reason}`);\n this.name = \"InvalidScopeEntryError\";\n this.entry = entry;\n }\n}\n\nconst OPERATION_SEPARATOR = \":\";\n\n// Render an untrusted value for an error message without ever throwing:\n// String() and JSON.stringify() both defer to the value's own toString /\n// toJSON, which a hostile JSON body can make throw.\nfunction describeValue(value: unknown): string {\n if (typeof value === \"string\") return JSON.stringify(value);\n if (value === null) return \"null\";\n return `[${typeof value}]`;\n}\n\n// The only operation that is ever written out. Read has no prefix, and\n// `read:` is NOT an alias for it: the Personal Server's read policy matches\n// entries verbatim, so a `read:x` entry would authorize nothing there, and\n// the parser must reject it for the same reason.\nconst OPERATION_BY_PREFIX: Readonly<Record<string, ScopeAction>> = {\n write: \"write\",\n};\n\nfunction assertScopePart(entry: string, scope: string): void {\n if (scope.length === 0) {\n throw new InvalidScopeEntryError(entry, \"scope part is empty\");\n }\n if (scope.includes(OPERATION_SEPARATOR)) {\n throw new InvalidScopeEntryError(\n entry,\n `scope part must not contain \"${OPERATION_SEPARATOR}\"`,\n );\n }\n}\n\n/**\n * Split one grant scope entry into its operation and scope pattern.\n *\n * - `notes.entries` parses as `{ scope: \"notes.entries\", action: \"read\" }`\n * - `write:notes.*` parses as `{ scope: \"notes.*\", action: \"write\" }`\n *\n * Fails closed: a non-string entry, or an entry whose operation prefix is\n * not recognised (including\n * `read:`, any uppercase or non-ASCII prefix, or a wildcard in the operation\n * position) throws {@link InvalidScopeEntryError} and is never treated as a\n * read entry. An empty scope part (`write:`) throws as well.\n *\n * @param entry - A single element of a grant's `scopes` array.\n * @returns The operation and the scope pattern it applies to.\n * @throws InvalidScopeEntryError when the entry does not fit the grammar.\n */\nexport function parseScopeEntry(entry: string): ParsedScopeEntry {\n // Grant bodies arrive from the network; a non-string element is a grammar\n // violation like any other, not a TypeError from indexOf.\n const raw: unknown = entry;\n if (typeof raw !== \"string\") {\n throw new InvalidScopeEntryError(raw, \"entry must be a string\");\n }\n const separatorIndex = entry.indexOf(OPERATION_SEPARATOR);\n if (separatorIndex === -1) {\n assertScopePart(entry, entry);\n return { scope: entry, action: \"read\" };\n }\n\n const prefix = entry.slice(0, separatorIndex);\n const scope = entry.slice(separatorIndex + 1);\n const action = Object.hasOwn(OPERATION_BY_PREFIX, prefix)\n ? OPERATION_BY_PREFIX[prefix]\n : undefined;\n if (action === undefined) {\n throw new InvalidScopeEntryError(\n entry,\n `unknown operation \"${prefix}\" (known: ${Object.keys(OPERATION_BY_PREFIX).join(\", \")}; read has no prefix)`,\n );\n }\n assertScopePart(entry, scope);\n return { scope, action };\n}\n\n/**\n * Inverse of {@link parseScopeEntry}: render one operation over one scope\n * pattern as a grant scope entry. Read has no prefix.\n *\n * @param parsed - The operation and scope pattern to encode.\n * @returns The wire-form entry, e.g. `write:notes.entries` or `notes.entries`.\n * @throws InvalidScopeEntryError when the action is unknown or the scope part\n * is empty or contains `:`.\n */\nexport function formatScopeEntry(parsed: ParsedScopeEntry): string {\n const { scope, action } = parsed;\n assertScopePart(scope, scope);\n if (action === \"read\") return scope;\n // Looked up rather than hard-coded so an action can never be emitted\n // without a prefix the parser accepts (and JS callers passing an unknown\n // action fail closed instead of producing a read entry).\n const prefix = Object.entries(OPERATION_BY_PREFIX).find(\n ([, candidate]) => candidate === action,\n )?.[0];\n if (prefix === undefined) {\n throw new InvalidScopeEntryError(\n scope,\n `unknown action ${describeValue(action)} (known: ${SCOPE_ACTIONS.join(\", \")})`,\n );\n }\n return `${prefix}${OPERATION_SEPARATOR}${scope}`;\n}\n\nfunction compareScopes(a: string, b: string): number {\n // Plain code-unit order: locale-independent, so the grouping is identical\n // on every runtime.\n if (a < b) return -1;\n if (a > b) return 1;\n return 0;\n}\n\nfunction sortActions(actions: Iterable<ScopeAction>): ScopeAction[] {\n const present = new Set(actions);\n return SCOPE_ACTIONS.filter((action) => present.has(action));\n}\n\n/**\n * Group a grant's scope entries into one `{ scope, actions }` row per scope\n * pattern - the view builders and consent UIs should render instead of the\n * raw strings.\n *\n * The result is canonical: rows are ordered by scope (code-unit order),\n * actions within a row follow {@link SCOPE_ACTIONS} order, and neither rows\n * nor actions repeat, whatever order or duplication the input had.\n *\n * Fails closed: if any entry does not fit the grammar this throws\n * {@link InvalidScopeEntryError} rather than silently dropping it, so a grant\n * carrying an operation this SDK does not know is never shown as narrower\n * than it is.\n *\n * @param scopes - A grant's `scopes` array, verbatim.\n * @returns The grouped, canonically ordered permissions.\n * @throws InvalidScopeEntryError when any entry does not fit the grammar.\n */\nexport function grantPermissions(scopes: readonly string[]): GrantPermission[] {\n const byScope = new Map<string, Set<ScopeAction>>();\n for (const entry of scopes) {\n const { scope, action } = parseScopeEntry(entry);\n let actions = byScope.get(scope);\n if (actions === undefined) {\n actions = new Set<ScopeAction>();\n byScope.set(scope, actions);\n }\n actions.add(action);\n }\n return [...byScope.keys()].sort(compareScopes).map((scope) => ({\n scope,\n actions: sortActions(byScope.get(scope) ?? []),\n }));\n}\n\n/**\n * Inverse of {@link grantPermissions}: flatten grouped permissions back into\n * the `string[]` form a grant is signed with.\n *\n * Output is canonical (scopes in code-unit order, read before write, no\n * duplicates), so `permissionsToScopes(grantPermissions(scopes))` is the\n * canonical form of `scopes`, and `grantPermissions(permissionsToScopes(p))`\n * is the canonical form of `p`. Rows with no actions contribute nothing; a\n * row with an action the grammar does not define throws rather than being\n * dropped.\n *\n * @param permissions - Grouped permissions, in any order, possibly repeating\n * a scope.\n * @returns The scope entries, one per (scope, action) pair.\n * @throws InvalidScopeEntryError when a scope or action does not fit the\n * grammar.\n */\nexport function permissionsToScopes(\n permissions: readonly GrantPermission[],\n): string[] {\n const byScope = new Map<string, Set<ScopeAction>>();\n for (const { scope, actions } of permissions) {\n let merged = byScope.get(scope);\n if (merged === undefined) {\n merged = new Set<ScopeAction>();\n byScope.set(scope, merged);\n }\n for (const action of actions) {\n if (!(SCOPE_ACTIONS as readonly string[]).includes(action)) {\n throw new InvalidScopeEntryError(\n scope,\n `unknown action ${describeValue(action)} (known: ${SCOPE_ACTIONS.join(\", \")})`,\n );\n }\n merged.add(action);\n }\n }\n const entries: string[] = [];\n for (const scope of [...byScope.keys()].sort(compareScopes)) {\n for (const action of sortActions(byScope.get(scope) ?? [])) {\n entries.push(formatScopeEntry({ scope, action }));\n }\n }\n return entries;\n}\n\n/**\n * Does this grant authorize `action` over `scope`?\n *\n * The scope part is matched with the SDK's scope wildcard matcher\n * ({@link scopeMatchesPattern}: `*`, `{prefix}.*`, or exact), the action\n * exactly. Entries that do not fit the grammar are skipped - they authorize\n * nothing, which is exactly how the Personal Server treats them - so a grant\n * that carries an operation this SDK does not know still answers correctly\n * for the operations it does.\n *\n * @param scopes - A grant's `scopes` array, verbatim.\n * @param scope - The concrete scope being requested. Never prefixed: a value\n * containing `:` is not a scope id and yields `false`.\n * @param action - The operation being requested.\n * @returns `true` if some entry grants `action` over a pattern covering\n * `scope`.\n */\nexport function hasAction(\n scopes: readonly string[],\n scope: string,\n action: ScopeAction,\n): boolean {\n // A requested scope is a concrete scope id and never carries a prefix; the\n // Personal Server rejects anything else with ScopeSchema before it ever\n // reaches its matcher, so answer the same way here instead of letting\n // `write:x` fall through to a `*` entry.\n if (scope.includes(OPERATION_SEPARATOR)) return false;\n for (const entry of scopes) {\n let parsed: ParsedScopeEntry;\n try {\n parsed = parseScopeEntry(entry);\n } catch (error) {\n if (error instanceof InvalidScopeEntryError) continue;\n throw error;\n }\n if (parsed.action === action && scopeMatchesPattern(scope, parsed.scope)) {\n return true;\n }\n }\n return false;\n}\n\n/**\n * {@link grantPermissions} for a grant record read back from the gateway:\n * returns `undefined` instead of throwing when the scope list carries an\n * entry this SDK version cannot interpret, so a grant with a newer operation\n * still loads (with `scopes` intact) rather than failing the whole read.\n *\n * @param scopes - A grant's `scopes` array, verbatim.\n * @returns The grouped permissions, or `undefined` if any entry is\n * uninterpretable.\n */\nexport function tryGrantPermissions(\n scopes: readonly string[],\n): GrantPermission[] | undefined {\n try {\n return grantPermissions(scopes);\n } catch (error) {\n if (error instanceof InvalidScopeEntryError) return undefined;\n throw error;\n }\n}\n"],"mappings":"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","names":[]}
@@ -0,0 +1,145 @@
1
+ /**
2
+ * Grant scope-entry grammar.
3
+ *
4
+ * A signed grant carries `scopes: string[]`. Each entry is
5
+ * `[operation:]scope` - an optional lowercase ASCII operation prefix before
6
+ * the first `:`, then a scope pattern (`*`, `{prefix}.*`, or an exact scope).
7
+ * A missing prefix means read. `write:notes.entries` authorizes writing
8
+ * `notes.entries` and nothing else; `notes.entries` authorizes reading it.
9
+ *
10
+ * The string form is the wire and storage detail: it is what the grantor
11
+ * signs (EIP-712 `GrantRegistration.scopes`) and what the gateway stores
12
+ * verbatim. The Personal Server is the sole interpreter, and this module is
13
+ * the SDK-side mirror of that interpretation. Builders and consent UIs should
14
+ * work with the grouped `{ scope, actions }` view (see
15
+ * {@link grantPermissions}) and never construct or parse the strings by hand.
16
+ *
17
+ * Matching rules, pinned by the Personal Server policy
18
+ * (personal-server-ts `packages/core/src/policy/data-write.ts` and
19
+ * `data-read.ts`):
20
+ * - the operation is compared exactly, case-sensitively;
21
+ * - wildcards apply to the scope part only, via {@link scopeMatchesPattern};
22
+ * - an entry whose operation is not recognised never authorizes anything.
23
+ * The parser fails closed on it (throws) rather than treating it as read;
24
+ * the matcher ({@link hasAction}) skips it, which is how the Personal
25
+ * Server treats an entry it does not understand.
26
+ */
27
+ /** Operations the grammar defines today, in canonical (output) order. */
28
+ export declare const SCOPE_ACTIONS: readonly ["read", "write"];
29
+ /** An operation a grant entry can authorize over a scope. */
30
+ export type ScopeAction = (typeof SCOPE_ACTIONS)[number];
31
+ /** One grant entry, split into its operation and scope pattern. */
32
+ export interface ParsedScopeEntry {
33
+ scope: string;
34
+ action: ScopeAction;
35
+ }
36
+ /**
37
+ * The grouped view of a grant's scope entries: one row per scope pattern
38
+ * with every operation the grant authorizes over it.
39
+ */
40
+ export interface GrantPermission {
41
+ scope: string;
42
+ actions: ScopeAction[];
43
+ }
44
+ /**
45
+ * Thrown when a scope entry does not fit the grammar - an unknown or
46
+ * malformed operation prefix, or an empty scope part.
47
+ */
48
+ export declare class InvalidScopeEntryError extends Error {
49
+ /** The offending entry, verbatim (unknown because it may not be a string). */
50
+ readonly entry: unknown;
51
+ constructor(entry: unknown, reason: string);
52
+ }
53
+ /**
54
+ * Split one grant scope entry into its operation and scope pattern.
55
+ *
56
+ * - `notes.entries` parses as `{ scope: "notes.entries", action: "read" }`
57
+ * - `write:notes.*` parses as `{ scope: "notes.*", action: "write" }`
58
+ *
59
+ * Fails closed: a non-string entry, or an entry whose operation prefix is
60
+ * not recognised (including
61
+ * `read:`, any uppercase or non-ASCII prefix, or a wildcard in the operation
62
+ * position) throws {@link InvalidScopeEntryError} and is never treated as a
63
+ * read entry. An empty scope part (`write:`) throws as well.
64
+ *
65
+ * @param entry - A single element of a grant's `scopes` array.
66
+ * @returns The operation and the scope pattern it applies to.
67
+ * @throws InvalidScopeEntryError when the entry does not fit the grammar.
68
+ */
69
+ export declare function parseScopeEntry(entry: string): ParsedScopeEntry;
70
+ /**
71
+ * Inverse of {@link parseScopeEntry}: render one operation over one scope
72
+ * pattern as a grant scope entry. Read has no prefix.
73
+ *
74
+ * @param parsed - The operation and scope pattern to encode.
75
+ * @returns The wire-form entry, e.g. `write:notes.entries` or `notes.entries`.
76
+ * @throws InvalidScopeEntryError when the action is unknown or the scope part
77
+ * is empty or contains `:`.
78
+ */
79
+ export declare function formatScopeEntry(parsed: ParsedScopeEntry): string;
80
+ /**
81
+ * Group a grant's scope entries into one `{ scope, actions }` row per scope
82
+ * pattern - the view builders and consent UIs should render instead of the
83
+ * raw strings.
84
+ *
85
+ * The result is canonical: rows are ordered by scope (code-unit order),
86
+ * actions within a row follow {@link SCOPE_ACTIONS} order, and neither rows
87
+ * nor actions repeat, whatever order or duplication the input had.
88
+ *
89
+ * Fails closed: if any entry does not fit the grammar this throws
90
+ * {@link InvalidScopeEntryError} rather than silently dropping it, so a grant
91
+ * carrying an operation this SDK does not know is never shown as narrower
92
+ * than it is.
93
+ *
94
+ * @param scopes - A grant's `scopes` array, verbatim.
95
+ * @returns The grouped, canonically ordered permissions.
96
+ * @throws InvalidScopeEntryError when any entry does not fit the grammar.
97
+ */
98
+ export declare function grantPermissions(scopes: readonly string[]): GrantPermission[];
99
+ /**
100
+ * Inverse of {@link grantPermissions}: flatten grouped permissions back into
101
+ * the `string[]` form a grant is signed with.
102
+ *
103
+ * Output is canonical (scopes in code-unit order, read before write, no
104
+ * duplicates), so `permissionsToScopes(grantPermissions(scopes))` is the
105
+ * canonical form of `scopes`, and `grantPermissions(permissionsToScopes(p))`
106
+ * is the canonical form of `p`. Rows with no actions contribute nothing; a
107
+ * row with an action the grammar does not define throws rather than being
108
+ * dropped.
109
+ *
110
+ * @param permissions - Grouped permissions, in any order, possibly repeating
111
+ * a scope.
112
+ * @returns The scope entries, one per (scope, action) pair.
113
+ * @throws InvalidScopeEntryError when a scope or action does not fit the
114
+ * grammar.
115
+ */
116
+ export declare function permissionsToScopes(permissions: readonly GrantPermission[]): string[];
117
+ /**
118
+ * Does this grant authorize `action` over `scope`?
119
+ *
120
+ * The scope part is matched with the SDK's scope wildcard matcher
121
+ * ({@link scopeMatchesPattern}: `*`, `{prefix}.*`, or exact), the action
122
+ * exactly. Entries that do not fit the grammar are skipped - they authorize
123
+ * nothing, which is exactly how the Personal Server treats them - so a grant
124
+ * that carries an operation this SDK does not know still answers correctly
125
+ * for the operations it does.
126
+ *
127
+ * @param scopes - A grant's `scopes` array, verbatim.
128
+ * @param scope - The concrete scope being requested. Never prefixed: a value
129
+ * containing `:` is not a scope id and yields `false`.
130
+ * @param action - The operation being requested.
131
+ * @returns `true` if some entry grants `action` over a pattern covering
132
+ * `scope`.
133
+ */
134
+ export declare function hasAction(scopes: readonly string[], scope: string, action: ScopeAction): boolean;
135
+ /**
136
+ * {@link grantPermissions} for a grant record read back from the gateway:
137
+ * returns `undefined` instead of throwing when the scope list carries an
138
+ * entry this SDK version cannot interpret, so a grant with a newer operation
139
+ * still loads (with `scopes` intact) rather than failing the whole read.
140
+ *
141
+ * @param scopes - A grant's `scopes` array, verbatim.
142
+ * @returns The grouped permissions, or `undefined` if any entry is
143
+ * uninterpretable.
144
+ */
145
+ export declare function tryGrantPermissions(scopes: readonly string[]): GrantPermission[] | undefined;
@@ -0,0 +1,154 @@
1
+ import { scopeMatchesPattern } from "./scopes.js";
2
+ const SCOPE_ACTIONS = ["read", "write"];
3
+ class InvalidScopeEntryError extends Error {
4
+ /** The offending entry, verbatim (unknown because it may not be a string). */
5
+ entry;
6
+ constructor(entry, reason) {
7
+ super(`Invalid scope entry ${describeValue(entry)}: ${reason}`);
8
+ this.name = "InvalidScopeEntryError";
9
+ this.entry = entry;
10
+ }
11
+ }
12
+ const OPERATION_SEPARATOR = ":";
13
+ function describeValue(value) {
14
+ if (typeof value === "string") return JSON.stringify(value);
15
+ if (value === null) return "null";
16
+ return `[${typeof value}]`;
17
+ }
18
+ const OPERATION_BY_PREFIX = {
19
+ write: "write"
20
+ };
21
+ function assertScopePart(entry, scope) {
22
+ if (scope.length === 0) {
23
+ throw new InvalidScopeEntryError(entry, "scope part is empty");
24
+ }
25
+ if (scope.includes(OPERATION_SEPARATOR)) {
26
+ throw new InvalidScopeEntryError(
27
+ entry,
28
+ `scope part must not contain "${OPERATION_SEPARATOR}"`
29
+ );
30
+ }
31
+ }
32
+ function parseScopeEntry(entry) {
33
+ const raw = entry;
34
+ if (typeof raw !== "string") {
35
+ throw new InvalidScopeEntryError(raw, "entry must be a string");
36
+ }
37
+ const separatorIndex = entry.indexOf(OPERATION_SEPARATOR);
38
+ if (separatorIndex === -1) {
39
+ assertScopePart(entry, entry);
40
+ return { scope: entry, action: "read" };
41
+ }
42
+ const prefix = entry.slice(0, separatorIndex);
43
+ const scope = entry.slice(separatorIndex + 1);
44
+ const action = Object.hasOwn(OPERATION_BY_PREFIX, prefix) ? OPERATION_BY_PREFIX[prefix] : void 0;
45
+ if (action === void 0) {
46
+ throw new InvalidScopeEntryError(
47
+ entry,
48
+ `unknown operation "${prefix}" (known: ${Object.keys(OPERATION_BY_PREFIX).join(", ")}; read has no prefix)`
49
+ );
50
+ }
51
+ assertScopePart(entry, scope);
52
+ return { scope, action };
53
+ }
54
+ function formatScopeEntry(parsed) {
55
+ const { scope, action } = parsed;
56
+ assertScopePart(scope, scope);
57
+ if (action === "read") return scope;
58
+ const prefix = Object.entries(OPERATION_BY_PREFIX).find(
59
+ ([, candidate]) => candidate === action
60
+ )?.[0];
61
+ if (prefix === void 0) {
62
+ throw new InvalidScopeEntryError(
63
+ scope,
64
+ `unknown action ${describeValue(action)} (known: ${SCOPE_ACTIONS.join(", ")})`
65
+ );
66
+ }
67
+ return `${prefix}${OPERATION_SEPARATOR}${scope}`;
68
+ }
69
+ function compareScopes(a, b) {
70
+ if (a < b) return -1;
71
+ if (a > b) return 1;
72
+ return 0;
73
+ }
74
+ function sortActions(actions) {
75
+ const present = new Set(actions);
76
+ return SCOPE_ACTIONS.filter((action) => present.has(action));
77
+ }
78
+ function grantPermissions(scopes) {
79
+ const byScope = /* @__PURE__ */ new Map();
80
+ for (const entry of scopes) {
81
+ const { scope, action } = parseScopeEntry(entry);
82
+ let actions = byScope.get(scope);
83
+ if (actions === void 0) {
84
+ actions = /* @__PURE__ */ new Set();
85
+ byScope.set(scope, actions);
86
+ }
87
+ actions.add(action);
88
+ }
89
+ return [...byScope.keys()].sort(compareScopes).map((scope) => ({
90
+ scope,
91
+ actions: sortActions(byScope.get(scope) ?? [])
92
+ }));
93
+ }
94
+ function permissionsToScopes(permissions) {
95
+ const byScope = /* @__PURE__ */ new Map();
96
+ for (const { scope, actions } of permissions) {
97
+ let merged = byScope.get(scope);
98
+ if (merged === void 0) {
99
+ merged = /* @__PURE__ */ new Set();
100
+ byScope.set(scope, merged);
101
+ }
102
+ for (const action of actions) {
103
+ if (!SCOPE_ACTIONS.includes(action)) {
104
+ throw new InvalidScopeEntryError(
105
+ scope,
106
+ `unknown action ${describeValue(action)} (known: ${SCOPE_ACTIONS.join(", ")})`
107
+ );
108
+ }
109
+ merged.add(action);
110
+ }
111
+ }
112
+ const entries = [];
113
+ for (const scope of [...byScope.keys()].sort(compareScopes)) {
114
+ for (const action of sortActions(byScope.get(scope) ?? [])) {
115
+ entries.push(formatScopeEntry({ scope, action }));
116
+ }
117
+ }
118
+ return entries;
119
+ }
120
+ function hasAction(scopes, scope, action) {
121
+ if (scope.includes(OPERATION_SEPARATOR)) return false;
122
+ for (const entry of scopes) {
123
+ let parsed;
124
+ try {
125
+ parsed = parseScopeEntry(entry);
126
+ } catch (error) {
127
+ if (error instanceof InvalidScopeEntryError) continue;
128
+ throw error;
129
+ }
130
+ if (parsed.action === action && scopeMatchesPattern(scope, parsed.scope)) {
131
+ return true;
132
+ }
133
+ }
134
+ return false;
135
+ }
136
+ function tryGrantPermissions(scopes) {
137
+ try {
138
+ return grantPermissions(scopes);
139
+ } catch (error) {
140
+ if (error instanceof InvalidScopeEntryError) return void 0;
141
+ throw error;
142
+ }
143
+ }
144
+ export {
145
+ InvalidScopeEntryError,
146
+ SCOPE_ACTIONS,
147
+ formatScopeEntry,
148
+ grantPermissions,
149
+ hasAction,
150
+ parseScopeEntry,
151
+ permissionsToScopes,
152
+ tryGrantPermissions
153
+ };
154
+ //# sourceMappingURL=scope-actions.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/protocol/scope-actions.ts"],"sourcesContent":["import { scopeMatchesPattern } from \"./scopes\";\n\n/**\n * Grant scope-entry grammar.\n *\n * A signed grant carries `scopes: string[]`. Each entry is\n * `[operation:]scope` - an optional lowercase ASCII operation prefix before\n * the first `:`, then a scope pattern (`*`, `{prefix}.*`, or an exact scope).\n * A missing prefix means read. `write:notes.entries` authorizes writing\n * `notes.entries` and nothing else; `notes.entries` authorizes reading it.\n *\n * The string form is the wire and storage detail: it is what the grantor\n * signs (EIP-712 `GrantRegistration.scopes`) and what the gateway stores\n * verbatim. The Personal Server is the sole interpreter, and this module is\n * the SDK-side mirror of that interpretation. Builders and consent UIs should\n * work with the grouped `{ scope, actions }` view (see\n * {@link grantPermissions}) and never construct or parse the strings by hand.\n *\n * Matching rules, pinned by the Personal Server policy\n * (personal-server-ts `packages/core/src/policy/data-write.ts` and\n * `data-read.ts`):\n * - the operation is compared exactly, case-sensitively;\n * - wildcards apply to the scope part only, via {@link scopeMatchesPattern};\n * - an entry whose operation is not recognised never authorizes anything.\n * The parser fails closed on it (throws) rather than treating it as read;\n * the matcher ({@link hasAction}) skips it, which is how the Personal\n * Server treats an entry it does not understand.\n */\n\n/** Operations the grammar defines today, in canonical (output) order. */\nexport const SCOPE_ACTIONS = [\"read\", \"write\"] as const;\n\n/** An operation a grant entry can authorize over a scope. */\nexport type ScopeAction = (typeof SCOPE_ACTIONS)[number];\n\n/** One grant entry, split into its operation and scope pattern. */\nexport interface ParsedScopeEntry {\n scope: string;\n action: ScopeAction;\n}\n\n/**\n * The grouped view of a grant's scope entries: one row per scope pattern\n * with every operation the grant authorizes over it.\n */\nexport interface GrantPermission {\n scope: string;\n actions: ScopeAction[];\n}\n\n/**\n * Thrown when a scope entry does not fit the grammar - an unknown or\n * malformed operation prefix, or an empty scope part.\n */\nexport class InvalidScopeEntryError extends Error {\n /** The offending entry, verbatim (unknown because it may not be a string). */\n readonly entry: unknown;\n\n constructor(entry: unknown, reason: string) {\n super(`Invalid scope entry ${describeValue(entry)}: ${reason}`);\n this.name = \"InvalidScopeEntryError\";\n this.entry = entry;\n }\n}\n\nconst OPERATION_SEPARATOR = \":\";\n\n// Render an untrusted value for an error message without ever throwing:\n// String() and JSON.stringify() both defer to the value's own toString /\n// toJSON, which a hostile JSON body can make throw.\nfunction describeValue(value: unknown): string {\n if (typeof value === \"string\") return JSON.stringify(value);\n if (value === null) return \"null\";\n return `[${typeof value}]`;\n}\n\n// The only operation that is ever written out. Read has no prefix, and\n// `read:` is NOT an alias for it: the Personal Server's read policy matches\n// entries verbatim, so a `read:x` entry would authorize nothing there, and\n// the parser must reject it for the same reason.\nconst OPERATION_BY_PREFIX: Readonly<Record<string, ScopeAction>> = {\n write: \"write\",\n};\n\nfunction assertScopePart(entry: string, scope: string): void {\n if (scope.length === 0) {\n throw new InvalidScopeEntryError(entry, \"scope part is empty\");\n }\n if (scope.includes(OPERATION_SEPARATOR)) {\n throw new InvalidScopeEntryError(\n entry,\n `scope part must not contain \"${OPERATION_SEPARATOR}\"`,\n );\n }\n}\n\n/**\n * Split one grant scope entry into its operation and scope pattern.\n *\n * - `notes.entries` parses as `{ scope: \"notes.entries\", action: \"read\" }`\n * - `write:notes.*` parses as `{ scope: \"notes.*\", action: \"write\" }`\n *\n * Fails closed: a non-string entry, or an entry whose operation prefix is\n * not recognised (including\n * `read:`, any uppercase or non-ASCII prefix, or a wildcard in the operation\n * position) throws {@link InvalidScopeEntryError} and is never treated as a\n * read entry. An empty scope part (`write:`) throws as well.\n *\n * @param entry - A single element of a grant's `scopes` array.\n * @returns The operation and the scope pattern it applies to.\n * @throws InvalidScopeEntryError when the entry does not fit the grammar.\n */\nexport function parseScopeEntry(entry: string): ParsedScopeEntry {\n // Grant bodies arrive from the network; a non-string element is a grammar\n // violation like any other, not a TypeError from indexOf.\n const raw: unknown = entry;\n if (typeof raw !== \"string\") {\n throw new InvalidScopeEntryError(raw, \"entry must be a string\");\n }\n const separatorIndex = entry.indexOf(OPERATION_SEPARATOR);\n if (separatorIndex === -1) {\n assertScopePart(entry, entry);\n return { scope: entry, action: \"read\" };\n }\n\n const prefix = entry.slice(0, separatorIndex);\n const scope = entry.slice(separatorIndex + 1);\n const action = Object.hasOwn(OPERATION_BY_PREFIX, prefix)\n ? OPERATION_BY_PREFIX[prefix]\n : undefined;\n if (action === undefined) {\n throw new InvalidScopeEntryError(\n entry,\n `unknown operation \"${prefix}\" (known: ${Object.keys(OPERATION_BY_PREFIX).join(\", \")}; read has no prefix)`,\n );\n }\n assertScopePart(entry, scope);\n return { scope, action };\n}\n\n/**\n * Inverse of {@link parseScopeEntry}: render one operation over one scope\n * pattern as a grant scope entry. Read has no prefix.\n *\n * @param parsed - The operation and scope pattern to encode.\n * @returns The wire-form entry, e.g. `write:notes.entries` or `notes.entries`.\n * @throws InvalidScopeEntryError when the action is unknown or the scope part\n * is empty or contains `:`.\n */\nexport function formatScopeEntry(parsed: ParsedScopeEntry): string {\n const { scope, action } = parsed;\n assertScopePart(scope, scope);\n if (action === \"read\") return scope;\n // Looked up rather than hard-coded so an action can never be emitted\n // without a prefix the parser accepts (and JS callers passing an unknown\n // action fail closed instead of producing a read entry).\n const prefix = Object.entries(OPERATION_BY_PREFIX).find(\n ([, candidate]) => candidate === action,\n )?.[0];\n if (prefix === undefined) {\n throw new InvalidScopeEntryError(\n scope,\n `unknown action ${describeValue(action)} (known: ${SCOPE_ACTIONS.join(\", \")})`,\n );\n }\n return `${prefix}${OPERATION_SEPARATOR}${scope}`;\n}\n\nfunction compareScopes(a: string, b: string): number {\n // Plain code-unit order: locale-independent, so the grouping is identical\n // on every runtime.\n if (a < b) return -1;\n if (a > b) return 1;\n return 0;\n}\n\nfunction sortActions(actions: Iterable<ScopeAction>): ScopeAction[] {\n const present = new Set(actions);\n return SCOPE_ACTIONS.filter((action) => present.has(action));\n}\n\n/**\n * Group a grant's scope entries into one `{ scope, actions }` row per scope\n * pattern - the view builders and consent UIs should render instead of the\n * raw strings.\n *\n * The result is canonical: rows are ordered by scope (code-unit order),\n * actions within a row follow {@link SCOPE_ACTIONS} order, and neither rows\n * nor actions repeat, whatever order or duplication the input had.\n *\n * Fails closed: if any entry does not fit the grammar this throws\n * {@link InvalidScopeEntryError} rather than silently dropping it, so a grant\n * carrying an operation this SDK does not know is never shown as narrower\n * than it is.\n *\n * @param scopes - A grant's `scopes` array, verbatim.\n * @returns The grouped, canonically ordered permissions.\n * @throws InvalidScopeEntryError when any entry does not fit the grammar.\n */\nexport function grantPermissions(scopes: readonly string[]): GrantPermission[] {\n const byScope = new Map<string, Set<ScopeAction>>();\n for (const entry of scopes) {\n const { scope, action } = parseScopeEntry(entry);\n let actions = byScope.get(scope);\n if (actions === undefined) {\n actions = new Set<ScopeAction>();\n byScope.set(scope, actions);\n }\n actions.add(action);\n }\n return [...byScope.keys()].sort(compareScopes).map((scope) => ({\n scope,\n actions: sortActions(byScope.get(scope) ?? []),\n }));\n}\n\n/**\n * Inverse of {@link grantPermissions}: flatten grouped permissions back into\n * the `string[]` form a grant is signed with.\n *\n * Output is canonical (scopes in code-unit order, read before write, no\n * duplicates), so `permissionsToScopes(grantPermissions(scopes))` is the\n * canonical form of `scopes`, and `grantPermissions(permissionsToScopes(p))`\n * is the canonical form of `p`. Rows with no actions contribute nothing; a\n * row with an action the grammar does not define throws rather than being\n * dropped.\n *\n * @param permissions - Grouped permissions, in any order, possibly repeating\n * a scope.\n * @returns The scope entries, one per (scope, action) pair.\n * @throws InvalidScopeEntryError when a scope or action does not fit the\n * grammar.\n */\nexport function permissionsToScopes(\n permissions: readonly GrantPermission[],\n): string[] {\n const byScope = new Map<string, Set<ScopeAction>>();\n for (const { scope, actions } of permissions) {\n let merged = byScope.get(scope);\n if (merged === undefined) {\n merged = new Set<ScopeAction>();\n byScope.set(scope, merged);\n }\n for (const action of actions) {\n if (!(SCOPE_ACTIONS as readonly string[]).includes(action)) {\n throw new InvalidScopeEntryError(\n scope,\n `unknown action ${describeValue(action)} (known: ${SCOPE_ACTIONS.join(\", \")})`,\n );\n }\n merged.add(action);\n }\n }\n const entries: string[] = [];\n for (const scope of [...byScope.keys()].sort(compareScopes)) {\n for (const action of sortActions(byScope.get(scope) ?? [])) {\n entries.push(formatScopeEntry({ scope, action }));\n }\n }\n return entries;\n}\n\n/**\n * Does this grant authorize `action` over `scope`?\n *\n * The scope part is matched with the SDK's scope wildcard matcher\n * ({@link scopeMatchesPattern}: `*`, `{prefix}.*`, or exact), the action\n * exactly. Entries that do not fit the grammar are skipped - they authorize\n * nothing, which is exactly how the Personal Server treats them - so a grant\n * that carries an operation this SDK does not know still answers correctly\n * for the operations it does.\n *\n * @param scopes - A grant's `scopes` array, verbatim.\n * @param scope - The concrete scope being requested. Never prefixed: a value\n * containing `:` is not a scope id and yields `false`.\n * @param action - The operation being requested.\n * @returns `true` if some entry grants `action` over a pattern covering\n * `scope`.\n */\nexport function hasAction(\n scopes: readonly string[],\n scope: string,\n action: ScopeAction,\n): boolean {\n // A requested scope is a concrete scope id and never carries a prefix; the\n // Personal Server rejects anything else with ScopeSchema before it ever\n // reaches its matcher, so answer the same way here instead of letting\n // `write:x` fall through to a `*` entry.\n if (scope.includes(OPERATION_SEPARATOR)) return false;\n for (const entry of scopes) {\n let parsed: ParsedScopeEntry;\n try {\n parsed = parseScopeEntry(entry);\n } catch (error) {\n if (error instanceof InvalidScopeEntryError) continue;\n throw error;\n }\n if (parsed.action === action && scopeMatchesPattern(scope, parsed.scope)) {\n return true;\n }\n }\n return false;\n}\n\n/**\n * {@link grantPermissions} for a grant record read back from the gateway:\n * returns `undefined` instead of throwing when the scope list carries an\n * entry this SDK version cannot interpret, so a grant with a newer operation\n * still loads (with `scopes` intact) rather than failing the whole read.\n *\n * @param scopes - A grant's `scopes` array, verbatim.\n * @returns The grouped permissions, or `undefined` if any entry is\n * uninterpretable.\n */\nexport function tryGrantPermissions(\n scopes: readonly string[],\n): GrantPermission[] | undefined {\n try {\n return grantPermissions(scopes);\n } catch (error) {\n if (error instanceof InvalidScopeEntryError) return undefined;\n throw error;\n }\n}\n"],"mappings":"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","names":[]}
@@ -0,0 +1 @@
1
+ export {};
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/react.ts"],"sourcesContent":["/**\n * React entry point for the Vana SDK direct Data Portability flow.\n *\n * @remarks\n * Exposes {@link useDirectVanaConnect} (and the underlying framework-agnostic\n * connect-flow store) for browser apps. This entry point is browser-safe and\n * imports nothing Node-only. `react` is a peer dependency.\n *\n * @example\n * ```tsx\n * \"use client\";\n * import { useDirectVanaConnect } from \"@opendatalabs/vana-sdk/react\";\n *\n * export function ConnectNotesButton() {\n * const connect = useDirectVanaConnect({\n * createRequest: () => fetch(\"/api/vana/request\", { method: \"POST\" }).then((r) => r.json()),\n * getStatus: (id) => fetch(`/api/vana/status?requestId=${id}`).then((r) => r.json()),\n * readResult: (id) => fetch(`/api/vana/data?requestId=${id}`).then((r) => r.json()),\n * });\n * const { state, start } = connect;\n * return (\n * <div>\n * <button disabled={state.type !== \"idle\"} onClick={start} type=\"button\">\n * {state.type === \"idle\" ? \"Connect Apple Notes\" : \"Connecting...\"}\n * </button>\n * {state.type === \"awaiting_approval\" && (\n * // Fallback link: if the browser blocked the approval popup, the user\n * // can still open it manually instead of the flow silently hanging.\n * <a href={state.request.approvalUrl} target=\"_blank\" rel=\"noreferrer\">\n * {state.popupBlocked ? \"Popup blocked — open approval\" : \"Open approval\"}\n * </a>\n * )}\n * </div>\n * );\n * }\n * ```\n *\n * @category Direct\n * @module react\n */\n\nexport {\n useDirectVanaConnect,\n type UseDirectVanaConnectOptions,\n type UseDirectVanaConnectResult,\n} from \"./direct/use-direct-vana-connect\";\n\n// Framework-agnostic store (usable without React).\nexport {\n createDirectConnectFlow,\n type ConnectWindow,\n type DirectConnectFlow,\n type DirectConnectState,\n type DirectConnectOptions,\n type DirectConnectTransports,\n} from \"./direct/connect-flow\";\n\n// Shared types useful when typing the transports.\nexport type {\n AccessRequest,\n AccessRequestStatus,\n AccessRequestStatusValue,\n ApprovedDataResult,\n} from \"./direct/types\";\n"],"mappings":";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAyCA,qCAIO;AAGP,0BAOO;","names":[]}
1
+ {"version":3,"sources":["../src/react.ts"],"sourcesContent":["/**\n * React entry point for the Vana SDK direct Data Portability flow.\n *\n * @remarks\n * Exposes {@link useDirectVanaConnect} (and the underlying framework-agnostic\n * connect-flow store) for browser apps. This entry point is browser-safe and\n * imports nothing Node-only. `react` is a peer dependency.\n *\n * @example\n * ```tsx\n * \"use client\";\n * import { useDirectVanaConnect } from \"@opendatalabs/vana-sdk/react\";\n *\n * export function ConnectNotesButton() {\n * const connect = useDirectVanaConnect({\n * createRequest: () => fetch(\"/api/vana/request\", { method: \"POST\" }).then((r) => r.json()),\n * getStatus: (id) => fetch(`/api/vana/status?requestId=${id}`).then((r) => r.json()),\n * readResult: (id) => fetch(`/api/vana/data?requestId=${id}`).then((r) => r.json()),\n * });\n * const { state, start } = connect;\n * return (\n * <div>\n * <button disabled={state.type !== \"idle\"} onClick={start} type=\"button\">\n * {state.type === \"idle\" ? \"Connect Apple Notes\" : \"Connecting...\"}\n * </button>\n * {state.type === \"awaiting_approval\" && (\n * // Fallback link: if the browser blocked the approval popup, the user\n * // can still open it manually instead of the flow silently hanging.\n * <a href={state.request.approvalUrl} target=\"_blank\" rel=\"noreferrer\">\n * {state.popupBlocked ? \"Popup blocked — open approval\" : \"Open approval\"}\n * </a>\n * )}\n * {state.type === \"ready_to_open\" && (\n * // Mobile deep Direct: render the HTTPS continuation as a primary tap.\n * // The SDK keeps polling in this tab; if the tab is lost, restart.\n * <a href={state.mobileContinuationUrl} target=\"_blank\" rel=\"noreferrer\">\n * Open Vana\n * </a>\n * )}\n * </div>\n * );\n * }\n * ```\n *\n * @category Direct\n * @module react\n */\n\nexport {\n useDirectVanaConnect,\n type UseDirectVanaConnectOptions,\n type UseDirectVanaConnectResult,\n} from \"./direct/use-direct-vana-connect\";\n\n// Framework-agnostic store (usable without React).\nexport {\n createDirectConnectFlow,\n type ConnectWindow,\n type DirectBrowserPlatform,\n type DirectBrowserPlatformPolicy,\n type DirectConnectFlow,\n type DirectConnectState,\n type DirectConnectOptions,\n type DirectConnectTransports,\n} from \"./direct/connect-flow\";\n\n// Shared types useful when typing the transports.\nexport type {\n ForegroundDelivery,\n AccessRequest,\n AccessRequestStatus,\n AccessRequestStatusValue,\n ApprovedDataResult,\n} from \"./direct/types\";\n"],"mappings":";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAgDA,qCAIO;AAGP,0BASO;","names":[]}
package/dist/react.d.ts CHANGED
@@ -30,6 +30,13 @@
30
30
  * {state.popupBlocked ? "Popup blocked — open approval" : "Open approval"}
31
31
  * </a>
32
32
  * )}
33
+ * {state.type === "ready_to_open" && (
34
+ * // Mobile deep Direct: render the HTTPS continuation as a primary tap.
35
+ * // The SDK keeps polling in this tab; if the tab is lost, restart.
36
+ * <a href={state.mobileContinuationUrl} target="_blank" rel="noreferrer">
37
+ * Open Vana
38
+ * </a>
39
+ * )}
33
40
  * </div>
34
41
  * );
35
42
  * }
@@ -39,5 +46,5 @@
39
46
  * @module react
40
47
  */
41
48
  export { useDirectVanaConnect, type UseDirectVanaConnectOptions, type UseDirectVanaConnectResult, } from "./direct/use-direct-vana-connect.js";
42
- export { createDirectConnectFlow, type ConnectWindow, type DirectConnectFlow, type DirectConnectState, type DirectConnectOptions, type DirectConnectTransports, } from "./direct/connect-flow.js";
43
- export type { AccessRequest, AccessRequestStatus, AccessRequestStatusValue, ApprovedDataResult, } from "./direct/types.js";
49
+ export { createDirectConnectFlow, type ConnectWindow, type DirectBrowserPlatform, type DirectBrowserPlatformPolicy, type DirectConnectFlow, type DirectConnectState, type DirectConnectOptions, type DirectConnectTransports, } from "./direct/connect-flow.js";
50
+ export type { ForegroundDelivery, AccessRequest, AccessRequestStatus, AccessRequestStatusValue, ApprovedDataResult, } from "./direct/types.js";
package/dist/react.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/react.ts"],"sourcesContent":["/**\n * React entry point for the Vana SDK direct Data Portability flow.\n *\n * @remarks\n * Exposes {@link useDirectVanaConnect} (and the underlying framework-agnostic\n * connect-flow store) for browser apps. This entry point is browser-safe and\n * imports nothing Node-only. `react` is a peer dependency.\n *\n * @example\n * ```tsx\n * \"use client\";\n * import { useDirectVanaConnect } from \"@opendatalabs/vana-sdk/react\";\n *\n * export function ConnectNotesButton() {\n * const connect = useDirectVanaConnect({\n * createRequest: () => fetch(\"/api/vana/request\", { method: \"POST\" }).then((r) => r.json()),\n * getStatus: (id) => fetch(`/api/vana/status?requestId=${id}`).then((r) => r.json()),\n * readResult: (id) => fetch(`/api/vana/data?requestId=${id}`).then((r) => r.json()),\n * });\n * const { state, start } = connect;\n * return (\n * <div>\n * <button disabled={state.type !== \"idle\"} onClick={start} type=\"button\">\n * {state.type === \"idle\" ? \"Connect Apple Notes\" : \"Connecting...\"}\n * </button>\n * {state.type === \"awaiting_approval\" && (\n * // Fallback link: if the browser blocked the approval popup, the user\n * // can still open it manually instead of the flow silently hanging.\n * <a href={state.request.approvalUrl} target=\"_blank\" rel=\"noreferrer\">\n * {state.popupBlocked ? \"Popup blocked — open approval\" : \"Open approval\"}\n * </a>\n * )}\n * </div>\n * );\n * }\n * ```\n *\n * @category Direct\n * @module react\n */\n\nexport {\n useDirectVanaConnect,\n type UseDirectVanaConnectOptions,\n type UseDirectVanaConnectResult,\n} from \"./direct/use-direct-vana-connect\";\n\n// Framework-agnostic store (usable without React).\nexport {\n createDirectConnectFlow,\n type ConnectWindow,\n type DirectConnectFlow,\n type DirectConnectState,\n type DirectConnectOptions,\n type DirectConnectTransports,\n} from \"./direct/connect-flow\";\n\n// Shared types useful when typing the transports.\nexport type {\n AccessRequest,\n AccessRequestStatus,\n AccessRequestStatusValue,\n ApprovedDataResult,\n} from \"./direct/types\";\n"],"mappings":"AAyCA;AAAA,EACE;AAAA,OAGK;AAGP;AAAA,EACE;AAAA,OAMK;","names":[]}
1
+ {"version":3,"sources":["../src/react.ts"],"sourcesContent":["/**\n * React entry point for the Vana SDK direct Data Portability flow.\n *\n * @remarks\n * Exposes {@link useDirectVanaConnect} (and the underlying framework-agnostic\n * connect-flow store) for browser apps. This entry point is browser-safe and\n * imports nothing Node-only. `react` is a peer dependency.\n *\n * @example\n * ```tsx\n * \"use client\";\n * import { useDirectVanaConnect } from \"@opendatalabs/vana-sdk/react\";\n *\n * export function ConnectNotesButton() {\n * const connect = useDirectVanaConnect({\n * createRequest: () => fetch(\"/api/vana/request\", { method: \"POST\" }).then((r) => r.json()),\n * getStatus: (id) => fetch(`/api/vana/status?requestId=${id}`).then((r) => r.json()),\n * readResult: (id) => fetch(`/api/vana/data?requestId=${id}`).then((r) => r.json()),\n * });\n * const { state, start } = connect;\n * return (\n * <div>\n * <button disabled={state.type !== \"idle\"} onClick={start} type=\"button\">\n * {state.type === \"idle\" ? \"Connect Apple Notes\" : \"Connecting...\"}\n * </button>\n * {state.type === \"awaiting_approval\" && (\n * // Fallback link: if the browser blocked the approval popup, the user\n * // can still open it manually instead of the flow silently hanging.\n * <a href={state.request.approvalUrl} target=\"_blank\" rel=\"noreferrer\">\n * {state.popupBlocked ? \"Popup blocked — open approval\" : \"Open approval\"}\n * </a>\n * )}\n * {state.type === \"ready_to_open\" && (\n * // Mobile deep Direct: render the HTTPS continuation as a primary tap.\n * // The SDK keeps polling in this tab; if the tab is lost, restart.\n * <a href={state.mobileContinuationUrl} target=\"_blank\" rel=\"noreferrer\">\n * Open Vana\n * </a>\n * )}\n * </div>\n * );\n * }\n * ```\n *\n * @category Direct\n * @module react\n */\n\nexport {\n useDirectVanaConnect,\n type UseDirectVanaConnectOptions,\n type UseDirectVanaConnectResult,\n} from \"./direct/use-direct-vana-connect\";\n\n// Framework-agnostic store (usable without React).\nexport {\n createDirectConnectFlow,\n type ConnectWindow,\n type DirectBrowserPlatform,\n type DirectBrowserPlatformPolicy,\n type DirectConnectFlow,\n type DirectConnectState,\n type DirectConnectOptions,\n type DirectConnectTransports,\n} from \"./direct/connect-flow\";\n\n// Shared types useful when typing the transports.\nexport type {\n ForegroundDelivery,\n AccessRequest,\n AccessRequestStatus,\n AccessRequestStatusValue,\n ApprovedDataResult,\n} from \"./direct/types\";\n"],"mappings":"AAgDA;AAAA,EACE;AAAA,OAGK;AAGP;AAAA,EACE;AAAA,OAQK;","names":[]}
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/server.ts"],"sourcesContent":["/**\n * Server entry point for the Vana SDK direct Data Portability flow.\n *\n * @remarks\n * Exposes {@link createDirectDataController} and its supporting types/errors.\n * This is a Node/server entry point — it owns the app private key and must never\n * be imported into browser code.\n *\n * @example\n * ```typescript\n * import { createDirectDataController } from \"@opendatalabs/vana-sdk/server\";\n *\n * export const vana = createDirectDataController({\n * env: process.env.VANA_ENV === \"dev\" ? \"dev\" : \"production\",\n * appPrivateKey: process.env.VANA_APP_PRIVATE_KEY!,\n * app: { id: \"notes-lens\", name: \"Notes Lens\", homepageUrl: process.env.VANA_APP_URL! },\n * source: \"icloud_notes\",\n * scopes: [\"icloud_notes.notes\"],\n * });\n * ```\n *\n * @category Direct\n * @module server\n */\n\nexport {\n createDirectDataController,\n type DirectDataController,\n type DirectDataControllerConfig,\n type DirectEscrowConfig,\n} from \"./direct/controller\";\n\n// Lower-level building blocks (advanced use / custom transports).\nexport {\n createDefaultAccessRequestClient,\n buildApprovalUrl,\n type DefaultAccessRequestClientOptions,\n type FetchLike,\n} from \"./direct/access-request-client\";\nexport {\n buildPersonalServerDataReadRequest,\n readPersonalServerData,\n parsePersonalServerPaymentRequired,\n dataPathForScope,\n type PersonalServerDataReadRequest,\n type PersonalServerReadResult,\n type PersonalServerFetch,\n type PersonalServerTransportRetryOptions,\n type FetchResponseLike,\n} from \"./direct/personal-server-read\";\n// Escrow-backed payment (built on protocol/escrow).\nexport {\n authorizeEscrowPayment,\n authorizeGrantPayment,\n buildEscrowPaymentHeader,\n buildGrantPaymentHeader,\n paymentResponseMetadataFromHeader,\n toDirectPaymentReceipt,\n toDirectFeeBreakdown,\n createDefaultNonceSource,\n DATA_ACCESS_OP_TYPE,\n GRANT_OP_TYPE,\n type EscrowPaymentConfig,\n type EscrowPaymentHeaderConfig,\n type SignTypedDataFn,\n type PaymentNonceSource,\n} from \"./direct/escrow-payment\";\nexport {\n getDirectEndpoints,\n PRODUCTION_ENDPOINTS,\n DEV_ENDPOINTS,\n} from \"./direct/endpoints\";\n\n// Errors\nexport {\n DirectConfigError,\n AccessNotApprovedError,\n ScopeNotApprovedError,\n PersonalServerReadError,\n PaymentRequiredError,\n} from \"./direct/errors\";\n\n// Shared types\nexport type {\n DirectEnv,\n DirectNetwork,\n DirectAppConfig,\n AppIdentity,\n DirectServiceEndpoints,\n AccessRequest,\n AccessRequestStatus,\n AccessRequestStatusValue,\n ApprovedDataResult,\n MultiScopeDataResult,\n AccessRequestClient,\n DirectOpTypeValue,\n PersonalServerDataAccessPaymentOperation,\n PersonalServerGrantPaymentOperation,\n PersonalServerPaymentOperation,\n PersonalServerPaymentRequired,\n DirectPaymentReceipt,\n DirectPaymentResponseMetadata,\n DirectFeeBreakdown,\n} from \"./direct/types\";\n\n// Op-type vocabulary constant.\nexport { DirectOpType } from \"./direct/types\";\n"],"mappings":";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAyBA,wBAKO;AAGP,mCAKO;AACP,kCAUO;AAEP,4BAeO;AACP,uBAIO;AAGP,oBAMO;AA0BP,mBAA6B;","names":[]}
1
+ {"version":3,"sources":["../src/server.ts"],"sourcesContent":["/**\n * Server entry point for the Vana SDK direct Data Portability flow.\n *\n * @remarks\n * Exposes {@link createDirectDataController} and its supporting types/errors.\n * This is a Node/server entry point — it owns the app private key and must never\n * be imported into browser code.\n *\n * @example\n * ```typescript\n * import { createDirectDataController } from \"@opendatalabs/vana-sdk/server\";\n *\n * export const vana = createDirectDataController({\n * env: process.env.VANA_ENV === \"dev\" ? \"dev\" : \"production\",\n * appPrivateKey: process.env.VANA_APP_PRIVATE_KEY!,\n * app: { id: \"notes-lens\", name: \"Notes Lens\", homepageUrl: process.env.VANA_APP_URL! },\n * source: \"icloud_notes\",\n * scopes: [\"icloud_notes.notes\"],\n * });\n * ```\n *\n * @category Direct\n * @module server\n */\n\nexport {\n createDirectDataController,\n type DirectDataController,\n type DirectDataControllerConfig,\n type DirectEscrowConfig,\n} from \"./direct/controller\";\n\n// Lower-level building blocks (advanced use / custom transports).\nexport {\n createDefaultAccessRequestClient,\n buildApprovalUrl,\n type DefaultAccessRequestClientOptions,\n type FetchLike,\n} from \"./direct/access-request-client\";\nexport {\n buildPersonalServerDataReadRequest,\n readPersonalServerData,\n parsePersonalServerPaymentRequired,\n dataPathForScope,\n type PersonalServerDataReadRequest,\n type PersonalServerReadResult,\n type PersonalServerFetch,\n type PersonalServerTransportRetryOptions,\n type FetchResponseLike,\n} from \"./direct/personal-server-read\";\n// Escrow-backed payment (built on protocol/escrow).\nexport {\n authorizeEscrowPayment,\n authorizeGrantPayment,\n buildEscrowPaymentHeader,\n buildGrantPaymentHeader,\n paymentResponseMetadataFromHeader,\n toDirectPaymentReceipt,\n toDirectFeeBreakdown,\n createDefaultNonceSource,\n DATA_ACCESS_OP_TYPE,\n GRANT_OP_TYPE,\n type EscrowPaymentConfig,\n type EscrowPaymentHeaderConfig,\n type SignTypedDataFn,\n type PaymentNonceSource,\n} from \"./direct/escrow-payment\";\nexport {\n getDirectEndpoints,\n PRODUCTION_ENDPOINTS,\n DEV_ENDPOINTS,\n} from \"./direct/endpoints\";\n\n// Errors\nexport {\n DirectConfigError,\n AccessNotApprovedError,\n ScopeNotApprovedError,\n PersonalServerReadError,\n PaymentRequiredError,\n} from \"./direct/errors\";\n\n// Shared types\nexport type {\n DirectEnv,\n DirectNetwork,\n DirectAppConfig,\n ForegroundDelivery,\n AppIdentity,\n DirectServiceEndpoints,\n AccessRequest,\n AccessRequestStatus,\n AccessRequestStatusValue,\n ApprovedDataResult,\n MultiScopeDataResult,\n AccessRequestClient,\n DirectOpTypeValue,\n PersonalServerDataAccessPaymentOperation,\n PersonalServerGrantPaymentOperation,\n PersonalServerPaymentOperation,\n PersonalServerPaymentRequired,\n DirectPaymentReceipt,\n DirectPaymentResponseMetadata,\n DirectFeeBreakdown,\n} from \"./direct/types\";\n\n// Op-type vocabulary constant.\nexport { DirectOpType } from \"./direct/types\";\n"],"mappings":";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAyBA,wBAKO;AAGP,mCAKO;AACP,kCAUO;AAEP,4BAeO;AACP,uBAIO;AAGP,oBAMO;AA2BP,mBAA6B;","names":[]}
package/dist/server.d.ts CHANGED
@@ -28,5 +28,5 @@ export { buildPersonalServerDataReadRequest, readPersonalServerData, parsePerson
28
28
  export { authorizeEscrowPayment, authorizeGrantPayment, buildEscrowPaymentHeader, buildGrantPaymentHeader, paymentResponseMetadataFromHeader, toDirectPaymentReceipt, toDirectFeeBreakdown, createDefaultNonceSource, DATA_ACCESS_OP_TYPE, GRANT_OP_TYPE, type EscrowPaymentConfig, type EscrowPaymentHeaderConfig, type SignTypedDataFn, type PaymentNonceSource, } from "./direct/escrow-payment.js";
29
29
  export { getDirectEndpoints, PRODUCTION_ENDPOINTS, DEV_ENDPOINTS, } from "./direct/endpoints.js";
30
30
  export { DirectConfigError, AccessNotApprovedError, ScopeNotApprovedError, PersonalServerReadError, PaymentRequiredError, } from "./direct/errors.js";
31
- export type { DirectEnv, DirectNetwork, DirectAppConfig, AppIdentity, DirectServiceEndpoints, AccessRequest, AccessRequestStatus, AccessRequestStatusValue, ApprovedDataResult, MultiScopeDataResult, AccessRequestClient, DirectOpTypeValue, PersonalServerDataAccessPaymentOperation, PersonalServerGrantPaymentOperation, PersonalServerPaymentOperation, PersonalServerPaymentRequired, DirectPaymentReceipt, DirectPaymentResponseMetadata, DirectFeeBreakdown, } from "./direct/types.js";
31
+ export type { DirectEnv, DirectNetwork, DirectAppConfig, ForegroundDelivery, AppIdentity, DirectServiceEndpoints, AccessRequest, AccessRequestStatus, AccessRequestStatusValue, ApprovedDataResult, MultiScopeDataResult, AccessRequestClient, DirectOpTypeValue, PersonalServerDataAccessPaymentOperation, PersonalServerGrantPaymentOperation, PersonalServerPaymentOperation, PersonalServerPaymentRequired, DirectPaymentReceipt, DirectPaymentResponseMetadata, DirectFeeBreakdown, } from "./direct/types.js";
32
32
  export { DirectOpType } from "./direct/types.js";