@matanetwork/sovereign-id 0.1.0 → 0.1.1

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 (4) hide show
  1. package/LICENSE +21 -21
  2. package/package.json +8 -5
  3. package/src/index.d.ts +229 -216
  4. package/src/index.js +132 -14
package/LICENSE CHANGED
@@ -1,21 +1,21 @@
1
- MIT License
2
-
3
- Copyright (c) 2026 MATA Network
4
-
5
- Permission is hereby granted, free of charge, to any person obtaining a copy
6
- of this software and associated documentation files (the "Software"), to deal
7
- in the Software without restriction, including without limitation the rights
8
- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
- copies of the Software, and to permit persons to whom the Software is
10
- furnished to do so, subject to the following conditions:
11
-
12
- The above copyright notice and this permission notice shall be included in all
13
- copies or substantial portions of the Software.
14
-
15
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
- SOFTWARE.
1
+ MIT License
2
+
3
+ Copyright (c) 2026 MATA Network
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@matanetwork/sovereign-id",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
4
4
  "description": "Page-side SDK for MATA Sovereign ID — the permissionless self-issued identity protocol (mID). Probes for the MATA browser extension or native app, dispatches sign-in requests, and resolves with a signed JWT.",
5
5
  "type": "module",
6
6
  "main": "src/index.js",
@@ -37,13 +37,16 @@
37
37
  "engines": {
38
38
  "node": ">=18"
39
39
  },
40
- "homepage": "https://github.com/mata-network/mata/tree/main/packages/mata-sovereign-id-sdk#readme",
40
+ "homepage": "https://github.com/Remade-With-Rust/sovereign-id/tree/main/packages/sovereign-id#readme",
41
41
  "bugs": {
42
- "url": "https://github.com/mata-network/mata/issues"
42
+ "url": "https://github.com/Remade-With-Rust/sovereign-id/issues"
43
43
  },
44
44
  "repository": {
45
45
  "type": "git",
46
- "url": "git+https://github.com/mata-network/mata.git",
47
- "directory": "packages/mata-sovereign-id-sdk"
46
+ "url": "git+https://github.com/Remade-With-Rust/sovereign-id.git",
47
+ "directory": "packages/sovereign-id"
48
+ },
49
+ "publishConfig": {
50
+ "access": "public"
48
51
  }
49
52
  }
