@novasamatech/host-papp 0.9.0 → 0.9.2
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/.papi/descriptors/dist/common-types.d.ts +1483 -1359
- package/.papi/descriptors/dist/descriptors-BLqmZ_vS.js +25 -0
- package/.papi/descriptors/dist/index.d.ts +1 -1
- package/.papi/descriptors/dist/index.js +8 -6
- package/.papi/descriptors/dist/metadataTypes-wXgvb64i.js +3 -0
- package/.papi/descriptors/dist/metadataTypes.d.ts +1 -1
- package/.papi/descriptors/dist/people_lite.d.ts +1175 -1001
- package/.papi/descriptors/dist/people_lite_metadata-CwU9OBGy.js +3 -0
- package/.papi/descriptors/generated.json +1 -1
- package/.papi/descriptors/package.json +1 -1
- package/.papi/metadata/people_lite.scale +0 -0
- package/.papi/polkadot-api.json +3 -3
- package/README.md +85 -79
- package/dist/identity/identifierKey.d.ts +19 -0
- package/dist/identity/identifierKey.js +17 -0
- package/dist/identity/impl.js +12 -17
- package/dist/identity/index.d.ts +2 -0
- package/dist/identity/index.js +1 -0
- package/dist/identity/rpcAdapter.d.ts +5 -1
- package/dist/identity/rpcAdapter.js +9 -3
- package/dist/identity/types.d.ts +9 -1
- package/dist/index.d.ts +3 -1
- package/dist/index.js +2 -0
- package/package.json +9 -5
- package/.papi/descriptors/dist/descriptors-D5oN4i7I.js +0 -25
- package/.papi/descriptors/dist/metadataTypes-BaUy_4gO.js +0 -3
- package/.papi/descriptors/dist/people_lite_metadata-Cj1JLFI2.js +0 -3
|
@@ -1 +1 @@
|
|
|
1
|
-
{"cliVersion":"0.21.
|
|
1
|
+
{"cliVersion":"0.21.9","whitelist":null,"chains":{"people_lite":"0xcca79c4482b2233e7cd4d24a23988e77"},"contracts":{"ink":{},"sol":{}}}
|
|
Binary file
|
package/.papi/polkadot-api.json
CHANGED
|
@@ -6,10 +6,10 @@
|
|
|
6
6
|
},
|
|
7
7
|
"entries": {
|
|
8
8
|
"people_lite": {
|
|
9
|
-
"wsUrl": "wss://
|
|
9
|
+
"wsUrl": "wss://people-paseo.gatotech.network",
|
|
10
10
|
"metadata": ".papi/metadata/people_lite.scale",
|
|
11
|
-
"genesis": "
|
|
12
|
-
"codeHash": "
|
|
11
|
+
"genesis": "0xe6c30d6e148f250b887105237bcaa5cb9f16dd203bf7b5b9d4f1da7387cb86ec",
|
|
12
|
+
"codeHash": "0x82a343ddb8c1faf6d88721f6188dabb393b3d5b4fd54aae6d621bc3b0452ebbb"
|
|
13
13
|
}
|
|
14
14
|
}
|
|
15
15
|
}
|
package/README.md
CHANGED
|
@@ -4,17 +4,16 @@ Polkadot app integration layer for host applications.
|
|
|
4
4
|
|
|
5
5
|
## Overview
|
|
6
6
|
|
|
7
|
-
`@novasamatech/host-papp` is the integration SDK that lets a javascript-based host embed Polkadot
|
|
8
|
-
|
|
7
|
+
`@novasamatech/host-papp` is the integration SDK that lets a javascript-based host embed Polkadot Mobile capabilities.
|
|
8
|
+
It encapsulates everything needed to:
|
|
9
9
|
|
|
10
10
|
- pair the host with a Polkadot wallet/SSO provider via a deeplink handshake;
|
|
11
11
|
- store and manage paired user sessions;
|
|
12
12
|
- send signing requests and ring-VRF alias requests to the paired wallet;
|
|
13
13
|
- look up on-chain identity information for accounts.
|
|
14
14
|
|
|
15
|
-
The package is UI-framework agnostic — it exposes plain async APIs and observable state
|
|
16
|
-
|
|
17
|
-
non-browser runtime.
|
|
15
|
+
The package is UI-framework agnostic — it exposes plain async APIs and observable state (`subscribe` / `read`), so it
|
|
16
|
+
can be wired into React, Vue, Svelte, vanilla DOM, or a non-browser runtime.
|
|
18
17
|
|
|
19
18
|
## Installation
|
|
20
19
|
|
|
@@ -24,8 +23,7 @@ npm install @novasamatech/host-papp --save -E
|
|
|
24
23
|
|
|
25
24
|
## Getting started
|
|
26
25
|
|
|
27
|
-
Create a single adapter instance for the lifetime of your host app and share it across the
|
|
28
|
-
features that need it.
|
|
26
|
+
Create a single adapter instance for the lifetime of your host app and share it across the features that need it.
|
|
29
27
|
|
|
30
28
|
```ts
|
|
31
29
|
import { createPappAdapter } from '@novasamatech/host-papp';
|
|
@@ -46,26 +44,25 @@ const papp = createPappAdapter({
|
|
|
46
44
|
|
|
47
45
|
`createPappAdapter` returns five sub-modules:
|
|
48
46
|
|
|
49
|
-
| Module | Purpose
|
|
50
|
-
| ---------------- |
|
|
51
|
-
| `papp.sso` | Authentication / pairing flow with a remote wallet.
|
|
52
|
-
| `papp.sessions` | List of paired user sessions and per-session messaging (sign, etc.)
|
|
53
|
-
| `papp.secrets` | Local secret storage for the derived guest accounts.
|
|
54
|
-
| `papp.identity` | On-chain identity lookups for arbitrary account ids.
|
|
55
|
-
| `papp.allowance` | Resource allowances (bulletin / statement-store signers) per product
|
|
47
|
+
| Module | Purpose |
|
|
48
|
+
| ---------------- | --------------------------------------------------------------------- |
|
|
49
|
+
| `papp.sso` | Authentication / pairing flow with a remote wallet. |
|
|
50
|
+
| `papp.sessions` | List of paired user sessions and per-session messaging (sign, etc.). |
|
|
51
|
+
| `papp.secrets` | Local secret storage for the derived guest accounts. |
|
|
52
|
+
| `papp.identity` | On-chain identity lookups for arbitrary account ids. |
|
|
53
|
+
| `papp.allowance` | Resource allowances (bulletin / statement-store signers) per product. |
|
|
56
54
|
|
|
57
|
-
Custom adapters (statement store, identity RPC, storage, lazy chain client) can be supplied
|
|
58
|
-
|
|
55
|
+
Custom adapters (statement store, identity RPC, storage, lazy chain client) can be supplied via the `adapters` option
|
|
56
|
+
for testing or non-browser environments.
|
|
59
57
|
|
|
60
58
|
## Authentication and pairing (V1)
|
|
61
59
|
|
|
62
|
-
The V1 SSO flow described in this section is the single-device pairing protocol used by
|
|
63
|
-
|
|
64
|
-
[V2 SSO handshake](#v2-sso-handshake) below.
|
|
60
|
+
The V1 SSO flow described in this section is the single-device pairing protocol used by `papp.sso.authenticate()`. For
|
|
61
|
+
the multi-device V2 protocol see [V2 SSO handshake](#v2-sso-handshake) below.
|
|
65
62
|
|
|
66
|
-
`papp.sso.authenticate()` runs the full pairing + attestation flow and resolves with the
|
|
67
|
-
|
|
68
|
-
|
|
63
|
+
`papp.sso.authenticate()` runs the full pairing + attestation flow and resolves with the stored user session, or `null`
|
|
64
|
+
if the flow was aborted. The flow is idempotent — calling it again while a previous run is in flight returns the same
|
|
65
|
+
in-progress promise.
|
|
69
66
|
|
|
70
67
|
```ts
|
|
71
68
|
const result = await papp.sso.authenticate();
|
|
@@ -92,8 +89,8 @@ papp.sso.abortAuthentication();
|
|
|
92
89
|
|
|
93
90
|
### Reacting to pairing status
|
|
94
91
|
|
|
95
|
-
The pairing process is observable. UI code typically renders a QR code / deeplink while the
|
|
96
|
-
|
|
92
|
+
The pairing process is observable. UI code typically renders a QR code / deeplink while the status is `pairing`, then
|
|
93
|
+
transitions to a "signing in" screen during attestation.
|
|
97
94
|
|
|
98
95
|
```ts
|
|
99
96
|
import type { PairingStatus } from '@novasamatech/host-papp';
|
|
@@ -121,15 +118,14 @@ render(papp.sso.pairingStatus.read());
|
|
|
121
118
|
const unsubscribe = papp.sso.pairingStatus.subscribe(render);
|
|
122
119
|
```
|
|
123
120
|
|
|
124
|
-
`papp.sso.attestationStatus` exposes the same `read` / `subscribe` shape and tracks
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
steps before `attestation`, then attestation, then back to pairing's `finished`.
|
|
121
|
+
`papp.sso.attestationStatus` exposes the same `read` / `subscribe` shape and tracks attestation progress (`attestation`
|
|
122
|
+
with a claimed `username`, `attestationError`, or `finished`). For convenience, treat the two streams as a single
|
|
123
|
+
derived UI state — pairing steps before `attestation`, then attestation, then back to pairing's `finished`.
|
|
128
124
|
|
|
129
125
|
## Managing user sessions
|
|
130
126
|
|
|
131
|
-
`papp.sessions.sessions` is an observable list of currently paired sessions. Most host apps
|
|
132
|
-
|
|
127
|
+
`papp.sessions.sessions` is an observable list of currently paired sessions. Most host apps work with the first one
|
|
128
|
+
(single-user model), but the SDK does not enforce that.
|
|
133
129
|
|
|
134
130
|
```ts
|
|
135
131
|
import type { UserSession } from '@novasamatech/host-papp';
|
|
@@ -144,8 +140,7 @@ const unsubscribe = papp.sessions.sessions.subscribe(sessions => {
|
|
|
144
140
|
currentSession = papp.sessions.sessions.read().at(0) ?? null;
|
|
145
141
|
```
|
|
146
142
|
|
|
147
|
-
Disconnecting notifies the wallet, removes local secrets, and triggers the subscription
|
|
148
|
-
above.
|
|
143
|
+
Disconnecting notifies the wallet, removes local secrets, and triggers the subscription above.
|
|
149
144
|
|
|
150
145
|
```ts
|
|
151
146
|
const disconnect = async (session: UserSession) => {
|
|
@@ -159,8 +154,7 @@ const disconnect = async (session: UserSession) => {
|
|
|
159
154
|
|
|
160
155
|
## Signing
|
|
161
156
|
|
|
162
|
-
A `UserSession` exposes `signPayload` and `signRaw` for forwarding signing requests to the
|
|
163
|
-
paired wallet.
|
|
157
|
+
A `UserSession` exposes `signPayload` and `signRaw` for forwarding signing requests to the paired wallet.
|
|
164
158
|
|
|
165
159
|
```ts
|
|
166
160
|
const signed = await currentSession.signPayload({
|
|
@@ -197,10 +191,9 @@ await currentSession.signRaw({
|
|
|
197
191
|
});
|
|
198
192
|
```
|
|
199
193
|
|
|
200
|
-
`signVrf` asks the wallet for an sr25519 (schnorrkel) VRF signature from a product account
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
transcript and signs. Callers that need a `signer` item must supply their own public key;
|
|
194
|
+
`signVrf` asks the wallet for an sr25519 (schnorrkel) VRF signature from a product account (RFC-0023). The transcript
|
|
195
|
+
travels as a recipe — a root domain-separation label plus an ordered list of `(label, value)` items — which the wallet
|
|
196
|
+
replays verbatim into a Merlin transcript and signs. Callers that need a `signer` item must supply their own public key;
|
|
204
197
|
the host never injects it.
|
|
205
198
|
|
|
206
199
|
```ts
|
|
@@ -222,15 +215,15 @@ vrf.match(
|
|
|
222
215
|
);
|
|
223
216
|
```
|
|
224
217
|
|
|
225
|
-
This is the non-`AutoSigning` path only: when `AutoSigning` covers the account the host
|
|
226
|
-
|
|
218
|
+
This is the non-`AutoSigning` path only: when `AutoSigning` covers the account the host signs locally and never
|
|
219
|
+
round-trips to the wallet.
|
|
227
220
|
|
|
228
221
|
## Ring VRF proofs and aliases
|
|
229
222
|
|
|
230
|
-
A `UserSession` can ask the paired device for a privacy-preserving contextual alias, or a
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
223
|
+
A `UserSession` can ask the paired device for a privacy-preserving contextual alias, or a ring VRF proof, for a
|
|
224
|
+
product-scoped `context` and a `ring` location. The device selects the member key for the ring; `callingProductId` names
|
|
225
|
+
the product the host is acting for. Both take the same `(context, ring)` so the alias in the proof matches
|
|
226
|
+
`getRingVrfAlias`.
|
|
234
227
|
|
|
235
228
|
```ts
|
|
236
229
|
// [productId, suffix]. The suffix is the wire `Index(u32) | Raw([u8; 32])` selector
|
|
@@ -255,11 +248,9 @@ proof.match(
|
|
|
255
248
|
|
|
256
249
|
## Product subtree public keys
|
|
257
250
|
|
|
258
|
-
Product accounts live at `//product//{productId}/{index}` (RFC 0022). The
|
|
259
|
-
product
|
|
260
|
-
|
|
261
|
-
product-subtree public key once, then soft-derives account public keys locally
|
|
262
|
-
from it.
|
|
251
|
+
Product accounts live at `//product//{productId}/{index}` (RFC 0022). The product junction is **hard**, so the user's
|
|
252
|
+
root public key alone no longer determines product account public keys — the host asks the paired device for the
|
|
253
|
+
product-subtree public key once, then soft-derives account public keys locally from it.
|
|
263
254
|
|
|
264
255
|
```ts
|
|
265
256
|
const subtreeKey = await currentSession.getProductSubtree('product.dot');
|
|
@@ -270,15 +261,14 @@ subtreeKey.match(
|
|
|
270
261
|
);
|
|
271
262
|
```
|
|
272
263
|
|
|
273
|
-
The request is consent-free — the response carries no secret material. Only
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
that product's subtree. The former `productDerivationSecret` is gone.
|
|
264
|
+
The request is consent-free — the response carries no secret material. Only `AutoSigning` does: its payload is now the
|
|
265
|
+
product-subtree secret key alone (`productRootPrivateKey`, 64-byte expanded sr25519 secret), which exposes exactly that
|
|
266
|
+
product's subtree. The former `productDerivationSecret` is gone.
|
|
277
267
|
|
|
278
268
|
## Identity lookups
|
|
279
269
|
|
|
280
|
-
`papp.identity` resolves on-chain identity data (lite / full username, credibility,
|
|
281
|
-
|
|
270
|
+
`papp.identity` resolves on-chain identity data (lite / full username, credibility, chat encryption key) for arbitrary
|
|
271
|
+
account ids. Pass an `0x`-prefixed account id (32-byte hex).
|
|
282
272
|
|
|
283
273
|
```ts
|
|
284
274
|
const lookup = async (accountId: string) => {
|
|
@@ -296,34 +286,51 @@ const lookup = async (accountId: string) => {
|
|
|
296
286
|
await papp.identity.getIdentities([accountIdA, accountIdB]);
|
|
297
287
|
```
|
|
298
288
|
|
|
299
|
-
A paired `UserSession` also exposes `getIdentity()` as a shortcut that looks up the identity
|
|
300
|
-
|
|
289
|
+
A paired `UserSession` also exposes `getIdentity()` as a shortcut that looks up the identity of its own user identity
|
|
290
|
+
account — no account id to pass:
|
|
301
291
|
|
|
302
292
|
```ts
|
|
303
293
|
const identity = await session.getIdentity(); // Result<Identity | null, Error>
|
|
304
294
|
```
|
|
305
295
|
|
|
296
|
+
`Identity` is:
|
|
297
|
+
|
|
298
|
+
```ts
|
|
299
|
+
type Identity = {
|
|
300
|
+
accountId: string;
|
|
301
|
+
fullUsername: string | null;
|
|
302
|
+
liteUsername: string;
|
|
303
|
+
credibility: { type: 'Lite' } | { type: 'Person'; alias: `0x${string}`; lastUpdate: string | null };
|
|
304
|
+
/** 32-byte X25519 chat encryption key as hex, unwrapped from the 65-byte
|
|
305
|
+
* RFC-0004 container; null for a keypair type this SDK does not implement. */
|
|
306
|
+
identifierKey: `0x${string}` | null;
|
|
307
|
+
};
|
|
308
|
+
```
|
|
309
|
+
|
|
310
|
+
`lastUpdate` and `identifierKey` are both nullable — the chain record can carry neither a readable timestamp nor a key
|
|
311
|
+
this SDK can use, and neither case is an error.
|
|
312
|
+
|
|
313
|
+
This package owns the `Resources.Consumers` reader for the monorepo. `host-chat` reads through it rather than querying
|
|
314
|
+
the chain itself; `createIdentityRpcAdapter` is exported for that, alongside the `Identity` and `Credibility` types.
|
|
315
|
+
|
|
306
316
|
## V2 SSO handshake
|
|
307
317
|
|
|
308
|
-
V2 is a redesign of the SSO pairing flow that supports the same user identity across
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
identity, so contacts, chats, and roster events are shared between them.
|
|
318
|
+
V2 is a redesign of the SSO pairing flow that supports the same user identity across multiple devices. The host
|
|
319
|
+
generates a stable device keypair locally, emits a `VersionedHandshakeProposal::V2` via QR/deeplink, and an authorising
|
|
320
|
+
peer (e.g. the user's existing Polkadot App) responds over the Statement Store with the user identity keys signed to
|
|
321
|
+
authorise this device. Subsequent devices belonging to the same user reuse the same identity, so contacts, chats, and
|
|
322
|
+
roster events are shared between them.
|
|
314
323
|
|
|
315
|
-
V2 is **not interoperable with V1**: a V1-only peer can't decode a V2 proposal QR and vice
|
|
316
|
-
|
|
324
|
+
V2 is **not interoperable with V1**: a V1-only peer can't decode a V2 proposal QR and vice versa. Hosts that want to
|
|
325
|
+
support both should branch on which protocol the peer advertises.
|
|
317
326
|
|
|
318
327
|
### The flow
|
|
319
328
|
|
|
320
329
|
1. The host builds a pairing deeplink from its device keypair and shows it as a QR code.
|
|
321
|
-
2. The authorising device scans it and posts its response to the Statement Store: first a
|
|
322
|
-
|
|
323
|
-
signed to authorise this device — or `Failed`.
|
|
330
|
+
2. The authorising device scans it and posts its response to the Statement Store: first a `Pending` acknowledgement,
|
|
331
|
+
then either `Success` — carrying the user's identity keys, signed to authorise this device — or `Failed`.
|
|
324
332
|
3. The host polls the pairing topic, decrypts and verifies each response, and drives a
|
|
325
|
-
`Submitted → Pending → Success | Failed` state machine. On `Success` it persists the
|
|
326
|
-
user identity.
|
|
333
|
+
`Submitted → Pending → Success | Failed` state machine. On `Success` it persists the user identity.
|
|
327
334
|
|
|
328
335
|
### Building and rendering the QR
|
|
329
336
|
|
|
@@ -333,7 +340,7 @@ import { buildPairingDeeplink } from '@novasamatech/host-papp';
|
|
|
333
340
|
const deeplink = buildPairingDeeplink(
|
|
334
341
|
{
|
|
335
342
|
statementAccountPublicKey: device.statementAccountPublicKey, // sr25519 device pubkey, 32 bytes
|
|
336
|
-
encryptionPublicKey: device.encryptionPublicKey,
|
|
343
|
+
encryptionPublicKey: device.encryptionPublicKey, // P-256 device pubkey, 65 bytes uncompressed
|
|
337
344
|
},
|
|
338
345
|
{
|
|
339
346
|
hostName: 'My Host App',
|
|
@@ -396,9 +403,9 @@ pairing.abort();
|
|
|
396
403
|
|
|
397
404
|
### Surviving reloads / proper logout
|
|
398
405
|
|
|
399
|
-
The chain holds the most recent statement on the pairing topic indefinitely, so on cold
|
|
400
|
-
|
|
401
|
-
|
|
406
|
+
The chain holds the most recent statement on the pairing topic indefinitely, so on cold start the service will see the
|
|
407
|
+
previous Success and replay it. To distinguish a stale replay from a fresh re-pair, callers can pass byte-level dedupe
|
|
408
|
+
state:
|
|
402
409
|
|
|
403
410
|
```ts
|
|
404
411
|
const pairing = startPairingV2({
|
|
@@ -410,14 +417,13 @@ const pairing = startPairingV2({
|
|
|
410
417
|
});
|
|
411
418
|
```
|
|
412
419
|
|
|
413
|
-
The service skips any incoming statement whose bytes match `initialProcessedDataHex`. PApp
|
|
414
|
-
|
|
415
|
-
always produces different bytes and passes the dedupe.
|
|
420
|
+
The service skips any incoming statement whose bytes match `initialProcessedDataHex`. PApp re-encrypts every Success
|
|
421
|
+
with a fresh ephemeral key + AES-GCM nonce, so a genuine re-pair always produces different bytes and passes the dedupe.
|
|
416
422
|
|
|
417
423
|
## Reading allowances
|
|
418
424
|
|
|
419
|
-
Each `UserSession` can read its own persisted allowance slot-account key for a given
|
|
420
|
-
|
|
425
|
+
Each `UserSession` can read its own persisted allowance slot-account key for a given product and resource. The session
|
|
426
|
+
id is implicit — you only pass the product and resource:
|
|
421
427
|
|
|
422
428
|
```ts
|
|
423
429
|
const session = papp.sessions.sessions.read().at(0);
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `Resources.Consumers.identifier_key` — a peer's chat encryption key as the chain
|
|
3
|
+
* records it (CHAT-RFC-0004 §4).
|
|
4
|
+
*
|
|
5
|
+
* The 65-byte width predates X25519: it is what an uncompressed P-256 point occupied,
|
|
6
|
+
* and it stayed when the curve changed. Only the keypair type and the key width moved
|
|
7
|
+
* (0x04 + 64 → 0x00 + 32), so the field is still `SizedHex<65>` in runtime metadata.
|
|
8
|
+
*
|
|
9
|
+
* Padding is carried as a field because the RFC requires readers to ignore it rather
|
|
10
|
+
* than validate it. Decoding throws on a keypair type this SDK does not implement;
|
|
11
|
+
* `decodeRawIdentity` maps that to `null`.
|
|
12
|
+
*/
|
|
13
|
+
export declare const IdentifierKey: import("scale-ts").Codec<{
|
|
14
|
+
tag: "X25519";
|
|
15
|
+
value: {
|
|
16
|
+
key: Uint8Array<ArrayBufferLike>;
|
|
17
|
+
padding: Uint8Array<ArrayBufferLike>;
|
|
18
|
+
};
|
|
19
|
+
}>;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { Bytes, Enum } from '@novasamatech/scale';
|
|
2
|
+
import { Struct } from 'scale-ts';
|
|
3
|
+
/**
|
|
4
|
+
* `Resources.Consumers.identifier_key` — a peer's chat encryption key as the chain
|
|
5
|
+
* records it (CHAT-RFC-0004 §4).
|
|
6
|
+
*
|
|
7
|
+
* The 65-byte width predates X25519: it is what an uncompressed P-256 point occupied,
|
|
8
|
+
* and it stayed when the curve changed. Only the keypair type and the key width moved
|
|
9
|
+
* (0x04 + 64 → 0x00 + 32), so the field is still `SizedHex<65>` in runtime metadata.
|
|
10
|
+
*
|
|
11
|
+
* Padding is carried as a field because the RFC requires readers to ignore it rather
|
|
12
|
+
* than validate it. Decoding throws on a keypair type this SDK does not implement;
|
|
13
|
+
* `decodeRawIdentity` maps that to `null`.
|
|
14
|
+
*/
|
|
15
|
+
export const IdentifierKey = Enum({
|
|
16
|
+
X25519: Struct({ key: Bytes(32), padding: Bytes(32) }),
|
|
17
|
+
});
|
package/dist/identity/impl.js
CHANGED
|
@@ -1,15 +1,20 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { ResultAsync, okAsync } from 'neverthrow';
|
|
2
2
|
import { defer, distinctUntilChanged, filter, finalize, map, merge, shareReplay, takeUntil, tap, timer } from 'rxjs';
|
|
3
|
-
import { toError } from '../helpers/utils.js';
|
|
4
3
|
const WATCH_IDENTITY_INITIAL_TIMEOUT_MS = 15_000;
|
|
5
4
|
function getCacheKey(accountId) {
|
|
6
5
|
return `identity_${accountId}`;
|
|
7
6
|
}
|
|
7
|
+
/**
|
|
8
|
+
* An older-shape record reads as absent so the caller refetches — `getIdentity` only goes
|
|
9
|
+
* to chain on a `null` hit. Extend whenever `Identity` grows a field.
|
|
10
|
+
*/
|
|
11
|
+
const REQUIRED_FIELDS = ['accountId', 'fullUsername', 'liteUsername', 'credibility', 'identifierKey'];
|
|
8
12
|
function parseIdentity(raw) {
|
|
9
13
|
if (!raw)
|
|
10
14
|
return null;
|
|
11
15
|
try {
|
|
12
|
-
|
|
16
|
+
const parsed = JSON.parse(raw);
|
|
17
|
+
return REQUIRED_FIELDS.every(field => field in parsed) ? parsed : null;
|
|
13
18
|
}
|
|
14
19
|
catch {
|
|
15
20
|
return null;
|
|
@@ -28,7 +33,7 @@ function readCachedIdentity(storage, accountId) {
|
|
|
28
33
|
return defer(() => storage.read(getCacheKey(accountId)).match(parseIdentity, () => null));
|
|
29
34
|
}
|
|
30
35
|
export function createIdentityRepository({ adapter, storage, initialEmissionTimeoutMs = WATCH_IDENTITY_INITIAL_TIMEOUT_MS, }) {
|
|
31
|
-
const cachedRequester = createCachedIdentityRequester(storage
|
|
36
|
+
const cachedRequester = createCachedIdentityRequester(storage);
|
|
32
37
|
// Per-account de-dup: concurrent watchIdentity(acc) calls share one chain
|
|
33
38
|
// subscription via the shared stream built below. The entry clears itself —
|
|
34
39
|
// see the `finalize` in `buildWatch` — so the map can't accumulate dead
|
|
@@ -81,25 +86,15 @@ export function createIdentityRepository({ adapter, storage, initialEmissionTime
|
|
|
81
86
|
},
|
|
82
87
|
};
|
|
83
88
|
}
|
|
84
|
-
function createCachedIdentityRequester(storage
|
|
89
|
+
function createCachedIdentityRequester(storage) {
|
|
85
90
|
function readSingleCacheRecord(accountId) {
|
|
86
|
-
return storage.read(
|
|
87
|
-
if (!raw) {
|
|
88
|
-
return ok(null);
|
|
89
|
-
}
|
|
90
|
-
try {
|
|
91
|
-
return ok(JSON.parse(raw));
|
|
92
|
-
}
|
|
93
|
-
catch (e) {
|
|
94
|
-
return err(toError(e));
|
|
95
|
-
}
|
|
96
|
-
});
|
|
91
|
+
return storage.read(getCacheKey(accountId)).map(parseIdentity);
|
|
97
92
|
}
|
|
98
93
|
function writeSingleCacheRecord(accountId, identity) {
|
|
99
94
|
if (identity === null) {
|
|
100
95
|
return okAsync(undefined);
|
|
101
96
|
}
|
|
102
|
-
return storage.write(
|
|
97
|
+
return storage.write(getCacheKey(accountId), JSON.stringify(identity));
|
|
103
98
|
}
|
|
104
99
|
function readCache(accounts) {
|
|
105
100
|
if (accounts.length === 0) {
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { createIdentityRpcAdapter } from './rpcAdapter.js';
|
|
@@ -1,3 +1,7 @@
|
|
|
1
1
|
import type { LazyClient } from '@novasamatech/statement-store';
|
|
2
|
-
import type {
|
|
2
|
+
import type { People_liteQueries } from '../../.papi/descriptors/dist/index.js';
|
|
3
|
+
import type { Identity, IdentityAdapter } from './types.js';
|
|
4
|
+
type RawConsumers = NonNullable<People_liteQueries['Resources']['Consumers']['Value']>;
|
|
5
|
+
export declare function decodeRawIdentity(accountId: string, raw: RawConsumers | undefined, textDecoder: TextDecoder): Identity | null;
|
|
3
6
|
export declare function createIdentityRpcAdapter(lazyClient: LazyClient): IdentityAdapter;
|
|
7
|
+
export {};
|
|
@@ -1,9 +1,11 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { toHex } from '@novasamatech/scale';
|
|
2
|
+
import { Result, errAsync, fromPromise, ok } from 'neverthrow';
|
|
2
3
|
import { AccountId } from 'polkadot-api';
|
|
3
4
|
import { defer, map, throwError } from 'rxjs';
|
|
4
5
|
import { toError } from '../helpers/utils.js';
|
|
5
6
|
import { zipWith } from '../helpers/zipWith.js';
|
|
6
|
-
|
|
7
|
+
import { IdentifierKey } from './identifierKey.js';
|
|
8
|
+
export function decodeRawIdentity(accountId, raw, textDecoder) {
|
|
7
9
|
if (!raw)
|
|
8
10
|
return null;
|
|
9
11
|
const credibility = raw.credibility.type === 'Lite'
|
|
@@ -11,13 +13,17 @@ function decodeRawIdentity(accountId, raw, textDecoder) {
|
|
|
11
13
|
: {
|
|
12
14
|
type: 'Person',
|
|
13
15
|
alias: raw.credibility.value.alias,
|
|
14
|
-
lastUpdate: raw.credibility.value.last_update
|
|
16
|
+
lastUpdate: raw.credibility.value.last_update?.toString() ?? null,
|
|
15
17
|
};
|
|
16
18
|
return {
|
|
17
19
|
accountId,
|
|
18
20
|
fullUsername: raw.full_username ? textDecoder.decode(raw.full_username) : null,
|
|
19
21
|
liteUsername: textDecoder.decode(raw.lite_username),
|
|
20
22
|
credibility,
|
|
23
|
+
// A keypair type we can't encrypt to is a normal condition, not a fault.
|
|
24
|
+
identifierKey: Result.fromThrowable(IdentifierKey.dec)(raw.identifier_key)
|
|
25
|
+
.map(({ value }) => toHex(value.key))
|
|
26
|
+
.unwrapOr(null),
|
|
21
27
|
};
|
|
22
28
|
}
|
|
23
29
|
export function createIdentityRpcAdapter(lazyClient) {
|
package/dist/identity/types.d.ts
CHANGED
|
@@ -5,13 +5,21 @@ export type Credibility = {
|
|
|
5
5
|
} | {
|
|
6
6
|
type: 'Person';
|
|
7
7
|
alias: `0x${string}`;
|
|
8
|
-
|
|
8
|
+
/** `null` when the chain record carries no readable timestamp. */
|
|
9
|
+
lastUpdate: string | null;
|
|
9
10
|
};
|
|
10
11
|
export type Identity = {
|
|
11
12
|
accountId: string;
|
|
12
13
|
fullUsername: string | null;
|
|
13
14
|
liteUsername: string;
|
|
14
15
|
credibility: Credibility;
|
|
16
|
+
/**
|
|
17
|
+
* The account's 32-byte X25519 chat encryption key, unwrapped from its RFC-0004
|
|
18
|
+
* container. `null` for a keypair type this SDK does not implement.
|
|
19
|
+
*
|
|
20
|
+
* Hex, not `Uint8Array`: `Identity` is JSON round-tripped through the storage cache.
|
|
21
|
+
*/
|
|
22
|
+
identifierKey: `0x${string}` | null;
|
|
15
23
|
};
|
|
16
24
|
export type IdentityAdapter = {
|
|
17
25
|
readIdentities(accounts: string[]): ResultAsync<Record<string, Identity | null>, Error>;
|
package/dist/index.d.ts
CHANGED
|
@@ -9,7 +9,9 @@ export { AllowanceError } from './sso/allowance/index.js';
|
|
|
9
9
|
export type { AllowanceResourceKind } from './sso/allowance/index.js';
|
|
10
10
|
export type { UserSession } from './sso/sessionManager/userSession.js';
|
|
11
11
|
export type { StoredUserSession } from './sso/userSessionRepository.js';
|
|
12
|
-
export type { Identity } from './identity/types.js';
|
|
12
|
+
export type { Credibility, Identity, IdentityAdapter, IdentityRepository } from './identity/types.js';
|
|
13
|
+
export { createIdentityRepository } from './identity/impl.js';
|
|
14
|
+
export { createIdentityRpcAdapter } from './identity/rpcAdapter.js';
|
|
13
15
|
export type { SignRawLegacyRequest, SignRawLegacyResponse, SigningPayloadRequest, SigningPayloadResponse, SigningRawRequest, SigningRequest, } from './sso/sessionManager/scale/signing.js';
|
|
14
16
|
export type { RingVrfAliasRequest, RingVrfAliasResponse, RingVrfProofRequest, RingVrfProofResponse, } from './sso/sessionManager/scale/ringVrf.js';
|
|
15
17
|
export type { SignVrfErr, SignVrfRequest, SignVrfResponse } from './sso/sessionManager/scale/signVrf.js';
|
package/dist/index.js
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
1
|
export { SS_PASEO_STABLE_STAGE_ENDPOINTS, SS_PREVIEW_STAGE_ENDPOINTS, SS_STABLE_STAGE_ENDPOINTS } from './constants.js';
|
|
2
2
|
export { createPappAdapter } from './papp.js';
|
|
3
3
|
export { AllowanceError } from './sso/allowance/index.js';
|
|
4
|
+
export { createIdentityRepository } from './identity/impl.js';
|
|
5
|
+
export { createIdentityRpcAdapter } from './identity/rpcAdapter.js';
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@novasamatech/host-papp",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "0.9.
|
|
4
|
+
"version": "0.9.2",
|
|
5
5
|
"description": "Polkadot app integration",
|
|
6
6
|
"license": "Apache-2.0",
|
|
7
7
|
"repository": {
|
|
@@ -11,6 +11,10 @@
|
|
|
11
11
|
"keywords": [
|
|
12
12
|
"polkadot"
|
|
13
13
|
],
|
|
14
|
+
"scripts": {
|
|
15
|
+
"papi:fix": "node ../../scripts/fix-papi-descriptors.mjs .papi/descriptors/dist",
|
|
16
|
+
"papi:update": "papi update && npm run papi:fix"
|
|
17
|
+
},
|
|
14
18
|
"main": "dist/index.js",
|
|
15
19
|
"exports": {
|
|
16
20
|
"./package.json": "./package.json",
|
|
@@ -34,10 +38,10 @@
|
|
|
34
38
|
"@noble/ciphers": "2.2.0",
|
|
35
39
|
"@noble/curves": "2.2.0",
|
|
36
40
|
"@noble/hashes": "2.2.0",
|
|
37
|
-
"@novasamatech/host-api": "0.9.
|
|
38
|
-
"@novasamatech/scale": "0.9.
|
|
39
|
-
"@novasamatech/statement-store": "0.9.
|
|
40
|
-
"@novasamatech/storage-adapter": "0.9.
|
|
41
|
+
"@novasamatech/host-api": "0.9.2",
|
|
42
|
+
"@novasamatech/scale": "0.9.2",
|
|
43
|
+
"@novasamatech/statement-store": "0.9.2",
|
|
44
|
+
"@novasamatech/storage-adapter": "0.9.2",
|
|
41
45
|
"@polkadot-api/utils": "^0.4.0",
|
|
42
46
|
"@polkadot-labs/hdkd-helpers": "^0.0.31",
|
|
43
47
|
"nanoevents": "10.0.0",
|