@zkproofport-app/sdk 0.2.10 → 0.2.11
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/README.md +52 -28
- package/dist/ProofportSDK.d.ts +22 -8
- package/dist/deeplink.d.ts +70 -0
- package/dist/index.esm.js +130 -16
- package/dist/index.esm.js.map +1 -1
- package/dist/index.js +130 -16
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +130 -16
- package/dist/index.mjs.map +1 -1
- package/dist/types.d.ts +6 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -292,7 +292,7 @@ const relay = await sdk.createRelayRequest('coinbase_attestation', {
|
|
|
292
292
|
dappIcon: 'https://myapp.com/icon.png',
|
|
293
293
|
message: 'Verify your identity to continue',
|
|
294
294
|
nonce: 'unique-nonce-123', // Optional: replay prevention
|
|
295
|
-
returnScheme: 'mydapp://', // Optional:
|
|
295
|
+
returnScheme: 'mydapp://', // Optional: native apps only, see below
|
|
296
296
|
});
|
|
297
297
|
|
|
298
298
|
// relay.requestId — Relay-issued UUID
|
|
@@ -303,48 +303,72 @@ const relay = await sdk.createRelayRequest('coinbase_attestation', {
|
|
|
303
303
|
|
|
304
304
|
Every field of the third argument is optional: `message`, `dappName` and `dappIcon` are shown to the user in the ZKProofport app, `nonce` is a value of your own choosing that the relay refuses to accept twice, and `returnScheme` is described next.
|
|
305
305
|
|
|
306
|
-
#### Switching back
|
|
306
|
+
#### Switching back: `returnScheme`
|
|
307
307
|
|
|
308
|
-
|
|
308
|
+
When ZKProofport finishes a proof, the user is sitting in the ZKProofport app. `returnScheme` answers one question: **which app should come back to the foreground?**
|
|
309
|
+
|
|
310
|
+
It names an app. It is not a URL, not a return address, and the proof is never sent to it — the proof still reaches you exactly as before, through `waitForProof()` / `waitForResult()`. This is the same handoff a wallet performs after a signature (`redirect.native`).
|
|
311
|
+
|
|
312
|
+
**One form is accepted:**
|
|
313
|
+
|
|
314
|
+
| Form | Example |
|
|
315
|
+
|------|---------|
|
|
316
|
+
| Bare custom scheme | `'mydapp://'` — an RFC 3986 scheme followed by exactly `://` |
|
|
317
|
+
|
|
318
|
+
**If you are a native app**, pass the scheme your app registers:
|
|
309
319
|
|
|
310
320
|
```typescript
|
|
311
|
-
// Native app: your own registered scheme
|
|
312
321
|
await sdk.createRelayRequest('coinbase_attestation', { scope: 'myapp.com' }, {
|
|
313
322
|
returnScheme: 'mydapp://',
|
|
314
323
|
});
|
|
315
|
-
|
|
316
|
-
// Web app: your own origin
|
|
317
|
-
await sdk.createRelayRequest('coinbase_attestation', { scope: 'myapp.com' }, {
|
|
318
|
-
returnScheme: 'https://myapp.com',
|
|
319
|
-
});
|
|
320
324
|
```
|
|
321
325
|
|
|
322
|
-
**
|
|
326
|
+
**If you are a web page, pass nothing.** You have no app to come back to, so there is no value you can honestly put here — and the SDK handles the browser case for you:
|
|
323
327
|
|
|
324
328
|
```typescript
|
|
325
|
-
|
|
326
|
-
|
|
329
|
+
// Correct. Do not pass returnScheme from a web page.
|
|
330
|
+
await sdk.createRelayRequest('coinbase_attestation', { scope: 'myapp.com' });
|
|
327
331
|
```
|
|
328
332
|
|
|
329
|
-
|
|
333
|
+
##### What the SDK does automatically
|
|
330
334
|
|
|
331
|
-
|
|
335
|
+
With the option omitted, the SDK looks at the page's own user agent and decides:
|
|
332
336
|
|
|
333
|
-
|
|
|
334
|
-
|
|
335
|
-
|
|
|
336
|
-
|
|
|
337
|
+
| Where your page is running | What the SDK sends | What the user sees when the proof is done |
|
|
338
|
+
|---|---|---|
|
|
339
|
+
| **Chrome for iOS** (`CriOS`) | `googlechrome://` | Chrome comes forward on **the tab they were already reading** — no new tab, no reload, page state intact |
|
|
340
|
+
| **Firefox for iOS** (`FxiOS`) | `firefox://` | Same: Firefox comes forward on the tab they were already reading |
|
|
341
|
+
| **Android**, any browser | nothing | The ZKProofport app puts itself in the background and the browser resumes exactly as it was |
|
|
342
|
+
| **Safari, Brave, Arc, Edge, Opera on iOS** | nothing | ZKProofport tells them the proof was delivered and to switch back; the system "‹ Back" breadcrumb is still at the top left |
|
|
343
|
+
| **In-app webviews** (KakaoTalk, Naver, Line, Instagram) | nothing | Same notice. Any scheme would destroy the page they are on |
|
|
344
|
+
| **Desktop** | nothing | This is the QR flow: the proof happens on a different device, and your page updates over the relay socket |
|
|
337
345
|
|
|
338
|
-
|
|
346
|
+
Only Chrome and Firefox are auto-detected, each from a positive UA token, because they are the two iOS browsers whose bare scheme is **verified in their own source** to bring the app forward without navigating anywhere. There is no Safari equivalent. Brave and Arc are indistinguishable from Safari on iOS, so guessing would eject those users into a browser they were not using — worse than doing nothing. Edge and Opera are closed source and stay out until someone can show their bare schemes behave the same way.
|
|
339
347
|
|
|
340
|
-
|
|
348
|
+
An explicit `returnScheme` always wins; the SDK never overrides a value you set.
|
|
349
|
+
|
|
350
|
+
##### `https://` origins are not accepted
|
|
351
|
+
|
|
352
|
+
An https origin such as `'https://myapp.com'` used to be allowed. **It is now rejected** by the SDK, the relay and the ZKProofport app alike.
|
|
353
|
+
|
|
354
|
+
Opening one does not return anybody anywhere. The OS hands the URL to the browser, and the browser opens a **new tab** on a freshly loaded page — the tab your user actually started in is left behind, along with everything in it, including the socket waiting for the proof you just generated. The round trip the field exists to complete was the thing it broke.
|
|
355
|
+
|
|
356
|
+
##### What is rejected
|
|
357
|
+
|
|
358
|
+
These throw before the SDK makes a single network call, so no challenge is consumed and nothing is wasted on a typo:
|
|
359
|
+
|
|
360
|
+
- **https and http origins** — `'https://myapp.com'`, `'https://myapp.com:8443'` (see above)
|
|
361
|
+
- **bare `https://` and `http://`** — a browser with no page to open is not a return target
|
|
362
|
+
- a host, path, query string or fragment — `'mydapp://transfer?to=0x...'` — which is what stops a request driving your app to a specific action rather than just opening it
|
|
341
363
|
- an empty or whitespace-containing value, or one longer than 128 characters
|
|
342
|
-
-
|
|
343
|
-
-
|
|
364
|
+
- non-ASCII schemes (use punycode if you somehow need one)
|
|
365
|
+
- schemes the OS treats specially: `about`, `blob`, `content`, `data`, `facetime`, `facetime-audio`, `file`, `ftp`, `intent`, `javascript`, `jar`, `mailto`, `sms`, `tel`, `vbscript`
|
|
344
366
|
|
|
345
367
|
The value is matched case-insensitively and stored lowercased by the relay.
|
|
346
368
|
|
|
347
|
-
|
|
369
|
+
##### When nothing happens
|
|
370
|
+
|
|
371
|
+
There is no auto-switch when proof generation **fails** — the error has to be read in the ZKProofport app first. And when no target is sent and none can be inferred, ZKProofport shows the user a notice saying the proof was delivered and to switch back themselves. That is the intended outcome on iOS outside Chrome, not a bug.
|
|
348
372
|
|
|
349
373
|
**OIDC Domain Attestation:**
|
|
350
374
|
|
|
@@ -860,7 +884,7 @@ import type {
|
|
|
860
884
|
| `MdlKrAgeInputs` | Inputs for `mdl_kr_age`: `{ scope, ageThreshold }` |
|
|
861
885
|
| `MdlKrRegionInputs` | Inputs for `mdl_kr_region`: `{ scope, targetRegion }` |
|
|
862
886
|
| `CircuitInputs` | Union of every input type above (plus an empty-input form for circuits that need nothing from the dApp) |
|
|
863
|
-
| `ProofRequest` | Request object: `requestId`, `circuit`, `inputs`, `createdAt`, plus optional `message`, `dappName`, `dappIcon`, `returnScheme
|
|
887
|
+
| `ProofRequest` | Request object: `requestId`, `circuit`, `inputs`, `createdAt`, plus optional `message`, `dappName`, `dappIcon`, `returnScheme` (a bare scheme such as `mydapp://`), `expiresAt` |
|
|
864
888
|
| `ProofResponse` | What `verifyResponseOnChain()` takes: `requestId`, `circuit`, `status`, and — when completed — `proof`, `publicInputs`, `verifierAddress`, `chainId`; `error` when it failed. `numPublicInputs` and `timestamp` also exist but the relay never fills them, so omit them. Field by field in [Step 6](#step-6-read-the-result) |
|
|
865
889
|
| `QRCodeOptions` | QR customization: `width`, `margin`, `darkColor`, `lightColor`, `errorCorrectionLevel` |
|
|
866
890
|
| `VerifierContract` | Verifier contract info: `{ address, chainId, abi }` |
|
|
@@ -996,12 +1020,12 @@ try {
|
|
|
996
1020
|
|
|
997
1021
|
try {
|
|
998
1022
|
await sdk.createRelayRequest('coinbase_attestation', { scope: 'app.com' }, {
|
|
999
|
-
returnScheme: 'https://myapp.com
|
|
1023
|
+
returnScheme: 'https://myapp.com',
|
|
1000
1024
|
});
|
|
1001
1025
|
} catch (err) {
|
|
1002
|
-
// "returnScheme must be a bare custom scheme such as "mydapp://"
|
|
1003
|
-
//
|
|
1004
|
-
// not
|
|
1026
|
+
// "returnScheme must be a bare custom scheme such as "mydapp://" — hosts,
|
|
1027
|
+
// paths, query strings and fragments are not accepted, and an https URL is
|
|
1028
|
+
// not a return target" (thrown before any network call)
|
|
1005
1029
|
}
|
|
1006
1030
|
|
|
1007
1031
|
try {
|
package/dist/ProofportSDK.d.ts
CHANGED
|
@@ -762,8 +762,10 @@ export declare class ProofportSDK {
|
|
|
762
762
|
* @param options - Request options (message, dappName, dappIcon, nonce, returnScheme)
|
|
763
763
|
* @param options.returnScheme - Which app the ZKProofport app should switch back
|
|
764
764
|
* to once it has finished. Put **your own** deep link scheme here — the bare
|
|
765
|
-
* scheme your app registers, e.g. `'mydapp://'`.
|
|
766
|
-
*
|
|
765
|
+
* scheme your app registers, e.g. `'mydapp://'`. Only a native app has an
|
|
766
|
+
* answer to this question; a web app omits the option entirely, because
|
|
767
|
+
* opening its https origin would launch a new browser tab on a fresh page
|
|
768
|
+
* rather than return the user to the tab they started from.
|
|
767
769
|
*
|
|
768
770
|
* This is intentionally **not** a URL and not a return address: no path, query
|
|
769
771
|
* string or fragment is accepted, and the ZKProofport app never sends the proof
|
|
@@ -772,14 +774,26 @@ export declare class ProofportSDK {
|
|
|
772
774
|
* "which app should come back to the foreground", exactly like the
|
|
773
775
|
* `redirect.native` value a wallet uses to hand control back after signing.
|
|
774
776
|
*
|
|
775
|
-
* **Omit it and
|
|
776
|
-
*
|
|
777
|
-
*
|
|
777
|
+
* **Omit it and the SDK decides for you.** When the request is created from a
|
|
778
|
+
* page running in Chrome for iOS (`CriOS`) or Firefox for iOS (`FxiOS`), the
|
|
779
|
+
* SDK fills in `'googlechrome://'` or `'firefox://'` by itself: opening either
|
|
780
|
+
* scheme bare brings that browser forward without navigating, so the user lands
|
|
781
|
+
* back on the tab they were already reading. Every other browser sends nothing,
|
|
782
|
+
* because nothing safe exists to send — Safari has no such scheme, and Brave
|
|
783
|
+
* and Arc are indistinguishable from Safari on iOS, so guessing would eject the
|
|
784
|
+
* user into the wrong browser.
|
|
785
|
+
* On Android nothing is needed at all: the ZKProofport app brings itself to the
|
|
786
|
+
* back and the browser resumes exactly as it was. When no target is sent and
|
|
787
|
+
* none can be inferred, the ZKProofport app tells the user the proof was
|
|
788
|
+
* delivered and lets them switch back themselves.
|
|
789
|
+
*
|
|
790
|
+
* There is no auto-switch on proof failure; only on success and on the user
|
|
791
|
+
* declining the request.
|
|
778
792
|
*
|
|
779
793
|
* Rejected with an error before the request is sent: empty or whitespace values,
|
|
780
|
-
* anything longer than 128 characters, values
|
|
781
|
-
*
|
|
782
|
-
* `mailto:` families.
|
|
794
|
+
* anything longer than 128 characters, values carrying a host/path/query/fragment
|
|
795
|
+
* (including https origins such as `'https://myapp.com'`), and the `http:`,
|
|
796
|
+
* `file:`, `data:`, `javascript:`, `intent:`, `tel:`, `sms:`, `mailto:` families.
|
|
783
797
|
* @returns Promise resolving to RelayProofRequest with requestId, deepLink, pollUrl
|
|
784
798
|
* @throws Error if signer not set, returnScheme is malformed, or the relay request fails
|
|
785
799
|
*
|
package/dist/deeplink.d.ts
CHANGED
|
@@ -197,12 +197,82 @@ export declare const MAX_RETURN_SCHEME_LENGTH = 128;
|
|
|
197
197
|
* value fails fast in the integrator's own code instead of coming back as a
|
|
198
198
|
* relay 400. The relay re-validates and is the authority.
|
|
199
199
|
*
|
|
200
|
+
* One shape is accepted: a bare custom scheme such as `mydapp://`. It names an
|
|
201
|
+
* app to switch back to, never a page to visit — so hosts, paths and query
|
|
202
|
+
* strings are rejected, and an https URL is not a valid value. A web page has
|
|
203
|
+
* no app to return to and simply omits the field.
|
|
204
|
+
*
|
|
200
205
|
* @param value - Candidate return target
|
|
201
206
|
* @returns `null` when acceptable, otherwise a human-readable error message
|
|
202
207
|
*
|
|
203
208
|
* @internal
|
|
204
209
|
*/
|
|
205
210
|
export declare function validateReturnScheme(value: unknown): string | null;
|
|
211
|
+
/**
|
|
212
|
+
* The return target to use when the integrator named none.
|
|
213
|
+
*
|
|
214
|
+
* Only the requesting PAGE can answer "which browser is this?" — iOS gives the
|
|
215
|
+
* ZKProofport app nothing to work with (`sourceApplication` is nil across team
|
|
216
|
+
* identifiers, and there is no public API to foreground an app by bundle id).
|
|
217
|
+
* So the detection has to happen here, in the SDK, and travel with the request.
|
|
218
|
+
*
|
|
219
|
+
* Two cases are detected, each from a POSITIVE token, and each verified in the
|
|
220
|
+
* browser's own source to foreground WITHOUT navigating anywhere:
|
|
221
|
+
*
|
|
222
|
+
* Chrome for iOS (`CriOS/`) -> `googlechrome://`
|
|
223
|
+
* Firefox for iOS (`FxiOS/`) -> `firefox://`
|
|
224
|
+
*
|
|
225
|
+
* The BARE scheme is the point in both cases: it carries no URL, so the browser
|
|
226
|
+
* comes forward with nothing to open and the user lands on the tab they were
|
|
227
|
+
* already reading — no new tab, no navigation, no reload, JS state intact.
|
|
228
|
+
*
|
|
229
|
+
* Chrome: `ios/chrome/app/startup/chrome_app_startup_parameters.mm` replaces
|
|
230
|
+
* the scheme with `http`, producing `http://`, which fails GURL validation
|
|
231
|
+
* (`if (!externalURL.is_valid()) return nil;`), so the startup parameters are
|
|
232
|
+
* nil and there is nothing to open.
|
|
233
|
+
*
|
|
234
|
+
* Firefox: `firefox://` has an empty host, and
|
|
235
|
+
* `Coordinators/Router/RouteBuilder.swift` resolves the host through
|
|
236
|
+
* `DeeplinkInput.Host(rawValue: "")`, which is nil because no case has an empty
|
|
237
|
+
* raw value (`Coordinators/Router/DeeplinkInput.swift`). The scheme is not http
|
|
238
|
+
* either, so `makeRoute(url:)` falls to its final `else { return nil }`, and
|
|
239
|
+
* `SceneDelegate.handleOpenURL` then hits `guard let route = ... else
|
|
240
|
+
* { return }` and does nothing at all. Verified on the warm path — the browser
|
|
241
|
+
* is already running, which is always true here, because the user came from a
|
|
242
|
+
* page in it.
|
|
243
|
+
*
|
|
244
|
+
* Everything else deliberately returns undefined, and that is not a gap:
|
|
245
|
+
*
|
|
246
|
+
* - Safari has no equivalent. No scheme means "just come forward";
|
|
247
|
+
* `https://<url>` navigates and opens a new tab, `x-web-search://` opens a
|
|
248
|
+
* search page. Both lose the user's page.
|
|
249
|
+
* - Brave and Arc for iOS are UNDETECTABLE. Brave ships no UA token and
|
|
250
|
+
* identifies itself only via Client Hints, which WebKit does not implement,
|
|
251
|
+
* so it looks exactly like Safari. Inferring "Safari" by elimination and
|
|
252
|
+
* firing a Safari-ish scheme would throw Brave and Arc users into a browser
|
|
253
|
+
* they were not using — strictly worse than doing nothing.
|
|
254
|
+
* - In-app webviews (KakaoTalk, Naver, Line, Instagram) are not tabs at all.
|
|
255
|
+
* Any scheme destroys the page; the system "< Back to X" breadcrumb is the
|
|
256
|
+
* only thing that preserves it.
|
|
257
|
+
* - Edge (`EdgiOS`) and Opera (`OPiOS` / `OPT`) for iOS are NOT included.
|
|
258
|
+
* Both are closed source, so `microsoft-edge://` and `opera-http://` cannot
|
|
259
|
+
* be checked the way Chrome's and Firefox's were. Their bare schemes are
|
|
260
|
+
* plausible analogues, and that is exactly why they stay out: an unverified
|
|
261
|
+
* guess that opens a new tab is the bug this field exists to avoid. Add one
|
|
262
|
+
* only with evidence that it foregrounds WITHOUT navigating.
|
|
263
|
+
* - Android needs nothing: the ZKProofport app brings itself to the back
|
|
264
|
+
* (`moveTaskToBack`), which resumes the browser exactly as it was.
|
|
265
|
+
*
|
|
266
|
+
* When this returns undefined the field is omitted and the ZKProofport app
|
|
267
|
+
* tells the user the proof was delivered and to switch back themselves.
|
|
268
|
+
*
|
|
269
|
+
* @param userAgent - override, for tests. Defaults to `navigator.userAgent`.
|
|
270
|
+
* Returns undefined when there is no navigator at all (Node, SSR).
|
|
271
|
+
* @returns a scheme that is safe to open, or undefined to send nothing
|
|
272
|
+
*
|
|
273
|
+
* @internal
|
|
274
|
+
*/
|
|
275
|
+
export declare function detectReturnScheme(userAgent?: string): string | undefined;
|
|
206
276
|
export declare function validateProofRequest(request: ProofRequest): {
|
|
207
277
|
valid: boolean;
|
|
208
278
|
error?: string;
|
package/dist/index.esm.js
CHANGED
|
@@ -613,8 +613,16 @@ function validateMdlInputs(circuit, inputs) {
|
|
|
613
613
|
const MAX_RETURN_SCHEME_LENGTH = 128;
|
|
614
614
|
/** RFC 3986 scheme (`ALPHA *( ALPHA / DIGIT / "+" / "-" / "." )`) followed by exactly `://`. */
|
|
615
615
|
const BARE_SCHEME_RE = /^[a-z][a-z0-9+.-]*:\/\/$/;
|
|
616
|
-
/**
|
|
617
|
-
|
|
616
|
+
/**
|
|
617
|
+
* Schemes we refuse even in the bare `scheme://` shape.
|
|
618
|
+
*
|
|
619
|
+
* `http` and `https` are the back door the shape rule alone leaves open: with
|
|
620
|
+
* no host, `https://` is shaped exactly like a bare custom scheme and would
|
|
621
|
+
* otherwise pass. A browser pointed at nowhere is not a return target, and
|
|
622
|
+
* accepting it would re-open the URL-shaped door this field just closed.
|
|
623
|
+
* `googlechrome://` and `firefox://` are deliberately NOT denied — bare, they
|
|
624
|
+
* foreground that browser without navigating, which is the whole point.
|
|
625
|
+
*/
|
|
618
626
|
const DENIED_RETURN_SCHEMES = new Set([
|
|
619
627
|
'about',
|
|
620
628
|
'blob',
|
|
@@ -625,6 +633,7 @@ const DENIED_RETURN_SCHEMES = new Set([
|
|
|
625
633
|
'file',
|
|
626
634
|
'ftp',
|
|
627
635
|
'http',
|
|
636
|
+
'https',
|
|
628
637
|
'intent',
|
|
629
638
|
'javascript',
|
|
630
639
|
'jar',
|
|
@@ -638,6 +647,11 @@ const DENIED_RETURN_SCHEMES = new Set([
|
|
|
638
647
|
* value fails fast in the integrator's own code instead of coming back as a
|
|
639
648
|
* relay 400. The relay re-validates and is the authority.
|
|
640
649
|
*
|
|
650
|
+
* One shape is accepted: a bare custom scheme such as `mydapp://`. It names an
|
|
651
|
+
* app to switch back to, never a page to visit — so hosts, paths and query
|
|
652
|
+
* strings are rejected, and an https URL is not a valid value. A web page has
|
|
653
|
+
* no app to return to and simply omits the field.
|
|
654
|
+
*
|
|
641
655
|
* @param value - Candidate return target
|
|
642
656
|
* @returns `null` when acceptable, otherwise a human-readable error message
|
|
643
657
|
*
|
|
@@ -658,11 +672,8 @@ function validateReturnScheme(value) {
|
|
|
658
672
|
return 'returnScheme must not contain whitespace';
|
|
659
673
|
}
|
|
660
674
|
const normalized = value.toLowerCase();
|
|
661
|
-
if (HTTPS_ORIGIN_RE.test(normalized)) {
|
|
662
|
-
return null;
|
|
663
|
-
}
|
|
664
675
|
if (!BARE_SCHEME_RE.test(normalized)) {
|
|
665
|
-
return 'returnScheme must be a bare custom scheme such as "mydapp://"
|
|
676
|
+
return 'returnScheme must be a bare custom scheme such as "mydapp://" — hosts, paths, query strings and fragments are not accepted, and an https URL is not a return target';
|
|
666
677
|
}
|
|
667
678
|
const schemeName = normalized.slice(0, normalized.indexOf(':'));
|
|
668
679
|
if (DENIED_RETURN_SCHEMES.has(schemeName)) {
|
|
@@ -670,6 +681,90 @@ function validateReturnScheme(value) {
|
|
|
670
681
|
}
|
|
671
682
|
return null;
|
|
672
683
|
}
|
|
684
|
+
/**
|
|
685
|
+
* The return target to use when the integrator named none.
|
|
686
|
+
*
|
|
687
|
+
* Only the requesting PAGE can answer "which browser is this?" — iOS gives the
|
|
688
|
+
* ZKProofport app nothing to work with (`sourceApplication` is nil across team
|
|
689
|
+
* identifiers, and there is no public API to foreground an app by bundle id).
|
|
690
|
+
* So the detection has to happen here, in the SDK, and travel with the request.
|
|
691
|
+
*
|
|
692
|
+
* Two cases are detected, each from a POSITIVE token, and each verified in the
|
|
693
|
+
* browser's own source to foreground WITHOUT navigating anywhere:
|
|
694
|
+
*
|
|
695
|
+
* Chrome for iOS (`CriOS/`) -> `googlechrome://`
|
|
696
|
+
* Firefox for iOS (`FxiOS/`) -> `firefox://`
|
|
697
|
+
*
|
|
698
|
+
* The BARE scheme is the point in both cases: it carries no URL, so the browser
|
|
699
|
+
* comes forward with nothing to open and the user lands on the tab they were
|
|
700
|
+
* already reading — no new tab, no navigation, no reload, JS state intact.
|
|
701
|
+
*
|
|
702
|
+
* Chrome: `ios/chrome/app/startup/chrome_app_startup_parameters.mm` replaces
|
|
703
|
+
* the scheme with `http`, producing `http://`, which fails GURL validation
|
|
704
|
+
* (`if (!externalURL.is_valid()) return nil;`), so the startup parameters are
|
|
705
|
+
* nil and there is nothing to open.
|
|
706
|
+
*
|
|
707
|
+
* Firefox: `firefox://` has an empty host, and
|
|
708
|
+
* `Coordinators/Router/RouteBuilder.swift` resolves the host through
|
|
709
|
+
* `DeeplinkInput.Host(rawValue: "")`, which is nil because no case has an empty
|
|
710
|
+
* raw value (`Coordinators/Router/DeeplinkInput.swift`). The scheme is not http
|
|
711
|
+
* either, so `makeRoute(url:)` falls to its final `else { return nil }`, and
|
|
712
|
+
* `SceneDelegate.handleOpenURL` then hits `guard let route = ... else
|
|
713
|
+
* { return }` and does nothing at all. Verified on the warm path — the browser
|
|
714
|
+
* is already running, which is always true here, because the user came from a
|
|
715
|
+
* page in it.
|
|
716
|
+
*
|
|
717
|
+
* Everything else deliberately returns undefined, and that is not a gap:
|
|
718
|
+
*
|
|
719
|
+
* - Safari has no equivalent. No scheme means "just come forward";
|
|
720
|
+
* `https://<url>` navigates and opens a new tab, `x-web-search://` opens a
|
|
721
|
+
* search page. Both lose the user's page.
|
|
722
|
+
* - Brave and Arc for iOS are UNDETECTABLE. Brave ships no UA token and
|
|
723
|
+
* identifies itself only via Client Hints, which WebKit does not implement,
|
|
724
|
+
* so it looks exactly like Safari. Inferring "Safari" by elimination and
|
|
725
|
+
* firing a Safari-ish scheme would throw Brave and Arc users into a browser
|
|
726
|
+
* they were not using — strictly worse than doing nothing.
|
|
727
|
+
* - In-app webviews (KakaoTalk, Naver, Line, Instagram) are not tabs at all.
|
|
728
|
+
* Any scheme destroys the page; the system "< Back to X" breadcrumb is the
|
|
729
|
+
* only thing that preserves it.
|
|
730
|
+
* - Edge (`EdgiOS`) and Opera (`OPiOS` / `OPT`) for iOS are NOT included.
|
|
731
|
+
* Both are closed source, so `microsoft-edge://` and `opera-http://` cannot
|
|
732
|
+
* be checked the way Chrome's and Firefox's were. Their bare schemes are
|
|
733
|
+
* plausible analogues, and that is exactly why they stay out: an unverified
|
|
734
|
+
* guess that opens a new tab is the bug this field exists to avoid. Add one
|
|
735
|
+
* only with evidence that it foregrounds WITHOUT navigating.
|
|
736
|
+
* - Android needs nothing: the ZKProofport app brings itself to the back
|
|
737
|
+
* (`moveTaskToBack`), which resumes the browser exactly as it was.
|
|
738
|
+
*
|
|
739
|
+
* When this returns undefined the field is omitted and the ZKProofport app
|
|
740
|
+
* tells the user the proof was delivered and to switch back themselves.
|
|
741
|
+
*
|
|
742
|
+
* @param userAgent - override, for tests. Defaults to `navigator.userAgent`.
|
|
743
|
+
* Returns undefined when there is no navigator at all (Node, SSR).
|
|
744
|
+
* @returns a scheme that is safe to open, or undefined to send nothing
|
|
745
|
+
*
|
|
746
|
+
* @internal
|
|
747
|
+
*/
|
|
748
|
+
function detectReturnScheme(userAgent) {
|
|
749
|
+
const ua = (typeof navigator === 'undefined' ? undefined : navigator.userAgent);
|
|
750
|
+
if (typeof ua !== 'string' || ua.length === 0) {
|
|
751
|
+
return undefined;
|
|
752
|
+
}
|
|
753
|
+
// A device token is required alongside the browser token. `CriOS` and `FxiOS`
|
|
754
|
+
// are already iOS-only and unambiguous on their own, but pairing them with the
|
|
755
|
+
// device token costs nothing and stops a synthetic or spoofed desktop user
|
|
756
|
+
// agent from producing a mobile-only scheme.
|
|
757
|
+
if (!/(iPhone|iPad|iPod)/.test(ua)) {
|
|
758
|
+
return undefined;
|
|
759
|
+
}
|
|
760
|
+
if (/CriOS\//.test(ua)) {
|
|
761
|
+
return 'googlechrome://';
|
|
762
|
+
}
|
|
763
|
+
if (/FxiOS\//.test(ua)) {
|
|
764
|
+
return 'firefox://';
|
|
765
|
+
}
|
|
766
|
+
return undefined;
|
|
767
|
+
}
|
|
673
768
|
function validateProofRequest(request) {
|
|
674
769
|
if (!request.requestId) {
|
|
675
770
|
return { valid: false, error: 'Missing requestId' };
|
|
@@ -5091,8 +5186,13 @@ class ProofportSDK {
|
|
|
5091
5186
|
body.dappIcon = options.dappIcon;
|
|
5092
5187
|
if (options.nonce)
|
|
5093
5188
|
body.nonce = options.nonce;
|
|
5094
|
-
|
|
5095
|
-
|
|
5189
|
+
// An explicit value always wins. Otherwise ask the SDK to work one out from
|
|
5190
|
+
// the page's own user agent — the requesting page is the only side that can
|
|
5191
|
+
// (iOS tells the app nothing about who launched it). Both paths can end in
|
|
5192
|
+
// "send nothing", which is a legitimate outcome, not a failure.
|
|
5193
|
+
const returnScheme = options.returnScheme ?? detectReturnScheme();
|
|
5194
|
+
if (returnScheme)
|
|
5195
|
+
body.returnScheme = returnScheme;
|
|
5096
5196
|
const response = await fetch(`${this.relayUrl}/api/v1/proof/request`, {
|
|
5097
5197
|
method: 'POST',
|
|
5098
5198
|
headers: {
|
|
@@ -5396,8 +5496,10 @@ class ProofportSDK {
|
|
|
5396
5496
|
* @param options - Request options (message, dappName, dappIcon, nonce, returnScheme)
|
|
5397
5497
|
* @param options.returnScheme - Which app the ZKProofport app should switch back
|
|
5398
5498
|
* to once it has finished. Put **your own** deep link scheme here — the bare
|
|
5399
|
-
* scheme your app registers, e.g. `'mydapp://'`.
|
|
5400
|
-
*
|
|
5499
|
+
* scheme your app registers, e.g. `'mydapp://'`. Only a native app has an
|
|
5500
|
+
* answer to this question; a web app omits the option entirely, because
|
|
5501
|
+
* opening its https origin would launch a new browser tab on a fresh page
|
|
5502
|
+
* rather than return the user to the tab they started from.
|
|
5401
5503
|
*
|
|
5402
5504
|
* This is intentionally **not** a URL and not a return address: no path, query
|
|
5403
5505
|
* string or fragment is accepted, and the ZKProofport app never sends the proof
|
|
@@ -5406,14 +5508,26 @@ class ProofportSDK {
|
|
|
5406
5508
|
* "which app should come back to the foreground", exactly like the
|
|
5407
5509
|
* `redirect.native` value a wallet uses to hand control back after signing.
|
|
5408
5510
|
*
|
|
5409
|
-
* **Omit it and
|
|
5410
|
-
*
|
|
5411
|
-
*
|
|
5511
|
+
* **Omit it and the SDK decides for you.** When the request is created from a
|
|
5512
|
+
* page running in Chrome for iOS (`CriOS`) or Firefox for iOS (`FxiOS`), the
|
|
5513
|
+
* SDK fills in `'googlechrome://'` or `'firefox://'` by itself: opening either
|
|
5514
|
+
* scheme bare brings that browser forward without navigating, so the user lands
|
|
5515
|
+
* back on the tab they were already reading. Every other browser sends nothing,
|
|
5516
|
+
* because nothing safe exists to send — Safari has no such scheme, and Brave
|
|
5517
|
+
* and Arc are indistinguishable from Safari on iOS, so guessing would eject the
|
|
5518
|
+
* user into the wrong browser.
|
|
5519
|
+
* On Android nothing is needed at all: the ZKProofport app brings itself to the
|
|
5520
|
+
* back and the browser resumes exactly as it was. When no target is sent and
|
|
5521
|
+
* none can be inferred, the ZKProofport app tells the user the proof was
|
|
5522
|
+
* delivered and lets them switch back themselves.
|
|
5523
|
+
*
|
|
5524
|
+
* There is no auto-switch on proof failure; only on success and on the user
|
|
5525
|
+
* declining the request.
|
|
5412
5526
|
*
|
|
5413
5527
|
* Rejected with an error before the request is sent: empty or whitespace values,
|
|
5414
|
-
* anything longer than 128 characters, values
|
|
5415
|
-
*
|
|
5416
|
-
* `mailto:` families.
|
|
5528
|
+
* anything longer than 128 characters, values carrying a host/path/query/fragment
|
|
5529
|
+
* (including https origins such as `'https://myapp.com'`), and the `http:`,
|
|
5530
|
+
* `file:`, `data:`, `javascript:`, `intent:`, `tel:`, `sms:`, `mailto:` families.
|
|
5417
5531
|
* @returns Promise resolving to RelayProofRequest with requestId, deepLink, pollUrl
|
|
5418
5532
|
* @throws Error if signer not set, returnScheme is malformed, or the relay request fails
|
|
5419
5533
|
*
|