@solarpunkltd/swarm-id 0.8.2 → 0.10.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 (35) hide show
  1. package/README.md +17 -7
  2. package/dist/bus/account-bus.d.ts +12 -0
  3. package/dist/bus/messages.d.ts +12 -0
  4. package/dist/bus/signaling-transport.d.ts +2 -0
  5. package/dist/chunks/ccip-BEanftc1.js +2 -0
  6. package/dist/chunks/ccip-BEanftc1.js.map +1 -0
  7. package/dist/chunks/{chat-binding-DgECjSWW.js → chat-binding-gSvKTm5l.js} +15 -15
  8. package/dist/chunks/chat-binding-gSvKTm5l.js.map +1 -0
  9. package/dist/chunks/index-BbRV2bja.js +2 -0
  10. package/dist/chunks/index-BbRV2bja.js.map +1 -0
  11. package/dist/index.d.ts +5 -1
  12. package/dist/internal.d.ts +3 -1
  13. package/dist/internal.esm.js +1 -1
  14. package/dist/internal.esm.js.map +1 -1
  15. package/dist/schemas.d.ts +59 -0
  16. package/dist/swarm-id-button-frame.d.ts +77 -0
  17. package/dist/swarm-id-client.d.ts +64 -69
  18. package/dist/swarm-id-proxy.d.ts +47 -55
  19. package/dist/swarm-id.esm.js +1 -1
  20. package/dist/swarm-id.esm.js.map +1 -1
  21. package/dist/sync/device-state.d.ts +16 -7
  22. package/dist/sync/index.d.ts +2 -1
  23. package/dist/sync/published-wallet-link.d.ts +27 -0
  24. package/dist/types.d.ts +374 -64
  25. package/dist/utils/button-view.d.ts +16 -0
  26. package/dist/utils/ens-name.d.ts +33 -0
  27. package/dist/utils/ens.d.ts +13 -0
  28. package/dist/utils/json-rpc.d.ts +5 -0
  29. package/dist/utils/personal-sign.d.ts +17 -0
  30. package/dist/utils/postage-stamp-association.d.ts +24 -25
  31. package/dist/utils/site-allow-list.d.ts +0 -4
  32. package/dist/utils/storage-probe.d.ts +20 -10
  33. package/dist/utils/wallet-link.d.ts +70 -0
  34. package/package.json +2 -1
  35. package/dist/chunks/chat-binding-DgECjSWW.js.map +0 -1
package/dist/schemas.d.ts CHANGED
@@ -59,6 +59,34 @@ export declare const ChatBindingSchema: z.ZodObject<{
59
59
  message: z.ZodString;
60
60
  signature: z.ZodString;
61
61
  }, z.core.$strip>;
62
+ /**
63
+ * Longer than any wallet link message can be: five lines, 258 characters,
64
+ * around two addresses and a date. A bound on what is stored and carried, not
65
+ * a check of the format, which `verifyWalletLink` does.
66
+ */
67
+ export declare const WALLET_LINK_MAX_LENGTH = 320;
68
+ /**
69
+ * The account's and its wallet's statement that they belong together
70
+ * (`utils/wallet-link.ts`, docs/Wallet-Link.md). Kept in the account's synced
71
+ * settings while the person has "Show my ENS name on sites" on, and shared
72
+ * with sites in the connection identity.
73
+ */
74
+ export declare const WalletLinkSchema: z.ZodObject<{
75
+ message: z.ZodString;
76
+ walletSignature: z.ZodString;
77
+ accountSignature: z.ZodString;
78
+ }, z.core.$strip>;
79
+ /**
80
+ * The account's synced settings, one shape for the account record, the state
81
+ * snapshot and the device feed, so a setting one of them does not name is
82
+ * never stripped on the way to another device. The whole object is one
83
+ * last-writer-wins field (`settingsAt`). The wallet link is not in it: it
84
+ * has a clock of its own (`walletLinkAt`), so another device's settings edit
85
+ * can never bring back a link that was turned off.
86
+ */
87
+ export declare const AccountSettingsSchema: z.ZodObject<{
88
+ appSessionDuration: z.ZodOptional<z.ZodNumber>;
89
+ }, z.core.$strip>;
62
90
  /** The stamp choice of an app the person set to use no stamp at all */
63
91
  export declare const NO_POSTAGE_STAMP = "none";