package/src/index.d.ts CHANGED
@@ -1,216 +1,229 @@
1
- /**
2
- * Type declarations for @matanetwork/sovereign-id.
3
- *
4
- * The runtime is pure JS (no TypeScript build step). This `.d.ts`
5
- * exists so consumers using TypeScript get full type-checking +
6
- * IDE intellisense.
7
- */
8
-
9
- // ─── Wire protocol constants ───────────────────────────────────────────────
10
-
11
- export const WINDOW_MID_GLOBAL: '__mata_mid__';
12
- export const MESSAGE_DISCRIMINATOR: '__mata_mid_v1';
13
- export const KIND_SIGN_IN_REQUEST: 'sign_in_request';
14
- export const KIND_SIGN_IN_RESPONSE: 'sign_in_response';
15
- export const URL_SCHEME: 'mata-mid';
16
- export const SCHEME_PATH_REQUEST: 'request';
17
- export const QUERY_PARAM_PAYLOAD: 'payload';
18
- export const FRAGMENT_KEY_RESPONSE: 'mid_response';
19
- export const PROTOCOL_VERSION: 1;
20
-
21
- // ─── Standard error codes ──────────────────────────────────────────────────
22
-
23
- export const ERR_USER_DENIED: 'user_denied';
24
- export const ERR_ORIGIN_MISMATCH: 'origin_mismatch';
25
- export const ERR_INVALID_REQUEST: 'invalid_request';
26
- export const ERR_WALLET_UNAVAILABLE: 'wallet_unavailable';
27
- export const ERR_REQUIRED_CLAIM_UNAVAILABLE: 'required_claim_unavailable';
28
- export const ERR_INTERNAL: 'internal_error';
29
- export const ERR_NO_WALLET_INSTALLED: 'no_wallet_installed';
30
- export const ERR_TIMEOUT: 'timeout';
31
- export const ERR_UPSELL_CANCELED: 'upsell_canceled';
32
-
33
- export type ErrorCode =
34
- | typeof ERR_USER_DENIED
35
- | typeof ERR_ORIGIN_MISMATCH
36
- | typeof ERR_INVALID_REQUEST
37
- | typeof ERR_WALLET_UNAVAILABLE
38
- | typeof ERR_REQUIRED_CLAIM_UNAVAILABLE
39
- | typeof ERR_INTERNAL
40
- | typeof ERR_NO_WALLET_INSTALLED
41
- | typeof ERR_TIMEOUT
42
- | typeof ERR_UPSELL_CANCELED;
43
-
44
- // ─── Request / response types ──────────────────────────────────────────────
45
-
46
- export interface CustomClaim {
47
- optional: true;
48
- description?: string;
49
- }
50
-
51
- export interface SignInRequest {
52
- /** The RP's bare origin, e.g. `"https://acme.com"`. */
53
- rpOrigin: string;
54
- /** RP-issued single-use nonce; appears in the JWT for replay defense. */
55
- nonce: string;
56
- /** Claim catalog the wallet should disclose. */
57
- claims: {
58
- /** Claims that MUST be approved; denial blocks sign-in. */
59
- required: string[];
60
- /** Claims the user can include or skip. */
61
- optional?: string[];
62
- /** Arbitrary custom keys from the user's profile_kv (v0: ignored). */
63
- custom?: Record<string, CustomClaim>;
64
- };
65
- }
66
-
67
- export interface SignInOptions {
68
- /** Hard request timeout. Default: 120 seconds. */
69
- timeoutMs?: number;
70
- /**
71
- * URL the native app's response will redirect to. Default:
72
- * `window.location.href`. Only honored when the SDK falls through
73
- * to the native-app deep link.
74
- */
75
- nativeAppCallback?: string;
76
- /**
77
- * Whether to show the install upsell modal when no wallet is
78
- * detected on the user's device. Default: `true`. Set to `false`
79
- * to get a raw `ERR_NO_WALLET_INSTALLED` rejection and handle
80
- * the upsell UI yourself.
81
- */
82
- installUpsell?: boolean;
83
- /**
84
- * Referral code attributed to signups that flow through the install
85
- * upsell. Default: the hostname extracted from `rpOrigin` (e.g.
86
- * `"acme.com"`), so RPs get attribution by default without any
87
- * extra wiring. Pass `null` to opt out of attribution entirely, or
88
- * a custom string to override (e.g. a configured MATA referral
89
- * code your team uses).
90
- *
91
- * Stamped onto the install CTA and the "create your account" link
92
- * inside the upsell modal as `?ref=<code>`, following the existing
93
- * MATA referral convention captured by my.mata.network's welcome
94
- * view + signup form.
95
- */
96
- ref?: string | null;
97
- }
98
-
99
- /**
100
- * One of the CTAs the install upsell can present. Pick by browser/OS.
101
- * Exposed for RPs that want to render their own upsell UI from the
102
- * same recommendation engine.
103
- */
104
- export interface InstallCta {
105
- /** Button text (e.g. `"Install MATA for Chrome"`). */
106
- label: string;
107
- /** Where the button navigates to. */
108
- url: string;
109
- /** Sub-text under the CTA (e.g. `"Opens the Chrome Web Store in a new tab"`). */
110
- hint: string;
111
- }
112
-
113
- export interface InstallUpsellOptions {
114
- /** Shown prominently as the requesting RP. */
115
- rpOrigin: string;
116
- /**
117
- * Inversion-of-control hook so the SDK's `hasExtension()` is used
118
- * in production and tests can stub it.
119
- */
120
- hasExtensionFn?: () => boolean;
121
- /** Override the auto-picked CTA. */
122
- cta?: InstallCta;
123
- /** Default 1000 ms. */
124
- pollIntervalMs?: number;
125
- /**
126
- * Referral code attributed to signups that flow through this
127
- * upsell. Default: hostname of `rpOrigin`. Pass `null` to disable.
128
- * See `SignInOptions.ref` for the full attribution model.
129
- */
130
- ref?: string | null;
131
- }
132
-
133
- export type InstallUpsellResult = 'installed' | 'canceled';
134
-
135
- export interface SignInSuccess {
136
- /** JWS compact-form mID token. */
137
- jwt: string;
138
- /** Which surface produced the JWT. */
139
- surface: 'extension' | 'native_app';
140
- }
141
-
142
- export class SignInError extends Error {
143
- constructor(code: ErrorCode, message: string);
144
- readonly code: ErrorCode;
145
- }
146
-
147
- // ─── Public functions ──────────────────────────────────────────────────────
148
-
149
- export function signIn(
150
- request: SignInRequest,
151
- options?: SignInOptions
152
- ): Promise<SignInSuccess>;
153
-
154
- export function hasExtension(): boolean;
155
-
156
- /** Internal exposed for SDK consumers that need to roll their own. */
157
- export function generateRequestId(): string;
158
- /** Internal — exposed for the verifier package + tests. */
159
- export function base64UrlEncode(str: string): string;
160
- /** Internal exposed for the verifier package + tests. */
161
- export function base64UrlDecode(b64url: string): string;
162
-
163
- /**
164
- * Pick the right install CTA for the user's browser / OS. Auto-called
165
- * by `signIn()` when the upsell fires; exposed so RPs that opted out
166
- * of the inline modal can use the same recommendation engine in
167
- * their own UI.
168
- */
169
- export function pickInstallCta(): InstallCta;
170
-
171
- /**
172
- * Extract the default referral code from an RP origin — the bare
173
- * hostname (e.g. `"https://acme.com"` `"acme.com"`). Exposed so
174
- * RPs can preview what attribution string they'd get without
175
- * actually triggering the upsell.
176
- *
177
- * Returns `null` for malformed origins.
178
- */
179
- export function defaultRefFromOrigin(rpOrigin: string): string | null;
180
-
181
- /**
182
- * Render the install upsell overlay and resolve when the user either
183
- * cancels or completes the install. Auto-called by `signIn()` on
184
- * `ERR_NO_WALLET_INSTALLED`; exposed for RPs who set
185
- * `installUpsell: false` and want to invoke it on their own
186
- * conditions.
187
- */
188
- export function showInstallUpsell(
189
- options: InstallUpsellOptions
190
- ): Promise<InstallUpsellResult>;
191
-
192
- /**
193
- * Resume a sign-in that was interrupted by a page reload during the
194
- * install upsell.
195
- *
196
- * Call once at app boot ideally as early as possible — so a user
197
- * who installed MATA and reloaded sees their sign-in continue without
198
- * a visible flicker through the logged-out state.
199
- *
200
- * - Returns `{jwt, surface}` if a pending request was found, the
201
- * extension is now installed, and the resumed sign-in completed.
202
- * - Returns `null` if no resume is pending, the stash is stale, or
203
- * the extension is still missing (the user reloaded before
204
- * actually installing).
205
- * - Rejects with `SignInError` when a pending request exists and the
206
- * extension is present but the sign-in itself failed (`user_denied`,
207
- * `timeout`, etc.). Same error shape as `signIn()`.
208
- */
209
- export function resumePendingSignIn(): Promise<SignInSuccess | null>;
210
-
211
- /**
212
- * Imperatively drop any pending resume entry. Useful when the RP
213
- * has navigated to a different sign-in flow that supersedes the
214
- * pending mID request.
215
- */
216
- export function clearPendingSignIn(): void;
1
+ /**
2
+ * Type declarations for @matanetwork/sovereign-id.
3
+ *
4
+ * The runtime is pure JS (no TypeScript build step). This `.d.ts`
5
+ * exists so consumers using TypeScript get full type-checking +
6
+ * IDE intellisense.
7
+ */
8
+
9
+ // ─── Wire protocol constants ───────────────────────────────────────────────
10
+
11
+ export const WINDOW_MID_GLOBAL: '__mata_mid__';
12
+ export const MESSAGE_DISCRIMINATOR: '__mata_mid_v1';
13
+ export const KIND_SIGN_IN_REQUEST: 'sign_in_request';
14
+ export const KIND_SIGN_IN_RESPONSE: 'sign_in_response';
15
+ export const URL_SCHEME: 'mata-mid';
16
+ export const SCHEME_PATH_REQUEST: 'request';
17
+ export const QUERY_PARAM_PAYLOAD: 'payload';
18
+ export const FRAGMENT_KEY_RESPONSE: 'mid_response';
19
+ export const PROTOCOL_VERSION: 1;
20
+
21
+ // ─── Standard error codes ──────────────────────────────────────────────────
22
+
23
+ export const ERR_USER_DENIED: 'user_denied';
24
+ export const ERR_ORIGIN_MISMATCH: 'origin_mismatch';
25
+ export const ERR_INVALID_REQUEST: 'invalid_request';
26
+ export const ERR_WALLET_UNAVAILABLE: 'wallet_unavailable';
27
+ export const ERR_REQUIRED_CLAIM_UNAVAILABLE: 'required_claim_unavailable';
28
+ export const ERR_INTERNAL: 'internal_error';
29
+ export const ERR_NO_WALLET_INSTALLED: 'no_wallet_installed';
30
+ export const ERR_TIMEOUT: 'timeout';
31
+ export const ERR_UPSELL_CANCELED: 'upsell_canceled';
32
+
33
+ export type ErrorCode =
34
+ | typeof ERR_USER_DENIED
35
+ | typeof ERR_ORIGIN_MISMATCH
36
+ | typeof ERR_INVALID_REQUEST
37
+ | typeof ERR_WALLET_UNAVAILABLE
38
+ | typeof ERR_REQUIRED_CLAIM_UNAVAILABLE
39
+ | typeof ERR_INTERNAL
40
+ | typeof ERR_NO_WALLET_INSTALLED
41
+ | typeof ERR_TIMEOUT
42
+ | typeof ERR_UPSELL_CANCELED;
43
+
44
+ // ─── Request / response types ──────────────────────────────────────────────
45
+
46
+ export interface CustomClaim {
47
+ optional: true;
48
+ description?: string;
49
+ }
50
+
51
+ export interface SignInRequest {
52
+ /** The RP's bare origin, e.g. `"https://acme.com"`. */
53
+ rpOrigin: string;
54
+ /** RP-issued single-use nonce; appears in the JWT for replay defense. */
55
+ nonce: string;
56
+ /** Claim catalog the wallet should disclose. */
57
+ claims: {
58
+ /** Claims that MUST be approved; denial blocks sign-in. */
59
+ required: string[];
60
+ /** Claims the user can include or skip. */
61
+ optional?: string[];
62
+ /** Arbitrary custom keys from the user's profile_kv (v0: ignored). */
63
+ custom?: Record<string, CustomClaim>;
64
+ };
65
+ }
66
+
67
+ export interface SignInOptions {
68
+ /** Hard request timeout. Default: 120 seconds. */
69
+ timeoutMs?: number;
70
+ /**
71
+ * URL the native app's response will redirect to. Default:
72
+ * `window.location.href`. Only honored when the SDK falls through
73
+ * to the native-app deep link. For a native host, set this to your
74
+ * app's own custom scheme (e.g. `"myapp://mid-callback"`) so the
75
+ * wallet's callback routes back to your app instead of a browser tab.
76
+ */
77
+ nativeAppCallback?: string;
78
+ /**
79
+ * Set `true` when this SDK runs inside a native webview
80
+ * (Dioxus/Tauri/wry/Electron) rather than a real browser tab. Skips
81
+ * the browser-only "no wallet installed" visibility heuristic — which
82
+ * would false-positive because a native webview never backgrounds when
83
+ * the OS opens the deep link — and relies on the hard `timeoutMs`
84
+ * instead. Pair with `nativeAppCallback` and have your host inject the
85
+ * returned `#mid_response=...` fragment back into the webview so the
86
+ * SDK's existing resume listener resolves the `signIn()` promise.
87
+ */
88
+ nativeHost?: boolean;
89
+ /**
90
+ * Whether to show the install upsell modal when no wallet is
91
+ * detected on the user's device. Default: `true`. Set to `false`
92
+ * to get a raw `ERR_NO_WALLET_INSTALLED` rejection and handle
93
+ * the upsell UI yourself.
94
+ */
95
+ installUpsell?: boolean;
96
+ /**
97
+ * Referral code attributed to signups that flow through the install
98
+ * upsell. Default: the hostname extracted from `rpOrigin` (e.g.
99
+ * `"acme.com"`), so RPs get attribution by default without any
100
+ * extra wiring. Pass `null` to opt out of attribution entirely, or
101
+ * a custom string to override (e.g. a configured MATA referral
102
+ * code your team uses).
103
+ *
104
+ * Stamped onto the install CTA and the "create your account" link
105
+ * inside the upsell modal as `?ref=<code>`, following the existing
106
+ * MATA referral convention captured by my.mata.network's welcome
107
+ * view + signup form.
108
+ */
109
+ ref?: string | null;
110
+ }
111
+
112
+ /**
113
+ * One of the CTAs the install upsell can present. Pick by browser/OS.
114
+ * Exposed for RPs that want to render their own upsell UI from the
115
+ * same recommendation engine.
116
+ */
117
+ export interface InstallCta {
118
+ /** Button text (e.g. `"Install MATA for Chrome"`). */
119
+ label: string;
120
+ /** Where the button navigates to. */
121
+ url: string;
122
+ /** Sub-text under the CTA (e.g. `"Opens the Chrome Web Store in a new tab"`). */
123
+ hint: string;
124
+ }
125
+
126
+ export interface InstallUpsellOptions {
127
+ /** Shown prominently as the requesting RP. */
128
+ rpOrigin: string;
129
+ /**
130
+ * Inversion-of-control hook so the SDK's `hasExtension()` is used
131
+ * in production and tests can stub it.
132
+ */
133
+ hasExtensionFn?: () => boolean;
134
+ /** Override the auto-picked CTA. */
135
+ cta?: InstallCta;
136
+ /** Default 1000 ms. */
137
+ pollIntervalMs?: number;
138
+ /**
139
+ * Referral code attributed to signups that flow through this
140
+ * upsell. Default: hostname of `rpOrigin`. Pass `null` to disable.
141
+ * See `SignInOptions.ref` for the full attribution model.
142
+ */
143
+ ref?: string | null;
144
+ }
145
+
146
+ export type InstallUpsellResult = 'installed' | 'canceled';
147
+
148
+ export interface SignInSuccess {
149
+ /** JWS compact-form mID token. */
150
+ jwt: string;
151
+ /** Which surface produced the JWT. */
152
+ surface: 'extension' | 'native_app';
153
+ }
154
+
155
+ export class SignInError extends Error {
156
+ constructor(code: ErrorCode, message: string);
157
+ readonly code: ErrorCode;
158
+ }
159
+
160
+ // ─── Public functions ──────────────────────────────────────────────────────
161
+
162
+ export function signIn(
163
+ request: SignInRequest,
164
+ options?: SignInOptions
165
+ ): Promise<SignInSuccess>;
166
+
167
+ export function hasExtension(): boolean;
168
+
169
+ /** Internal exposed for SDK consumers that need to roll their own. */
170
+ export function generateRequestId(): string;
171
+ /** Internal — exposed for the verifier package + tests. */
172
+ export function base64UrlEncode(str: string): string;
173
+ /** Internal exposed for the verifier package + tests. */
174
+ export function base64UrlDecode(b64url: string): string;
175
+
176
+ /**
177
+ * Pick the right install CTA for the user's browser / OS. Auto-called
178
+ * by `signIn()` when the upsell fires; exposed so RPs that opted out
179
+ * of the inline modal can use the same recommendation engine in
180
+ * their own UI.
181
+ */
182
+ export function pickInstallCta(): InstallCta;
183
+
184
+ /**
185
+ * Extract the default referral code from an RP origin — the bare
186
+ * hostname (e.g. `"https://acme.com"` → `"acme.com"`). Exposed so
187
+ * RPs can preview what attribution string they'd get without
188
+ * actually triggering the upsell.
189
+ *
190
+ * Returns `null` for malformed origins.
191
+ */
192
+ export function defaultRefFromOrigin(rpOrigin: string): string | null;
193
+
194
+ /**
195
+ * Render the install upsell overlay and resolve when the user either
196
+ * cancels or completes the install. Auto-called by `signIn()` on
197
+ * `ERR_NO_WALLET_INSTALLED`; exposed for RPs who set
198
+ * `installUpsell: false` and want to invoke it on their own
199
+ * conditions.
200
+ */
201
+ export function showInstallUpsell(
202
+ options: InstallUpsellOptions
203
+ ): Promise<InstallUpsellResult>;
204
+
205
+ /**
206
+ * Resume a sign-in that was interrupted by a page reload during the
207
+ * install upsell.
208
+ *
209
+ * Call once at app boot — ideally as early as possible — so a user
210
+ * who installed MATA and reloaded sees their sign-in continue without
211
+ * a visible flicker through the logged-out state.
212
+ *
213
+ * - Returns `{jwt, surface}` if a pending request was found, the
214
+ * extension is now installed, and the resumed sign-in completed.
215
+ * - Returns `null` if no resume is pending, the stash is stale, or
216
+ * the extension is still missing (the user reloaded before
217
+ * actually installing).
218
+ * - Rejects with `SignInError` when a pending request exists and the
219
+ * extension is present but the sign-in itself failed (`user_denied`,
220
+ * `timeout`, etc.). Same error shape as `signIn()`.
221
+ */
222
+ export function resumePendingSignIn(): Promise<SignInSuccess | null>;
223
+
224
+ /**
225
+ * Imperatively drop any pending resume entry. Useful when the RP
226
+ * has navigated to a different sign-in flow that supersedes the
227
+ * pending mID request.
228
+ */
229
+ export function clearPendingSignIn(): void;
package/src/index.js CHANGED
@@ -38,6 +38,12 @@ export const KIND_SIGN_IN_REQUEST = 'sign_in_request';
38
38
  /** The `kind` value on the sign-in response message. @internal */
