@matanetwork/sovereign-id 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +251 -0
- package/package.json +49 -0
- package/src/index.d.ts +216 -0
- package/src/index.js +616 -0
- package/src/install-upsell.js +592 -0
- package/src/resume.js +115 -0
package/LICENSE
ADDED
|
@@ -0,0 +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.
|
package/README.md
ADDED
|
@@ -0,0 +1,251 @@
|
|
|
1
|
+
# @matanetwork/sovereign-id
|
|
2
|
+
|
|
3
|
+
Page-side SDK for **MATA Sovereign ID** (mID) — the permissionless
|
|
4
|
+
self-issued identity protocol.
|
|
5
|
+
|
|
6
|
+
Drop one button into your sign-in page. Users authenticate with their
|
|
7
|
+
own wallet (browser extension or native app). You get back a signed
|
|
8
|
+
JWT carrying a stable DID + the claims they consented to disclose.
|
|
9
|
+
No `client_id`, no portal account, no MAU pricing, no MATA HTTP
|
|
10
|
+
traffic at runtime.
|
|
11
|
+
|
|
12
|
+
## Install
|
|
13
|
+
|
|
14
|
+
```bash
|
|
15
|
+
npm install @matanetwork/sovereign-id
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
Pair with the backend verifier (separate package, same protocol):
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
npm install @matanetwork/sovereign-id-verify
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
## Quick start
|
|
25
|
+
|
|
26
|
+
```javascript
|
|
27
|
+
import { signIn, resumePendingSignIn, SignInError } from '@matanetwork/sovereign-id';
|
|
28
|
+
|
|
29
|
+
// 1. On boot — resume an interrupted sign-in if one was stashed.
|
|
30
|
+
// Returns null when there's nothing pending; the normal page renders.
|
|
31
|
+
resumePendingSignIn().then(handleResult);
|
|
32
|
+
|
|
33
|
+
// 2. On sign-in button click — start a fresh request.
|
|
34
|
+
document.getElementById('signin').addEventListener('click', async () => {
|
|
35
|
+
try {
|
|
36
|
+
const nonce = await fetch('/api/auth/nonce').then(r => r.text());
|
|
37
|
+
const result = await signIn({
|
|
38
|
+
rpOrigin: 'https://acme.com',
|
|
39
|
+
nonce,
|
|
40
|
+
claims: {
|
|
41
|
+
required: ['did'],
|
|
42
|
+
optional: ['email', 'name'],
|
|
43
|
+
},
|
|
44
|
+
});
|
|
45
|
+
handleResult(result);
|
|
46
|
+
} catch (err) {
|
|
47
|
+
if (err instanceof SignInError) handleSignInError(err);
|
|
48
|
+
else throw err;
|
|
49
|
+
}
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
async function handleResult(result) {
|
|
53
|
+
if (!result) return; // resumePendingSignIn returned null — nothing to do.
|
|
54
|
+
|
|
55
|
+
// Hand the JWT to your backend for verification.
|
|
56
|
+
const resp = await fetch('/api/auth/mid', {
|
|
57
|
+
method: 'POST',
|
|
58
|
+
headers: { 'Content-Type': 'application/json' },
|
|
59
|
+
body: JSON.stringify({ jwt: result.jwt }),
|
|
60
|
+
});
|
|
61
|
+
if (resp.ok) window.location.href = '/dashboard';
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function handleSignInError(err) {
|
|
65
|
+
switch (err.code) {
|
|
66
|
+
case 'user_denied': /* user clicked Deny on the consent screen */ break;
|
|
67
|
+
case 'upsell_canceled': /* user dismissed the install upsell */ break;
|
|
68
|
+
case 'timeout': /* user left the consent screen open too long */ break;
|
|
69
|
+
case 'origin_mismatch': /* page lied about its origin (rare) */ break;
|
|
70
|
+
default: console.error(err.code, err.message);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
That's it. No back-channel `/token` exchange. No JWKS to refresh. The
|
|
76
|
+
JWT is self-anchored — your backend verifies it entirely against
|
|
77
|
+
cryptographic material embedded in the token.
|
|
78
|
+
|
|
79
|
+
## API reference
|
|
80
|
+
|
|
81
|
+
### `signIn(request, options?)`
|
|
82
|
+
|
|
83
|
+
Probes for the MATA browser extension first; falls back to the
|
|
84
|
+
`mata-mid://` native-app deep link. If neither responds, an install
|
|
85
|
+
upsell modal opens (links to the Chrome Web Store, polls for the
|
|
86
|
+
extension, auto-resumes once installed).
|
|
87
|
+
|
|
88
|
+
#### `request` — required
|
|
89
|
+
|
|
90
|
+
| Field | Type | Notes |
|
|
91
|
+
|---|---|---|
|
|
92
|
+
| `rpOrigin` | `string` | Your bare origin, e.g. `"https://acme.com"`. Becomes the JWT's `aud` claim. |
|
|
93
|
+
| `nonce` | `string` | Single-use random string from your backend. Echoed in the JWT for replay defense. |
|
|
94
|
+
| `claims.required` | `string[]` | Claim keys the user MUST approve. Almost always `["did"]`. Denial blocks sign-in. |
|
|
95
|
+
| `claims.optional` | `string[]` | Claim keys the user can include or skip. |
|
|
96
|
+
| `claims.custom` | `Record<string, {optional: true, description?: string}>` | Arbitrary keys from the user's `profile_kv` (v0: ignored by the wallet). |
|
|
97
|
+
|
|
98
|
+
**Standard claim catalog (v1):** `did`, `email`, `name`, `created_at`,
|
|
99
|
+
`paired_devices_count`, `level_rating.trust`, `level_rating.security`,
|
|
100
|
+
`level_rating.incentive`.
|
|
101
|
+
|
|
102
|
+
#### `options` — optional
|
|
103
|
+
|
|
104
|
+
| Field | Type | Default | Notes |
|
|
105
|
+
|---|---|---|---|
|
|
106
|
+
| `timeoutMs` | `number` | `120000` | Hard request timeout. |
|
|
107
|
+
| `nativeAppCallback` | `string` | `window.location.href` | URL the native app's callback redirects to. |
|
|
108
|
+
| `installUpsell` | `boolean` | `true` | When `false`, `ERR_NO_WALLET_INSTALLED` is thrown raw instead of showing the modal. |
|
|
109
|
+
| `ref` | `string \| null` | hostname of `rpOrigin` | Referral code stamped onto the upsell's outbound links. Default delivers attribution back to your domain. Pass `null` to opt out. |
|
|
110
|
+
|
|
111
|
+
#### Returns
|
|
112
|
+
|
|
113
|
+
`Promise<{ jwt: string, surface: 'extension' | 'native_app' }>`.
|
|
114
|
+
|
|
115
|
+
#### Throws
|
|
116
|
+
|
|
117
|
+
`SignInError` with `.code` one of:
|
|
118
|
+
|
|
119
|
+
| Code | When |
|
|
120
|
+
|---|---|
|
|
121
|
+
| `user_denied` | User clicked Deny on the consent screen. |
|
|
122
|
+
| `upsell_canceled` | User dismissed the install upsell ("Cancel" or Escape). |
|
|
123
|
+
| `no_wallet_installed` | Only when `installUpsell: false`. Neither extension nor native app responded. |
|
|
124
|
+
| `invalid_request` | Your code passed a malformed request (caught on the page). |
|
|
125
|
+
| `origin_mismatch` | Page-claimed origin didn't match the actual tab origin. Possible page bug or attack. |
|
|
126
|
+
| `wallet_unavailable` | Vault locked, wallet not bootstrapped, or no matching credential. |
|
|
127
|
+
| `required_claim_unavailable` | User signed up without an email but you required it. |
|
|
128
|
+
| `timeout` | User didn't decide within `timeoutMs`. |
|
|
129
|
+
| `internal_error` | Anything else. |
|
|
130
|
+
|
|
131
|
+
### `resumePendingSignIn()`
|
|
132
|
+
|
|
133
|
+
Call once at app boot. Resumes a sign-in that was interrupted by a
|
|
134
|
+
page reload during the install upsell.
|
|
135
|
+
|
|
136
|
+
| Returns | When |
|
|
137
|
+
|---|---|
|
|
138
|
+
| `{ jwt, surface }` | A pending request was stashed, the extension is now installed, and the resumed sign-in completed. |
|
|
139
|
+
| `null` | No pending request, or the stash is stale, or the extension is still missing. |
|
|
140
|
+
| `throws SignInError` | A pending request exists and the extension is present, but the resumed sign-in itself failed. |
|
|
141
|
+
|
|
142
|
+
The stash lives in `sessionStorage` (per-tab, cleared on tab close).
|
|
143
|
+
TTL = the original request's `timeoutMs`. Stale entries are silently
|
|
144
|
+
dropped on read.
|
|
145
|
+
|
|
146
|
+
### `hasExtension()`
|
|
147
|
+
|
|
148
|
+
Returns `true` when `window.__mata_mid__` (set by the extension's
|
|
149
|
+
content script) is present. Useful for conditionally rendering the
|
|
150
|
+
sign-in button vs. an install prompt before the user has clicked.
|
|
151
|
+
|
|
152
|
+
> **Race-condition note.** The content script runs at `document_idle`.
|
|
153
|
+
> RPs calling `signIn()` synchronously from `<head>` may probe before
|
|
154
|
+
> the script is injected. Call after `DOMContentLoaded` (or after a
|
|
155
|
+
> user gesture) to avoid the false negative.
|
|
156
|
+
|
|
157
|
+
### `showInstallUpsell(options)`
|
|
158
|
+
|
|
159
|
+
Exposed for RPs who set `installUpsell: false` and want to render the
|
|
160
|
+
upsell on their own conditions (e.g. after their own UI flow). Same
|
|
161
|
+
modal `signIn()` uses internally; same `'installed' | 'canceled'`
|
|
162
|
+
result shape.
|
|
163
|
+
|
|
164
|
+
### `pickInstallCta()` / `defaultRefFromOrigin(origin)`
|
|
165
|
+
|
|
166
|
+
Helpers for RPs building their own install upsell UI:
|
|
167
|
+
|
|
168
|
+
- `pickInstallCta()` returns the right `{label, url, hint}` for the
|
|
169
|
+
current browser/OS.
|
|
170
|
+
- `defaultRefFromOrigin('https://acme.com')` returns `'acme.com'` — the
|
|
171
|
+
referral code the SDK would stamp by default.
|
|
172
|
+
|
|
173
|
+
### `clearPendingSignIn()`
|
|
174
|
+
|
|
175
|
+
Imperatively drops the resume stash. Use when you've navigated to a
|
|
176
|
+
different sign-in flow that supersedes the pending mID request.
|
|
177
|
+
|
|
178
|
+
### `SignInError`
|
|
179
|
+
|
|
180
|
+
```typescript
|
|
181
|
+
class SignInError extends Error {
|
|
182
|
+
readonly code: ErrorCode;
|
|
183
|
+
}
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
## Referral attribution
|
|
187
|
+
|
|
188
|
+
Both outbound links in the install upsell modal carry `?ref=<code>` by
|
|
189
|
+
default — your domain becomes the attribution code without any extra
|
|
190
|
+
wiring. The signup flow at my.mata.network reads `?ref=` and forwards
|
|
191
|
+
it through to your downstream analytics as `referral_code`.
|
|
192
|
+
|
|
193
|
+
Customize:
|
|
194
|
+
|
|
195
|
+
```javascript
|
|
196
|
+
signIn(request, { ref: 'acme-launch-2026' }); // custom code
|
|
197
|
+
signIn(request, { ref: null }); // opt out
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
## What you don't have to think about
|
|
201
|
+
|
|
202
|
+
- **No `client_id`, `client_secret`, redirect URI allowlist.** There's no registration step.
|
|
203
|
+
- **No `/token` back-channel exchange.** The JWT comes back from `signIn()` directly.
|
|
204
|
+
- **No JWKS endpoint refresh.** The JWT bundles its own resolution data.
|
|
205
|
+
- **No DID-resolver HTTP calls.** The DID is its own public key.
|
|
206
|
+
- **No MAU pricing.** No metering of any kind.
|
|
207
|
+
|
|
208
|
+
## Verification on the backend
|
|
209
|
+
|
|
210
|
+
Use [@matanetwork/sovereign-id-verify](https://npmjs.com/package/@matanetwork/sovereign-id-verify):
|
|
211
|
+
|
|
212
|
+
```javascript
|
|
213
|
+
import { verifyResponse } from '@matanetwork/sovereign-id-verify';
|
|
214
|
+
|
|
215
|
+
const verified = await verifyResponse(req.body.jwt, {
|
|
216
|
+
expectedAudience: 'https://acme.com',
|
|
217
|
+
expectedNonce: sessionNonce,
|
|
218
|
+
nowUnixSecs: Math.floor(Date.now() / 1000),
|
|
219
|
+
});
|
|
220
|
+
|
|
221
|
+
// verified.did — stable user identifier
|
|
222
|
+
// verified.claims — disclosed values
|
|
223
|
+
// verified.currentVersion — head roster version; cache for rollback detection
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
## Browser support
|
|
227
|
+
|
|
228
|
+
| Browser | Sign-in path |
|
|
229
|
+
|---|---|
|
|
230
|
+
| Chrome / Edge / Brave / Arc / Opera | Extension or native-app deep link |
|
|
231
|
+
| Safari | Native-app deep link (extension coming) |
|
|
232
|
+
| Firefox | Native-app deep link (extension coming) |
|
|
233
|
+
| Mobile Chrome / Safari | Native-app deep link (apps coming) |
|
|
234
|
+
|
|
235
|
+
When no compatible surface is available, the install upsell modal
|
|
236
|
+
surfaces the right CTA per browser/OS automatically.
|
|
237
|
+
|
|
238
|
+
## Accessibility
|
|
239
|
+
|
|
240
|
+
The install upsell modal:
|
|
241
|
+
|
|
242
|
+
- Renders in a closed Shadow DOM (RP CSS can't break or skin it).
|
|
243
|
+
- Traps Tab / Shift+Tab focus inside the modal.
|
|
244
|
+
- Restores focus to the element that was focused before opening.
|
|
245
|
+
- Closes on Escape and on backdrop click.
|
|
246
|
+
- Shows `:focus-visible` outlines for keyboard users.
|
|
247
|
+
- Uses `role="dialog"` + `aria-modal="true"` + `aria-labelledby`.
|
|
248
|
+
|
|
249
|
+
## License
|
|
250
|
+
|
|
251
|
+
MIT — see [LICENSE](LICENSE).
|
package/package.json
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@matanetwork/sovereign-id",
|
|
3
|
+
"version": "0.1.0",
|
|
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
|
+
"type": "module",
|
|
6
|
+
"main": "src/index.js",
|
|
7
|
+
"types": "src/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"types": "./src/index.d.ts",
|
|
11
|
+
"import": "./src/index.js"
|
|
12
|
+
}
|
|
13
|
+
},
|
|
14
|
+
"sideEffects": false,
|
|
15
|
+
"scripts": {
|
|
16
|
+
"test": "node --test tests/index.test.js",
|
|
17
|
+
"prepublishOnly": "node --test tests/index.test.js"
|
|
18
|
+
},
|
|
19
|
+
"files": [
|
|
20
|
+
"src/",
|
|
21
|
+
"README.md",
|
|
22
|
+
"LICENSE"
|
|
23
|
+
],
|
|
24
|
+
"keywords": [
|
|
25
|
+
"mata",
|
|
26
|
+
"sovereign-id",
|
|
27
|
+
"mid",
|
|
28
|
+
"sso",
|
|
29
|
+
"identity",
|
|
30
|
+
"did",
|
|
31
|
+
"self-sovereign",
|
|
32
|
+
"passwordless",
|
|
33
|
+
"did-mata",
|
|
34
|
+
"permissionless-auth"
|
|
35
|
+
],
|
|
36
|
+
"license": "MIT",
|
|
37
|
+
"engines": {
|
|
38
|
+
"node": ">=18"
|
|
39
|
+
},
|
|
40
|
+
"homepage": "https://github.com/mata-network/mata/tree/main/packages/mata-sovereign-id-sdk#readme",
|
|
41
|
+
"bugs": {
|
|
42
|
+
"url": "https://github.com/mata-network/mata/issues"
|
|
43
|
+
},
|
|
44
|
+
"repository": {
|
|
45
|
+
"type": "git",
|
|
46
|
+
"url": "git+https://github.com/mata-network/mata.git",
|
|
47
|
+
"directory": "packages/mata-sovereign-id-sdk"
|
|
48
|
+
}
|
|
49
|
+
}
|
package/src/index.d.ts
ADDED
|
@@ -0,0 +1,216 @@
|
|
|
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;
|