64
92
  /**
@@ -236,9 +264,15 @@ export declare const SyncedAccountSchemaV1: z.ZodObject<{
236
264
  settings: z.ZodOptional<z.ZodObject<{
237
265
  appSessionDuration: z.ZodOptional<z.ZodNumber>;
238
266
  }, z.core.$strip>>;
267
+ walletLink: z.ZodOptional<z.ZodObject<{
268
+ message: z.ZodString;
269
+ walletSignature: z.ZodString;
270
+ accountSignature: z.ZodString;
271
+ }, z.core.$strip>>;
239
272
  accountNameAt: z.ZodOptional<z.ZodNumber>;
240
273
  defaultStampAt: z.ZodOptional<z.ZodNumber>;
241
274
  settingsAt: z.ZodOptional<z.ZodNumber>;
275
+ walletLinkAt: z.ZodOptional<z.ZodNumber>;
242
276
  lastModified: z.ZodOptional<z.ZodNumber>;
243
277
  partitionCount: z.ZodOptional<z.ZodNumber>;
244
278
  }, z.core.$strip>;
@@ -303,9 +337,15 @@ declare const SignedInAccountSchemaV1: z.ZodObject<{
303
337
  settings: z.ZodOptional<z.ZodObject<{
304
338
  appSessionDuration: z.ZodOptional<z.ZodNumber>;
305
339
  }, z.core.$strip>>;
340
+ walletLink: z.ZodOptional<z.ZodObject<{
341
+ message: z.ZodString;
342
+ walletSignature: z.ZodString;
343
+ accountSignature: z.ZodString;
344
+ }, z.core.$strip>>;
306
345
  accountNameAt: z.ZodOptional<z.ZodNumber>;
307
346
  defaultStampAt: z.ZodOptional<z.ZodNumber>;
308
347
  settingsAt: z.ZodOptional<z.ZodNumber>;
348
+ walletLinkAt: z.ZodOptional<z.ZodNumber>;
309
349
  lastModified: z.ZodOptional<z.ZodNumber>;
310
350
  partitionCount: z.ZodOptional<z.ZodNumber>;
311
351
  signedOutAt: z.ZodOptional<z.ZodUndefined>;
@@ -428,9 +468,15 @@ export declare const LocalAccountSchemaV1: z.ZodUnion<readonly [z.ZodObject<{
428
468
  settings: z.ZodOptional<z.ZodObject<{
429
469
  appSessionDuration: z.ZodOptional<z.ZodNumber>;
430
470
  }, z.core.$strip>>;
471
+ walletLink: z.ZodOptional<z.ZodObject<{
472
+ message: z.ZodString;
473
+ walletSignature: z.ZodString;
474
+ accountSignature: z.ZodString;
475
+ }, z.core.$strip>>;
431
476
  accountNameAt: z.ZodOptional<z.ZodNumber>;
432
477
  defaultStampAt: z.ZodOptional<z.ZodNumber>;
433
478
  settingsAt: z.ZodOptional<z.ZodNumber>;
479
+ walletLinkAt: z.ZodOptional<z.ZodNumber>;
434
480
  lastModified: z.ZodOptional<z.ZodNumber>;
435
481
  partitionCount: z.ZodOptional<z.ZodNumber>;
436
482
  signedOutAt: z.ZodOptional<z.ZodUndefined>;
@@ -477,9 +523,15 @@ export declare const AccountMetadataSchemaV1: z.ZodObject<{
477
523
  settings: z.ZodOptional<z.ZodObject<{
478
524
  appSessionDuration: z.ZodOptional<z.ZodNumber>;
479
525
  }, z.core.$strip>>;
526
+ walletLink: z.ZodOptional<z.ZodObject<{
527
+ message: z.ZodString;
528
+ walletSignature: z.ZodString;
529
+ accountSignature: z.ZodString;
530
+ }, z.core.$strip>>;
480
531
  accountNameAt: z.ZodOptional<z.ZodNumber>;
481
532
  defaultStampAt: z.ZodOptional<z.ZodNumber>;
482
533
  settingsAt: z.ZodOptional<z.ZodNumber>;
534
+ walletLinkAt: z.ZodOptional<z.ZodNumber>;
483
535
  createdAt: z.ZodNumber;
484
536
  lastModified: z.ZodNumber;
485
537
  devices: z.ZodDefault<z.ZodArray<z.ZodObject<{
@@ -511,9 +563,15 @@ export declare const AccountStateSnapshotSchemaV1: z.ZodObject<{
511
563
  settings: z.ZodOptional<z.ZodObject<{
512
564
  appSessionDuration: z.ZodOptional<z.ZodNumber>;
513
565
  }, z.core.$strip>>;
566
+ walletLink: z.ZodOptional<z.ZodObject<{
567
+ message: z.ZodString;
568
+ walletSignature: z.ZodString;
569
+ accountSignature: z.ZodString;
570
+ }, z.core.$strip>>;
514
571
  accountNameAt: z.ZodOptional<z.ZodNumber>;
515
572
  defaultStampAt: z.ZodOptional<z.ZodNumber>;
516
573
  settingsAt: z.ZodOptional<z.ZodNumber>;
574
+ walletLinkAt: z.ZodOptional<z.ZodNumber>;
517
575
  createdAt: z.ZodNumber;
518
576
  lastModified: z.ZodNumber;
519
577
  devices: z.ZodDefault<z.ZodArray<z.ZodObject<{
@@ -572,6 +630,7 @@ export type SignedOutAccount = z.infer<typeof SignedOutAccountSchemaV1>;
572
630
  /** The local (on-device) account record: `SignedInAccount | SignedOutAccount`. */