39
39
  export const KIND_SIGN_IN_RESPONSE = 'sign_in_response';
40
40
 
41
+ /** The `kind` value on the IAMHUMAN statement-sign request message. @internal */
42
+ export const KIND_STATEMENT_SIGN_REQUEST = 'statement_sign_request';
43
+
44
+ /** The `kind` value on the IAMHUMAN statement-sign response message. @internal */
45
+ export const KIND_STATEMENT_SIGN_RESPONSE = 'statement_sign_response';
46
+
41
47
  /** The native app's URL scheme. @internal */
42
48
  export const URL_SCHEME = 'mata-mid';
43
49
 
@@ -122,7 +128,8 @@ const DEFAULT_TIMEOUT_MS = 120_000;
122
128
  * @param {Record<string, {optional: true, description?: string}>} [request.claims.custom] - custom claims
123
129
  * @param {object} [options]
124
130
  * @param {number} [options.timeoutMs] - request timeout (default: 2 min)
125
- * @param {string} [options.nativeAppCallback] - URL the native app opens to return (default: window.location.href)
131
+ * @param {string} [options.nativeAppCallback] - URL the native app opens to return (default: window.location.href). For a native host, set this to your app's own custom scheme (e.g. `"myapp://mid-callback"`) so the wallet's callback routes back to your app rather than a browser tab.
132
+ * @param {boolean} [options.nativeHost] - set `true` when this SDK runs inside a native webview (Dioxus/Tauri/wry/Electron) rather than a real browser tab. Skips the browser-only "no wallet" visibility heuristic (which would false-positive because a native webview never backgrounds) and relies on the hard `timeoutMs` instead. Pair with `nativeAppCallback` + your host injecting the `#mid_response` fragment back into the webview.
126
133
  * @param {boolean} [options.installUpsell] - whether to show the install upsell when no wallet is detected (default: true). Set to false to get the raw `ERR_NO_WALLET_INSTALLED` error and handle the upsell UI yourself.
