|
| 1 | +# Migration Guide: v0.7 → v0.8 |
| 2 | + |
| 3 | +This guide covers all breaking changes and new APIs introduced in v0.8, organized by feature area. |
| 4 | + |
| 5 | +## Overview of Breaking Changes |
| 6 | + |
| 7 | +- [Multi-device SSO V2 wire protocol](#multi-device-sso-v2) — V1 SSO handshake is gone; both ends must run V2; persisted V1 sessions are dropped on first read |
| 8 | +- [`createPappAdapter` parameter change](#createpappadapter-parameter-change) — `metadata: string` URL removed; host name / icon / platform now ride inline on `hostMetadata` |
| 9 | +- [`HostMetadata` reshape](#hostmetadata-reshape) — `hostVersion / osType / osVersion` → `hostName / hostVersion / hostIcon / platformType / platformVersion / custom` |
| 10 | +- [`MessageContent.chatAccepted` payload](#messagecontentchataccepted-payload) — chat-accepted variant changed from `_void` to `{ messageId: string }` |
| 11 | + |
| 12 | +--- |
| 13 | + |
| 14 | +## Breaking Changes |
| 15 | + |
| 16 | +### Multi-device SSO V2 |
| 17 | + |
| 18 | +The SSO handshake has been replaced with the V2 multi-device protocol end-to-end. The consumer-facing surface on `pappAdapter.sso` — `pairingStatus`, `authenticate()`, `abortAuthentication()`, the `StoredUserSession` returned to consumers — is unchanged. |
| 19 | + |
| 20 | +There is no V1 ↔ V2 compatibility: |
| 21 | + |
| 22 | +- A host on v0.8 will not pair with a Polkadot Mobile build that only speaks V1. |
| 23 | +- A host on v0.7 will not pair with a multi-device Polkadot Mobile build. |
| 24 | +- Persisted v0.7 SSO session blobs decode short against the V2 codec and are silently dropped on first read — users with existing sessions need to re-pair. |
| 25 | + |
| 26 | +Hosts and the paired Polkadot Mobile app must upgrade together. |
| 27 | + |
| 28 | +--- |
| 29 | + |
| 30 | +### `createPappAdapter` parameter change |
| 31 | + |
| 32 | +The `metadata: string` URL parameter (which pointed at a JSON file describing the host's `name` / `icon`) has been removed. Host identity now ships inline with the V2 QR proposal via the structured `hostMetadata` object, which also gains platform fields. |
| 33 | + |
| 34 | +```ts |
| 35 | +// Before — v0.7 |
| 36 | +createPappAdapter({ |
| 37 | + appId: 'my-host', |
| 38 | + metadata: 'https://example.com/host-metadata.json', |
| 39 | + hostMetadata: { |
| 40 | + hostVersion: '1.2.3', |
| 41 | + osType: 'macOS', |
| 42 | + osVersion: '15.0', |
| 43 | + }, |
| 44 | +}); |
| 45 | + |
| 46 | +// After — v0.8 |
| 47 | +createPappAdapter({ |
| 48 | + appId: 'my-host', |
| 49 | + hostMetadata: { |
| 50 | + hostName: 'My Host', |
| 51 | + hostVersion: '1.2.3', |
| 52 | + hostIcon: 'https://example.com/icon-256.png', |
| 53 | + platformType: 'macOS', |
| 54 | + platformVersion: '15.0', |
| 55 | + }, |
| 56 | +}); |
| 57 | +``` |
| 58 | + |
| 59 | +Hosts that previously served a `host-metadata.json` can drop the file — its contents now ride inline on `hostMetadata` as `hostName` and `hostIcon`. |
| 60 | + |
| 61 | +--- |
| 62 | + |
| 63 | +### `HostMetadata` reshape |
| 64 | + |
| 65 | +```ts |
| 66 | +// Before — v0.7 |
| 67 | +type HostMetadata = { |
| 68 | + hostVersion?: string; |
| 69 | + osType?: string; |
| 70 | + osVersion?: string; |
| 71 | +}; |
| 72 | + |
| 73 | +// After — v0.8 |
| 74 | +type HostMetadata = { |
| 75 | + hostName?: string; |
| 76 | + hostVersion?: string; |
| 77 | + hostIcon?: string; |
| 78 | + platformType?: string; |
| 79 | + platformVersion?: string; |
| 80 | + custom?: { name: string; value: string }[]; |
| 81 | +}; |
| 82 | +``` |
| 83 | + |
| 84 | +Mechanical renames: |
| 85 | + |
| 86 | +| v0.7 | v0.8 | |
| 87 | +|-------------|-------------------| |
| 88 | +| `osType` | `platformType` | |
| 89 | +| `osVersion` | `platformVersion` | |
| 90 | + |
| 91 | +The new optional fields — `hostName`, `hostIcon`, `custom` — replace what used to be fetched from the `metadata` URL and add a place for host-specific extension entries. |
| 92 | + |
| 93 | +--- |
| 94 | + |
| 95 | +### `MessageContent.chatAccepted` payload |
| 96 | + |
| 97 | +The legacy single-device `chatAccepted` variant used to be a `_void` marker. It now carries the originating chat-request message id so the recipient can correlate the accept with its request. |
| 98 | + |
| 99 | +```ts |
| 100 | +// Before — v0.7 |
| 101 | +{ tag: 'chatAccepted', value: undefined } |
| 102 | + |
| 103 | +// After — v0.8 |
| 104 | +{ tag: 'chatAccepted', value: { messageId: '...' } } |
| 105 | +``` |
| 106 | + |
| 107 | +Older clients that emit the v0.7 `_void` shape will fail to decode against the v0.8 codec. |
| 108 | + |
| 109 | +The new multi-device accept lives at a separate index (`deviceChatAccepted`, index 20) — see [Multi-device chat message variants](#multi-device-chat-message-variants) below. |
| 110 | + |
| 111 | +--- |
| 112 | + |
| 113 | +## New Features |
| 114 | + |
| 115 | +### Auto-persisted device identity |
| 116 | + |
| 117 | +The V2 protocol identifies the host by a per-installation device identity (sr25519 statement-account key + X25519 encryption key). The SDK now creates and persists this identity for you on first run, against the configured `StorageAdapter`, and reuses it on subsequent launches — no consumer wiring needed for web hosts. |
| 118 | + |
| 119 | +For Electron / native hosts that want a hardware-backed identity (Keychain, native secure storage), pass an optional `deviceIdentity` factory: |
| 120 | + |
| 121 | +```ts |
| 122 | +import type { DeviceIdentityForPairing } from '@novasamatech/host-papp'; |
| 123 | + |
| 124 | +createPappAdapter({ |
| 125 | + appId: 'my-host', |
| 126 | + hostMetadata: { /* ... */ }, |
| 127 | + async deviceIdentity(): Promise<DeviceIdentityForPairing> { |
| 128 | + return { |
| 129 | + statementAccountPublicKey, |
| 130 | + statementAccountSecret, |
| 131 | + encryptionPublicKey, |
| 132 | + encryptionPrivateKey, |
| 133 | + } |
| 134 | + }, |
| 135 | +}); |
| 136 | +``` |
| 137 | + |
| 138 | +The factory is invoked once per pairing flow. |
| 139 | + |
| 140 | +### `onAuthSuccess` hook |
| 141 | + |
| 142 | +A new optional caller hook fires after the V2 handshake succeeds — after the SDK has already written the session and secrets to its own repositories. Use it for consumer-specific post-pairing work (telemetry, custom peer caches, device-sync seeding). |
| 143 | + |
| 144 | +```ts |
| 145 | +createPappAdapter({ |
| 146 | + appId: 'my-host', |
| 147 | + hostMetadata: { /* ... */ }, |
| 148 | + onAuthSuccess: ({ session, identityChatPrivateKey }) => { |
| 149 | + // session: StoredUserSession (already persisted by the SDK) |
| 150 | + // identityChatPrivateKey: the private half of session.identityChatPublicKey; |
| 151 | + // lives in UserSecretRepository and is surfaced here for consumers |
| 152 | + // whose chat / device-sync layer needs it directly. |
| 153 | + telemetry.track('sso_paired', { sessionId: session.id }); |
| 154 | + }, |
| 155 | +}); |
| 156 | +``` |
| 157 | + |
| 158 | +Throwing from the hook fails the pending `authenticate()` call. |
| 159 | + |
| 160 | +### New `StoredUserSession` V2 fields |
| 161 | + |
| 162 | +`StoredUserSession` gains two optional V2 fields, populated when pairing happens against a V2 mobile build: |
| 163 | + |
| 164 | +```ts |
| 165 | +type StoredUserSession = { |
| 166 | + // ... existing v0.7 fields ... |
| 167 | + identityAccountId?: AccountId; // user's identity sr25519 account |
| 168 | + identityChatPublicKey?: Uint8Array; // P-256 65-byte, derived locally from identityChatPrivateKey |
| 169 | +}; |
| 170 | +``` |
| 171 | + |
| 172 | +Both are `Option`-wrapped on the wire so future schema revisions can append further fields without breaking decode of v0.8.0 blobs. The matching `identityChatPrivateKey` lives in `UserSecretRepository` and is surfaced through the [`onAuthSuccess`](#onauthsuccess-hook) hook. |
| 173 | + |
| 174 | +### Multi-device chat message variants |
| 175 | + |
| 176 | +`MessageContent` gains three new variants for the multi-device chat layer: |
| 177 | + |
| 178 | +```ts |
| 179 | +// Index 17 — announce a new peer device on the chat |
| 180 | +{ tag: 'deviceAdded', value: { statementAccountId, encryptionPublicKey } } |
| 181 | + |
| 182 | +// Index 18 — remove a peer device |
| 183 | +{ tag: 'deviceRemoved', value: { statementAccountId } } |
| 184 | + |
| 185 | +// Index 20 — multi-device accept. |
| 186 | +// Sent on the identity-level session SessionId(B, A) encrypted with K(A, B) |
| 187 | +// so all of A's devices can decrypt without per-device envelope. |
| 188 | +{ tag: 'deviceChatAccepted', value: { requestId, device } } |
| 189 | +``` |
| 190 | + |
| 191 | +Index 19 is reserved as a spec placeholder so `deviceChatAccepted` lands on its specified index. |
0 commit comments