573
631
  export type Account = z.infer<typeof LocalAccountSchemaV1>;
574
632
  export type ConnectedApp = z.infer<typeof ConnectedAppSchemaV1>;
633
+ export type AccountSettings = z.infer<typeof AccountSettingsSchema>;
575
634
  export type PostageStamp = z.infer<typeof PostageStampSchemaV1>;
576
635
  export type AccountMetadata = z.infer<typeof AccountMetadataSchemaV1>;
577
636
  export type AccountStateSnapshot = z.infer<typeof AccountStateSnapshotSchemaV1>;
@@ -0,0 +1,77 @@
1
+ import type { SiteAllowList } from "./utils/site-allow-list";
2
+ export interface ButtonFrameConfig {
3
+ /**
4
+ * The sites this frame serves (docs/Site-Allow-List.md), the same list the
5
+ * session frame asks. A site it refuses gets no button.
6
+ */
7
+ siteAllowList: SiteAllowList;
8
+ /** Take font files over http on localhost, which only a development build does */
9
+ allowLocalHttp?: boolean;
10
+ }
11
+ /**
12
+ * The visible Swarm ID button a site mounts with `SwarmIdClient.mountButton`.
13
+ *
14
+ * It exists because a frame may open a window only from a click inside it.
15
+ * A site on another site than Swarm ID gets a session frame whose storage is
16
+ * partitioned from the connect window's, so the only way in is a handover the
17
+ * window posts to Swarm ID's frames on the page that opened it, and a window
18
+ * the site's page opens has no such page to post to. The click therefore
19
+ * happens here: this frame opens the window and is its opener, and tells the
20
+ * session frame beside it the window's challenge, so it takes the handover
21
+ * that comes back under it (the window side is `ui/src/lib/handover-targets.ts`).
22
+ * Both frames are Swarm ID's origin under the same top-level site, so they
23
+ * share the partition, and nothing passes the site's page. Every check on the
24
+ * handover stays in the session frame (`SwarmIdProxy.handlePopupMessage`).
25
+ */
26
+ export declare class SwarmIdButtonFrame {
27
+ private readonly options;
28
+ private readonly container;
29
+ private parentOrigin;
30
+ private identifyReceived;
31
+ private action;
32
+ private config;
33
+ private metadata;
34
+ private popupMode;
35
+ private storageShared;
36
+ private connectWindow;
37
+ private watchStart;
38
+ private watchInterval;
39
+ private button;
40
+ /** Font files asked for, so each is fetched, and reported, once */
41
+ private readonly requestedFonts;
42
+ private readonly onMessage;
43
+ constructor(options: ButtonFrameConfig, container: HTMLElement);
44
+ destroy(): void;
45
+ private handleMessage;
46
+ private handleIdentify;
47
+ private handleUpdate;
48
+ /**
49
+ * Loads the site's font files into this frame. The label already names the
50
+ * family, so it changes once a file arrives, and keeps the fallback family
51
+ * when one does not. The fetch is cross-origin, so the site serves the file
52
+ * with an `Access-Control-Allow-Origin` naming Swarm ID's origin.
53
+ */
54
+ private loadFontFaces;
55
+ private render;
56
+ /**
57
+ * Opens the connect window from the person's click, which is what lets the
58
+ * browser open it at all. The window looks for the challenge in its own
59
+ * storage to learn whether the session frame reads that storage, so it goes
60
+ * there first only when this frame can open what the first-party pages
61
+ * sealed (`isStorageShared`). Otherwise the window hands the session over,
62
+ * and the session frames are told the challenge once the window is open, so
63
+ * they take the handover that comes back under it.
64
+ */
65
+ private openConnectWindow;
66
+ private watchWindow;
67
+ private stopWatchingWindow;
68
+ /**
69
+ * Every other frame of the parent page, on Swarm ID's own origin only, so
70
+ * the browser delivers it to Swarm ID's frames and drops it for the site's.
71
+ */
72
+ private tellSessionFrames;
73
+ private tellWindowState;
74
+ private tellParent;
75
+ }
76
+ /** Starts the button frame, as `initProxy` starts the session frame */
77
+ export declare function initButtonFrame(config: ButtonFrameConfig, container: HTMLElement): SwarmIdButtonFrame;
@@ -1,4 +1,4 @@
1
- import type { ClientOptions, ConnectOptions, AuthStatus, Avatar, ConnectionInfo, UploadResult, FileData, UploadOptions, UploadFilesOptions, CollectionFileInput, CollectionEntry, ActUploadOptions, DownloadOptions, RequestOptions, Reference, SOCReader, SOCWriter, FeedReaderOptions, FeedWriterOptions, FeedReader, FeedWriter, SequentialFeedReaderOptions, SequentialFeedWriterOptions, SequentialFeedReader, SequentialFeedWriter, StampChunkInput, StampedChunk, PostageBatch } from "./types";
1
+ import type { ClientOptions, ConnectOptions, AuthStatus, Avatar, ConnectionInfo, UploadResult, FileData, UploadOptions, UploadFilesOptions, CollectionFileInput, CollectionEntry, ActUploadOptions, DownloadOptions, RequestOptions, Reference, SOCReader, SOCWriter, FeedReaderOptions, FeedWriterOptions, FeedReader, FeedWriter, SequentialFeedReaderOptions, SequentialFeedWriterOptions, SequentialFeedReader, SequentialFeedWriter, StampChunkInput, StampedChunk, MountButtonOptions, MountedButton, PostageBatch } from "./types";
2
2
  import { EthAddress, PrivateKey } from "@ethersphere/bee-js";