127
134
  * @param {string | null} [options.ref] - referral code attributed to signups that flow through the install upsell. Defaults to the hostname extracted from `rpOrigin` (e.g., `"acme.com"`) so RPs get attribution by default. Pass `null` to opt out, or a custom string to override (e.g., a configured MATA referral code). Follows the existing `?ref=` convention captured by my.mata.network's signup flow.
128
135
  * @returns {Promise<{jwt: string, surface: "extension" | "native_app"}>}
@@ -397,6 +404,106 @@ function signInViaExtension(request, timeoutMs) {
397
404
  });
398
405
  }
399
406
 
407
+ // ─── signStatement() — IAMHUMAN content signing (H0-broker) ─────────────────
408
+
409
+ /**
410
+ * Ask the user's MATA wallet to sign content as their personal DID (an
411
+ * IAMHUMAN post/comment). The private key never leaves the wallet — it signs
412
+ * and returns a self-contained `mata-sign` token that the IAMHUMAN host
413
+ * accepts as-is. Parallel to `signIn()`, but produces a content signature.
414
+ *
415
+ * Extension surface only for now; native `mata-mid://statement` is a follow-up.
416
+ *
417
+ * @param {object} request
418
+ * @param {string} request.rpOrigin - the requesting site's bare origin
419
+ * @param {string} request.content - the exact content to sign
420
+ * @param {string} request.context - domain tag, e.g. "iamhuman"
421
+ * @param {string} [request.contentType] - media type (default "text/plain")
422
+ * @param {string | null} [request.nonce] - optional single-use nonce
423
+ * @param {object} [options]
424
+ * @param {number} [options.timeoutMs] - request timeout (default 2 min)
425
+ * @returns {Promise<{token: string, surface: "extension"}>}
426
+ * @throws {SignInError} with `.code` matching one of the `ERR_*` constants
427
+ */
428
+ export async function signStatement(request, options = {}) {
429
+ if (
430
+ !request ||
431
+ typeof request.rpOrigin !== 'string' ||
432
+ typeof request.content !== 'string' ||
433
+ typeof request.context !== 'string'
434
+ ) {
435
+ throw new SignInError(
436
+ ERR_INVALID_REQUEST,
437
+ 'signStatement requires { rpOrigin, content, context }',
438
+ );
439
+ }
440
+ const timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;
441
+ if (hasExtension()) {
442
+ return await signStatementViaExtension(request, timeoutMs);
443
+ }
444
+ throw new SignInError(
445
+ ERR_NO_WALLET_INSTALLED,
446
+ 'MATA extension not detected (native statement signing is a follow-up)',
447
+ );
448
+ }
449
+
450
+ /**
451
+ * Send the statement-sign request via the extension's `window.postMessage`
452
+ * bridge and await the wallet's response.
453
+ * @internal
454
+ */
455
+ function signStatementViaExtension(request, timeoutMs) {
456
+ return new Promise((resolve, reject) => {
457
+ const requestId = generateRequestId();
458
+ let timeoutHandle = null;
459
+
460
+ const messageHandler = (event) => {
461
+ const data = event.data;
462
+ if (typeof data !== 'object' || data === null) return;
463
+ if (data[MESSAGE_DISCRIMINATOR] !== true) return;
464
+ if (data.kind !== KIND_STATEMENT_SIGN_RESPONSE) return;
465
+ if (data.request_id !== requestId) return;
466
+
467
+ window.removeEventListener('message', messageHandler);
468
+ if (timeoutHandle !== null) clearTimeout(timeoutHandle);
469
+
470
+ const result = data.result;
471
+ if (typeof result !== 'object' || result === null) {
472
+ reject(new SignInError(ERR_INTERNAL, 'malformed response from wallet'));
473
+ return;
474
+ }
475
+ if (result.outcome === 'ok') {
476
+ resolve({ token: result.token, surface: 'extension' });
477
+ } else if (result.outcome === 'denied') {
478
+ reject(new SignInError(ERR_USER_DENIED, 'user denied signing'));
479
+ } else if (result.outcome === 'error') {
480
+ reject(new SignInError(result.error_code ?? ERR_INTERNAL, result.message ?? ''));
481
+ } else {
482
+ reject(new SignInError(ERR_INTERNAL, `unknown outcome: ${result.outcome}`));
483
+ }
484
+ };
485
+
486
+ window.addEventListener('message', messageHandler);
487
+
488
+ const payload = {
489
+ [MESSAGE_DISCRIMINATOR]: true,
490
+ kind: KIND_STATEMENT_SIGN_REQUEST,
491
+ request_id: requestId,
492
+ rp_origin: request.rpOrigin,
493
+ context: request.context,
494
+ content: request.content,
495
+ content_type: request.contentType ?? 'text/plain',
496
+ nonce: request.nonce ?? null,
497
+ };
498
+ window.postMessage(payload, '*');
499
+
500
+ timeoutHandle = setTimeout(() => {
501
+ window.removeEventListener('message', messageHandler);
502
+ reject(new SignInError(ERR_TIMEOUT, `no response within ${timeoutMs}ms`));
503
+ }, timeoutMs);
504
+ });
505
+ }
506
+
400
507
  // ─── Native app surface ────────────────────────────────────────────────────