3
3
  /**
4
4
  * Main client library for integrating Swarm ID authentication and storage capabilities
@@ -45,8 +45,8 @@ export declare class SwarmIdClient {
45
45
  private iframeLoadReject?;
46
46
  private popupMode;
47
47
  private metadata;
48
- private buttonConfig?;
49
- private containerId?;
48
+ /** Each mounted button, with what removes its frame at once */
49
+ private mountedButtons;
50
50
  private subsidisedGatewayUrl?;
51
51
  private beeApiUrl?;
52
52
  private ready;
@@ -80,22 +80,11 @@ export declare class SwarmIdClient {
80
80
  * @param options.iframePath - The path to the proxy iframe (defaults to "/proxy")
81
81
  * @param options.timeout - Request timeout in milliseconds (defaults to 30000). Bounds each call's whole round trip through the iframe; a call's own `requestOptions.timeout` replaces it for that call
82
82
  * @param options.onConnectionChange - Callback invoked when the dApp-visible ConnectionInfo changes (identity, stamp, account type, auth)
83
- * @param options.popupMode - How to display the authentication popup: "popup" or "window" (defaults to "window")
83
+ * @param options.popupMode - How the connect window opens, from {@link connect} or a mounted button: "popup" or "window" (defaults to "window")
84
84
  * @param options.metadata - Application metadata shown to users during authentication
85
85
  * @param options.metadata.name - Application name (1-100 characters)
86
86
  * @param options.metadata.description - Optional application description (max 500 characters)
87
87
  * @param options.metadata.icon - Optional application icon as a data URL (SVG or PNG, max 4KB)
88
- * @param options.buttonConfig - Button configuration for the authentication UI (optional)
89
- * @param options.buttonConfig.connectText - Text for the connect button (optional)
90
- * @param options.buttonConfig.disconnectText - Text for the disconnect button (optional)
91
- * @param options.buttonConfig.loadingText - Text shown during loading (optional)
92
- * @param options.buttonConfig.backgroundColor - Background color for buttons (optional)
93
- * @param options.buttonConfig.color - Text color for buttons (optional)
94
- * @param options.buttonConfig.borderRadius - Border radius for buttons and iframe (optional)
95
- * @param options.buttonConfig.fontFamily - CSS font-family for the button, so it can match the embedding page; a font stack falls back as usual (optional)
96
- * @param options.buttonConfig.fontSize - CSS font-size for the button; the default floating iframe is a fixed 300x50 px and hides its overflow, so a size that does not fit is clipped — pass `containerId` to own the box (optional)
97
- * @param options.buttonConfig.fontWeight - CSS font-weight for the button, as a keyword or a number (optional)
98
- * @param options.containerId - ID of container element to place iframe in (optional)
99
88
  * @param options.subsidisedGatewayUrl - A gateway that stamps uploads for a person with no drive of their own (optional)
100
89
  * @param options.beeApiUrl - The Bee node this site's content reads and stamped uploads go through, in place of the person's own setting. Their account and device coordination stay on their own node. Change it later with {@link setBeeApiUrl} (optional)
101
90
  * @throws {Error} If constructed where there is no `window` (a server render)
@@ -112,7 +101,6 @@ export declare class SwarmIdClient {
112
101
  *
113
102
  * @returns A promise that resolves when the client is fully initialized
114
103
  * @throws {SwarmIdError} `invalid-state` if the client is already initialized, or `destroy()` runs before it resolves
115
- * @throws {SwarmIdError} `invalid-request` if `containerId` names no element
116
104
  * @throws {SwarmIdError} `init-failed` if the iframe fails to load, origin validation fails on the proxy side, or the proxy does not respond within `initializationTimeout` (30 seconds by default)
117
105
  *
118
106
  * @example
@@ -172,25 +160,6 @@ export declare class SwarmIdClient {
172
160
  * Ensure client is initialized
173
161
  */
174
162
  private ensureReady;
175
- /**
176
- * Returns the authentication iframe element.
177
- *
178
- * The iframe displays authentication UI based on the current auth status:
179
- * - If not authenticated: shows a "Connect" button
180
- * - If authenticated: shows identity info and a "Disconnect" button
181
- *
182
- * The iframe is positioned fixed in the bottom-right corner of the viewport.
183
- *
184
- * @returns The iframe element displaying the authentication UI
185
- * @throws {SwarmIdError} `invalid-state` if the client is not initialized, or the iframe is not available
186
- *
187
- * @example
188
- * ```typescript
189
- * const iframe = client.getAuthIframe()
190
- * // The iframe is already displayed; this returns a reference to it
191
- * ```
192
- */
193
- getAuthIframe(): HTMLIFrameElement;
194
163
  /**
195
164
  * Checks the current authentication status with the Swarm ID proxy.
196
165
  *
@@ -280,52 +249,78 @@ export declare class SwarmIdClient {
280
249
  */
281
250
  disconnect(): Promise<void>;
282
251
  /**
283
- * Opens the Swarm ID authentication page in a new window.
284
- *
285
- * This method creates the same authentication URL as used by the iframe
286
- * proxy and opens it in a new browser window. The user can authenticate
287
- * with their Swarm ID, and the resulting authentication will be available
288
- * to the client when they return.
289
- *
290
- * **Which window opens it** is decided by the iframe's STORAGE, not by the
291
- * user agent (#613):
292
- * - Shared storage: opened here, from the parent, so the click's activation is
293
- * still unspent. The popup writes the connection to the first-party store
294
- * the iframe reads, and a `storage` event carries it across.
295
- * - Partitioned, or not proven shared: the proxy opens it, so the popup's
296
- * `window.opener` points back at the iframe and the session can be handed
297
- * over directly — the only channel into a partitioned frame. The session
298
- * is then kept in that partition's own store until it is disconnected or
299
- * its 7 days are up (#635), so a reload does not re-run this; a handover
300
- * that arrives without the account view is refused outright rather than
301
- * producing a session that cannot upload and cannot hear a revoke. A popup
302
- * blocked on this path falls back to opening from the parent.
303
- *
304
- * Safari private mode sessions are ephemeral (lost when the private window
305
- * closes). The whole partitioned path — handover, stamper, upload — is
306
- * confirmed on real Safari; see
307
- * https://github.com/snaha/swarm-id/issues/584
252
+ * Opens the Swarm ID connect window from the site's own control, where the
253
+ * session frame shares Swarm ID's storage: a site on the same site as Swarm
254
+ * ID, such as a self-hosted copy on the site's own domain. The window
255
+ * writes the connection to that storage and the session frame picks it up.
256
+ *
257
+ * Anywhere else the browser partitions the session frame's storage, and the
258
+ * session can reach it only through a window a Swarm ID frame opened from a
259
+ * click inside it. A window opened from here would end on "Connected" with
260
+ * the site still signed out, so this refuses instead and the site shows
261
+ * {@link mountButton}'s button. `connectionInfo.storagePartitioned` and the
262
+ * error's code tell the two apart. The button works in both cases, so a
263
+ * site that always mounts it never needs this.
308
264
  *
309
265
  * @param options - Configuration options for the connect flow
310
266
  * @throws {SwarmIdError} `invalid-state` if the client is not initialized
311
- * @throws {SwarmIdError} `popup-blocked` if the browser blocks the popup
267
+ * @throws {SwarmIdError} `button-required` if the session frame's storage is partitioned, or not proven shared
268
+ * @throws {SwarmIdError} `popup-blocked` if the browser blocks the window
312
269
  *
313
270
  * @example
314
271
  * ```typescript
315
- * const client = new SwarmIdClient({ ... })
316
- * await client.initialize()
317
- *
318
- * // Open authentication page
319
- * await client.connect()
272
+ * try {
273
+ * await client.connect()
274
+ * } catch (error) {
275
+ * if (error instanceof SwarmIdError && error.code === 'button-required') {
276
+ * client.mountButton(container, { action: 'sign-in' })
277
+ * }
278
+ * }
320
279
  * ```
321
280
  */
322
281
  connect(options?: ConnectOptions): Promise<void>;
323
282
  /**
324
- * Open the auth popup from the dApp page.
283
+ * Shows a Swarm ID button in `container`: a small frame of Swarm ID's own,
284
+ * apart from the hidden session frame, that fills the container. Its click
285
+ * opens the connect window, which hands the session to the site's Swarm ID
286
+ * frames, and the site hears of it on {@link ClientOptions.onConnectionChange} as from
287
+ * any sign-in. This is how a person signs in, or changes the stamp the
288
+ * site uses, wherever the site and Swarm ID are on different sites.
289
+ *
290
+ * Unmounting removes only the button, at any time. The session stays, and
291
+ * signing out stays {@link disconnect}. A button unmounted while its window
292
+ * is open stays hidden until the window closes, because the window reads
293
+ * from it, as it opens, where to hand the session. Unmounting it, or
294
+ * removing its container, in the moment between the click and the window
295
+ * opening loses that sign-in, and the person signs in again.
296
+ *
297
+ * @param container - The element the button fills. Give it the size the button should have
298
+ * @param options.action - `sign-in`, or `change-stamp` for a person signed in
299
+ * @param options.config - The label, its busy and disabled states, colours and typography
300
+ * @param options.onWindowChange - Called with `open`, `closed` or `blocked` for the button's connect window
301
+ * @returns The button: `ready`, `update()` and `unmount()`, where `update()` refuses a bad value the same way
302
+ * @throws {SwarmIdError} `invalid-state` if the client is not initialized
303
+ * @throws {SwarmIdError} `invalid-request` if `action` or a `config` value is not one the button takes, naming the field
304
+ *
305
+ * @example
306
+ * ```typescript
307
+ * const button = client.mountButton(document.getElementById('sign-in')!, {
308
+ * action: 'sign-in',
309
+ * config: { text: 'Sign in', backgroundColor: '#000', borderRadius: '6px' },
310
+ * })
311
+ * await button.ready
312
+ * // later, closing the panel it sits in
313
+ * button.unmount()
314
+ * ```
315
+ */
316
+ mountButton(container: HTMLElement, options: MountButtonOptions): MountedButton;
317
+ /**
318
+ * Open the connect window from the site's page, for a session frame that
319
+ * shares Swarm ID's storage.
325
320
  *
326
- * Carries no challenge: a popup opened here has the dApp page as its
321
+ * Carries no challenge: a window opened here has the site's page as its
327
322
  * `window.opener`, so there is nothing for a handover to attach to, and the
328
- * popup falls back to the storage write it does anyway.
323
+ * window writes the connection to the storage the session frame reads.
329
324
  *
330
325
  * @returns whether the popup opened — a blocked one returns null, which the
331
326
  * caller must not report as a connect in progress.
@@ -8,7 +8,8 @@ export declare const PUBLISH_DEBOUNCE_MS = 1500;
8
8
  * Swarm ID Proxy - Runs inside the iframe
9
9
  *
10
10
  * Responsibilities:
11
- * - Receive app-specific secrets from auth popup
11
+ * - Receive app-specific secrets from the connect popup the button frame beside
12
+ * it opened, under the challenge that frame told it
12
13
  * - Keep a partitioned session in this partition's own localStorage (#635)
13
14
  * - Proxy Bee API calls from parent dApp
14
15
  * - Augment requests with authentication
@@ -25,7 +26,6 @@ export declare class SwarmIdProxy {
25
26
  private parentIdentifyReceived;
26
27
  private siteAllowList;
27
28
  private authenticated;
28
- private authLoading;
29
29
  private appSecret;
30
30
  /**
31
31
  * The chat binding the popup handed over with this session, already checked
@@ -65,6 +65,12 @@ export declare class SwarmIdProxy {
65
65
  * first-class writer despite the partition (docs/Account-Bus.md, phase 3).
66
66
  */
67
67
  private partitionAccount;
68
+ /**
69
+ * Set while a restored session's saved wallet link is unconfirmed, and kept
70
+ * out of the identity until then (docs/Wallet-Link.md). Cleared by the
71
+ * account's published state, a peer's `account-delta` or a new handover.
72
+ */
73
+ private walletLinkUnconfirmed;
68
74
  /** Account-derived topic the attached bus transports share; undefined when
69
75
  * none are attached. */
70
76
  private busTopic;
@@ -127,9 +133,6 @@ export declare class SwarmIdProxy {
127
133
  private gnosisRpcUrl;
128
134
  private postageStampContractAddress;
129
135
  private signalingUrl;
130
- private authButtonContainer;
131
- private buttonConfig;
132
- private popupMode;
133
136
  private appMetadata;
134
137
  /**
135
138
  * Bee clients by the URL they were asked for. Keyed on that input URL, not
@@ -139,7 +142,6 @@ export declare class SwarmIdProxy {
139
142
  private unsubscribeStorageListeners;
140
143
  private storageWorkQueue;
141
144
  private lastConnectionInfo;
142
- private isConnecting;
143
145
  private parentWindow;
144
146
  private bus;
145
147
  /** Who else is live in the account's room, from their `presence` beats. */
@@ -258,7 +260,8 @@ export declare class SwarmIdProxy {
258
260
  * Note: We always set up these listeners, even when storage might be partitioned.
259
261
  * In some browsers/configurations (like localhost development), storage events
260
262
  * work between same-origin windows even in iframes. If storage IS partitioned,
261
- * the listeners simply won't fire, and we fall back to postMessage from the popup.
263
+ * the listeners simply won't fire, and the connect popup the button frame
264
+ * opened hands the session over by postMessage instead.
262
265
  */
263
266
  private setupStorageListeners;
264
267
  /**
@@ -338,10 +341,22 @@ export declare class SwarmIdProxy {
338
341
  */
339
342
  private authenticateFromStorage;
340
343
  /**
341
- * Handle messages from the auth popup (same-origin postMessage).
342
- * Used as storage partitioning fallback when storage events don't fire due to partitioning.
344
+ * A handover from a connect window the button frame beside this one opened.
345
+ * The only way into a session whose storage is partitioned from the
346
+ * window's, where no storage event arrives.
343
347
  */
344
348
  private handlePopupMessage;
349
+ /**
350
+ * Whether a message came from another frame of the page this one is in,
351
+ * which is where the button frame sits. A top-level window is not one.
352
+ */
353
+ private isSiblingFrame;
354
+ /**
355
+ * The button frame beside this one opened a connect window under
356
+ * `challenge`, so the handover that comes back under it is this session's
357
+ * to take. A window opened before it can no longer hand over a stamp.
358
+ */
359
+ private awaitHandover;
345
360
  /**
346
361
  * A second handover from the window this session was handed over by,
347
362
  * carrying the stamp the person picked there after the first one. Accepted
@@ -354,24 +369,6 @@ export declare class SwarmIdProxy {
354
369
  private isStampHandover;
355
370
  /** Authenticate in storage-partitioned mode from a checked handover */
356
371
  private adoptHandover;
357
- /**
358
- * End the connect attempt a refused handover belonged to.
359
- *
360
- * The popup cannot be told it was refused — there is no channel back — so it
361
- * shows "Connected!" while the button here spins on, and the only other
362
- * thing that ever cleared the flag was the user closing that popup
363
- * (`checkPopupClosed`), which nothing gave them a reason to do.
364
- *
365
- * `isConnecting` first: `showAuthButton` early-returns while it is set, so
366
- * clearing the flag is what lets the button come back. `authLoading` is
367
- * already false on every path that reaches here — belt-and-braces, not a
368
- * second fix.
369
- *
370
- * The challenge is deliberately kept. The popup is still open and may yet
371
- * send a payload this bundle can read; refusing one message ends the
372
- * attempt's spinner, not the ceremony behind it.
373
- */
374
- private endRefusedConnect;
375
372
  /**
376
373
  * The handed-over chat binding, if it names exactly this parent origin, the
377
374
  * address of the chat key of the secret it came with and the account it came
@@ -404,7 +401,7 @@ export declare class SwarmIdProxy {
404
401
  * Re-persist the stored session's account view after a live fold moved it.
405
402
  *
406
403
  * Without this the record is frozen at the handover: a rotated signer key or
407
- * a moved default stamp arrives over the bus, updates `partitionAccount`, and
404
+ * a moved pick for this site arrives over the bus, updates `partitionAccount`, and
408
405
  * then a reload resurrects the pre-fold copy and uploads with it until the
409
406
  * next live overlap.
410
407
  *
@@ -693,6 +690,20 @@ export declare class SwarmIdProxy {
693
690
  * restored from shared storage does.
694
691
  */
695
692
  private restorePartitionSession;
693
+ /**
694
+ * Take the wallet link the account published when it is newer than the
695
+ * restored copy, and tell the site (docs/Wallet-Link.md).
696
+ *
697
+ * The bus carries a change of "Show my ENS name on sites" to a frame that is
698
+ * open, and keeps no mailbox. Turned off while no page of the site was open,
699
+ * the restored copy would go on handing the site the withdrawn wallet, so
700
+ * the published state decides on the link's own clock. Only the link: the
701
+ * rest of the published account still arrives over the bus.
702
+ *
703
+ * Nothing published, or nothing read, confirms nothing, so the restored link
704
+ * stays held back.
705
+ */
706
+ private foldPublishedWalletLink;
696
707
  /**
697
708
  * Ask the account's published state whether this restored session was ended
698
709
  * while no page of the site was open. Ended means what it means
@@ -763,10 +774,11 @@ export declare class SwarmIdProxy {
763
774
  * `buildAccountStateSnapshotForPublish` below — reusing that one would cost
764
775
  * two key derivations to send a bus message.
765
776
  *
766
- * `requireDefaultStamp` is the FEED path's precondition, not the snapshot's:
767
- * that write is paid for by the default stamp, while a bus message costs
768
- * nothing to send and `metadata.defaultPostageStampBatchID` is optional on
769
- * the wire. An account with no drives is a supported connected state, and
777
+ * `requireDefaultStamp` is the FEED path's precondition, not the snapshot's.
778
+ * That write is paid from the drive the person picked for this site, through
779
+ * the coordinator built on the site's resolved stamp, and not from the
780
+ * account default this flag checks. A bus message costs nothing to send and
781
+ * `metadata.defaultPostageStampBatchID` is optional on the wire. An account with no drives is a supported connected state, and
770
782
  * gating the delta on a stamp is how such an account's revokes never reach
771
783
  * its partitioned sessions at all.
772
784
  */
@@ -804,7 +816,9 @@ export declare class SwarmIdProxy {
804
816
  * and to the shared feed via the coordinator (same write lock + held
805
817
  * partition). The feed write needs a partition; the bus message does not, so
806
818
  * it goes out first and a read-only session still relays to peers that
807
- * cannot see storage at all. Single-device accounts' feeds are published by
819
+ * cannot see storage at all. The feed write is paid from the site's picked
820
+ * drive, through the coordinator, and a site with no pick has no coordinator
821
+ * and writes none. Single-device accounts' feeds are published by
808
822
  * the SwarmID UI. Re-arms if a publish is already in flight so the latest
809
823
  * state still lands.
810
824
  */
@@ -923,28 +937,6 @@ export declare class SwarmIdProxy {
923
937
  private handleIsConnected;
924
938
  private handleGetNodeInfo;
925
939
  private handleDisconnect;
926
- /**
927
- * Show authentication button in the UI
928
- */
929
- private showAuthButton;
930
- private handleConnect;
931
- /**
932
- * Open the authentication popup window.
933
- * Returns true if popup was opened, false if parent origin is not set.
934
- */
935
- private openAuthPopup;
936
- /**
937
- * Handle login button click
938
- */
939
- private handleLoginClick;
940
- /**
941
- * Handle disconnect button click
942
- */
943
- private handleDisconnectClick;
944
- /**
945
- * Set container element for auth button
946
- */
947
- setAuthButtonContainer(container: HTMLElement): void;
948
940
  private handleUploadData;
949
941
  private handleDownloadData;
950
942
  private handleUploadFile;