401
508
 
402
509
  /**
@@ -477,19 +584,30 @@ function signInViaNativeApp(request, options, timeoutMs) {
477
584
  // app. If the page is still visible 1.5s after dispatching the
478
585
  // deep link AND no hash response has arrived, assume no app is
479
586
  // installed and reject with `no_wallet_installed`.
480
- const NO_APP_DETECT_MS = 1500;
481
- const noAppCheckHandle = setTimeout(() => {
482
- if (document.visibilityState === 'visible') {
483
- cleanup();
484
- clearTimeout(noAppCheckHandle);
485
- reject(
486
- new SignInError(
487
- ERR_NO_WALLET_INSTALLED,
488
- 'no MATA app responded to the deep link'
489
- )
490
- );
491
- }
492
- }, NO_APP_DETECT_MS);
587
+ //
588
+ // This heuristic is browser-only: it relies on the OS deep link
589
+ // backgrounding the tab. Inside a native host (a Dioxus/Tauri/wry/
590
+ // Electron webview that embeds this SDK), the page never loses
591
+ // visibility, so the check would false-positive on every call. Hosts
592
+ // that deliver the callback themselves — registering their own scheme
593
+ // and injecting the `#mid_response` fragment back into the webview —
594
+ // pass `options.nativeHost = true` to skip it and rely on the hard
595
+ // timeout instead.
596
+ let noAppCheckHandle = null;
597
+ if (!options.nativeHost) {
598
+ const NO_APP_DETECT_MS = 1500;
599
+ noAppCheckHandle = setTimeout(() => {
600
+ if (document.visibilityState === 'visible') {
601
+ cleanup();
602
+ reject(
603
+ new SignInError(
604
+ ERR_NO_WALLET_INSTALLED,
605
+ 'no MATA app responded to the deep link'
606
+ )
607
+ );
608
+ }
609
+ }, NO_APP_DETECT_MS);
610
+ }
493
611
 
494
612
  // Hard timeout — user has the configured timeoutMs to complete.
495
613
  timeoutHandle = setTimeout(() => {