@parity/product-sdk-terminal 0.4.0 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/testing.d.ts +2 -2
- package/dist/testing.js +15 -13
- package/dist/testing.js.map +1 -1
- package/package.json +5 -5
package/dist/testing.d.ts
CHANGED
|
@@ -65,9 +65,9 @@ interface TestSession {
|
|
|
65
65
|
* tracked via on-chain attestation state. See above for how expiry-path
|
|
66
66
|
* tests still work in practice.
|
|
67
67
|
* - **Corrupted-session cases** don't need a helper — write garbage to
|
|
68
|
-
* `<storageDir>/<appId>
|
|
68
|
+
* `<storageDir>/<appId>_SsoSessionsV3.json` with `fs.writeFile` directly.
|
|
69
69
|
* - **Repeated calls replace the session list.** Each call writes a fresh
|
|
70
|
-
* single-entry `
|
|
70
|
+
* single-entry `SsoSessionsV3` file, so calling twice on the same
|
|
71
71
|
* `storageDir`+`appId` leaves only the second session on disk. Use a
|
|
72
72
|
* fresh `mkdtempSync` per test to keep cases isolated.
|
|
73
73
|
*
|
package/dist/testing.js
CHANGED
|
@@ -34,7 +34,8 @@ var storedUserSessionCodec = Struct({
|
|
|
34
34
|
identityAccountId: AccountIdCodec,
|
|
35
35
|
identityChatPublicKey: Bytes(65),
|
|
36
36
|
ssoEncPubKey: Bytes(65),
|
|
37
|
-
rootEntropySource: Bytes(32)
|
|
37
|
+
rootEntropySource: Bytes(32),
|
|
38
|
+
deviceEncPubKey: Bytes(65)
|
|
38
39
|
});
|
|
39
40
|
var sessionsCodec = Vector(storedUserSessionCodec);
|
|
40
41
|
var storedUserSecretsCodec = Struct({
|
|
@@ -80,10 +81,11 @@ async function createTestSession(options) {
|
|
|
80
81
|
identityAccountId: createAccountId(remotePublicKey),
|
|
81
82
|
identityChatPublicKey: remoteEncrPublicKey,
|
|
82
83
|
ssoEncPubKey: remoteEncrPublicKey,
|
|
83
|
-
rootEntropySource: remoteEntropy
|
|
84
|
+
rootEntropySource: remoteEntropy,
|
|
85
|
+
deviceEncPubKey: remoteEncrPublicKey
|
|
84
86
|
};
|
|
85
87
|
await writeFile(
|
|
86
|
-
join(options.storageDir, `${sanitizeKey(options.appId, "
|
|
88
|
+
join(options.storageDir, `${sanitizeKey(options.appId, "SsoSessionsV3")}.json`),
|
|
87
89
|
toHex(sessionsCodec.enc([session])),
|
|
88
90
|
"utf-8"
|
|
89
91
|
);
|
|
@@ -131,14 +133,14 @@ if (void 0) {
|
|
|
131
133
|
}
|
|
132
134
|
});
|
|
133
135
|
describe("createTestSession", () => {
|
|
134
|
-
test("writes both
|
|
136
|
+
test("writes both SsoSessionsV3 and UserSecretsV2 files by default", async () => {
|
|
135
137
|
const result = await createTestSession({
|
|
136
138
|
appId: "my-app",
|
|
137
139
|
storageDir,
|
|
138
140
|
localMnemonic: LOCAL_MNEMONIC,
|
|
139
141
|
remoteMnemonic: REMOTE_MNEMONIC
|
|
140
142
|
});
|
|
141
|
-
const sessions = await readFile(join(storageDir, "my-
|
|
143
|
+
const sessions = await readFile(join(storageDir, "my-app_SsoSessionsV3.json"), "utf-8");
|
|
142
144
|
expect(sessions).toMatch(/^0x[0-9a-f]+$/);
|
|
143
145
|
const secrets = await readFile(
|
|
144
146
|
join(storageDir, `my-app_UserSecretsV2_${result.sessionId}.json`),
|
|
@@ -155,7 +157,7 @@ if (void 0) {
|
|
|
155
157
|
includeSecrets: false
|
|
156
158
|
});
|
|
157
159
|
await expect(
|
|
158
|
-
readFile(join(storageDir, "my-
|
|
160
|
+
readFile(join(storageDir, "my-app_SsoSessionsV3.json"), "utf-8")
|
|
159
161
|
).resolves.toMatch(/^0x/);
|
|
160
162
|
await expect(
|
|
161
163
|
readFile(
|
|
@@ -164,7 +166,7 @@ if (void 0) {
|
|
|
164
166
|
)
|
|
165
167
|
).rejects.toThrow(/ENOENT/);
|
|
166
168
|
});
|
|
167
|
-
test("
|
|
169
|
+
test("SsoSessionsV3 file decodes with the host-papp session codec shape", async () => {
|
|
168
170
|
const result = await createTestSession({
|
|
169
171
|
appId: "my-app",
|
|
170
172
|
storageDir,
|
|
@@ -172,7 +174,7 @@ if (void 0) {
|
|
|
172
174
|
remoteMnemonic: REMOTE_MNEMONIC,
|
|
173
175
|
sessionId: "stable-test-id"
|
|
174
176
|
});
|
|
175
|
-
const hex = await readFile(join(storageDir, "my-
|
|
177
|
+
const hex = await readFile(join(storageDir, "my-app_SsoSessionsV3.json"), "utf-8");
|
|
176
178
|
const decoded = sessionsCodec.dec(fromHex(hex));
|
|
177
179
|
expect(decoded).toHaveLength(1);
|
|
178
180
|
expect(decoded[0].id).toBe("stable-test-id");
|
|
@@ -219,10 +221,10 @@ if (void 0) {
|
|
|
219
221
|
await createTestSession({ appId: "app-a", storageDir, sessionId: "id" });
|
|
220
222
|
await createTestSession({ appId: "app-b", storageDir, sessionId: "id" });
|
|
221
223
|
await expect(
|
|
222
|
-
readFile(join(storageDir, "app-
|
|
224
|
+
readFile(join(storageDir, "app-a_SsoSessionsV3.json"), "utf-8")
|
|
223
225
|
).resolves.toMatch(/^0x/);
|
|
224
226
|
await expect(
|
|
225
|
-
readFile(join(storageDir, "app-
|
|
227
|
+
readFile(join(storageDir, "app-b_SsoSessionsV3.json"), "utf-8")
|
|
226
228
|
).resolves.toMatch(/^0x/);
|
|
227
229
|
});
|
|
228
230
|
test("sanitizes appIds with special characters", async () => {
|
|
@@ -232,14 +234,14 @@ if (void 0) {
|
|
|
232
234
|
sessionId: "id"
|
|
233
235
|
});
|
|
234
236
|
await expect(
|
|
235
|
-
readFile(join(storageDir, "
|
|
237
|
+
readFile(join(storageDir, "app_with_spaces_SsoSessionsV3.json"), "utf-8")
|
|
236
238
|
).resolves.toMatch(/^0x/);
|
|
237
239
|
});
|
|
238
240
|
test("creates storageDir when it does not yet exist", async () => {
|
|
239
241
|
const nested = join(storageDir, "does", "not", "exist");
|
|
240
242
|
await createTestSession({ appId: "my-app", storageDir: nested });
|
|
241
243
|
await expect(
|
|
242
|
-
readFile(join(nested, "my-
|
|
244
|
+
readFile(join(nested, "my-app_SsoSessionsV3.json"), "utf-8")
|
|
243
245
|
).resolves.toMatch(/^0x/);
|
|
244
246
|
});
|
|
245
247
|
test("generates fresh mnemonics when none are supplied", async () => {
|
|
@@ -285,7 +287,7 @@ if (void 0) {
|
|
|
285
287
|
storageDir,
|
|
286
288
|
sessionId: "second"
|
|
287
289
|
});
|
|
288
|
-
const hex = await readFile(join(storageDir, "my-
|
|
290
|
+
const hex = await readFile(join(storageDir, "my-app_SsoSessionsV3.json"), "utf-8");
|
|
289
291
|
const decoded = sessionsCodec.dec(fromHex(hex));
|
|
290
292
|
expect(decoded).toHaveLength(1);
|
|
291
293
|
expect(decoded[0].id).toBe("second");
|
package/dist/testing.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/testing.ts"],"sourcesContent":["// Copyright 2026 Parity Technologies (UK) Ltd.\n// SPDX-License-Identifier: Apache-2.0\n/**\n * Test helpers for synthesizing persisted sessions.\n *\n * Production sessions are persisted to the storage directory by\n * `createNodeStorageAdapter` in the SCALE-encoded / AES-GCM-encrypted\n * format defined by `@novasamatech/host-papp`'s internal repositories.\n * There is no public API on the adapter to write a session without going\n * through the QR pairing + on-chain attestation flow, which makes E2E\n * testing of session-dependent CLI flows impossible without a real phone.\n *\n * This module mirrors that format using only public primitives, so E2E\n * tests can inject a known-good session file. The `testing.test.ts`\n * integration test reads the synthesized files back through the real\n * `ssoSessionRepository` + `userSecretRepository` from `@novasamatech/host-papp`\n * and fails if the upstream format ever drifts from our reproduction.\n */\nimport { gcm } from \"@noble/ciphers/aes.js\";\nimport { p256 } from \"@noble/curves/nist.js\";\nimport { blake2b } from \"@noble/hashes/blake2.js\";\nimport {\n AccountIdCodec,\n LocalSessionAccountCodec,\n RemoteSessionAccountCodec,\n createAccountId,\n createLocalSessionAccount,\n createRemoteSessionAccount,\n createSr25519Secret,\n deriveSr25519PublicKey,\n} from \"@novasamatech/statement-store\";\nimport { toHex } from \"@polkadot-api/utils\";\nimport {\n entropyToMiniSecret,\n generateMnemonic,\n mnemonicToEntropy,\n} from \"@polkadot-labs/hdkd-helpers\";\nimport { mkdir, writeFile } from \"node:fs/promises\";\nimport { join } from \"node:path\";\nimport { nanoid } from \"nanoid\";\nimport { Bytes, type Codec, Struct, Vector, str } from \"scale-ts\";\nimport type { StoredUserSession } from \"@novasamatech/host-papp\";\n\nimport { sanitizeKey } from \"./node-storage.js\";\n\n// Mirror of host-papp's internal stored-session codec (not exported — only the\n// `StoredUserSession` type is). `satisfies` guards drift at build time, the\n// interop test at runtime; field order and the fixed-width fields must match.\n//\n// host-papp 0.8.6 (RFC-0007, PR #205) dropped the `Option` wrapper on\n// `identityAccountId`, `identityChatPublicKey`, and `ssoEncPubKey` — all\n// three are now required — and appended `rootEntropySource: Bytes(32)`,\n// the layer-1 entropy the host consumes via `deriveProductEntropyFromSource`.\nconst storedUserSessionCodec = Struct({\n id: str,\n localAccount: LocalSessionAccountCodec,\n remoteAccount: RemoteSessionAccountCodec,\n rootAccountId: AccountIdCodec,\n identityAccountId: AccountIdCodec,\n identityChatPublicKey: Bytes(65),\n ssoEncPubKey: Bytes(65),\n rootEntropySource: Bytes(32),\n}) satisfies Codec<StoredUserSession>;\nconst sessionsCodec = Vector(storedUserSessionCodec);\n\n// Mirrors the internal StoredUserSecretsCodec in host-papp's userSecretRepository.\n//\n// host-papp 0.8.6 dropped `entropy` (root entropy moved onto the session as\n// `rootEntropySource`) and added the V2 `identityChatPrivateKey` field.\n// The storage key was also renamed `UserSecrets` → `UserSecretsV2`.\nconst storedUserSecretsCodec = Struct({\n ssSecret: Bytes(),\n encrSecret: Bytes(),\n identityChatPrivateKey: Bytes(32),\n});\n\n// Mirrors host-papp's userSecretRepository AES-GCM wrapper: the encryption\n// key is blake2b(appId, 16) and the nonce is blake2b(\"nonce\", 32). The appId\n// doubles as the salt, so sessions written by this helper are only readable\n// by a TerminalAdapter configured with the same appId.\nfunction encryptSecrets(appId: string, plaintext: Uint8Array): string {\n const key = blake2b(new TextEncoder().encode(appId), { dkLen: 16 });\n const nonce = blake2b(new TextEncoder().encode(\"nonce\"), { dkLen: 32 });\n return toHex(gcm(key, nonce).encrypt(plaintext));\n}\n\n/** Mirrors host-papp's createEncrSecret: pad the mini-secret to 48 bytes and feed it to P256 keygen. */\nfunction p256SecretFromEntropy(entropy: Uint8Array): Uint8Array {\n const seed = new Uint8Array(48);\n seed.set(entropyToMiniSecret(entropy));\n return p256.keygen(seed).secretKey;\n}\n\nexport interface CreateTestSessionOptions {\n /** Unique app identifier. Must match the one passed to `createTerminalAdapter`. */\n appId: string;\n /**\n * Directory where session files are written. Must match the one the\n * CLI reads from — pass the same value to `createTerminalAdapter`'s\n * `storageDir` option.\n */\n storageDir: string;\n /** BIP39 mnemonic for the local (host-side) account. Default: freshly generated. */\n localMnemonic?: string;\n /** Derivation path for the local account. Default: `\"//wallet//sso\"` (matches production). */\n localDerivation?: string;\n /** BIP39 mnemonic for the remote (phone) account. Default: freshly generated. */\n remoteMnemonic?: string;\n /** Derivation path for the remote account. Default: `\"\"`. */\n remoteDerivation?: string;\n /** Stable session id. Default: random nanoid(12). */\n sessionId?: string;\n /**\n * Whether to write the encrypted `UserSecretsV2_<id>` file. Default: `true`.\n * Set to `false` to exercise recovery paths where a session exists on disk\n * but its secrets are missing.\n */\n includeSecrets?: boolean;\n}\n\nexport interface TestSession {\n sessionId: string;\n /** Sr25519 public key of the local (host) account, 32 bytes. */\n localAccountId: Uint8Array;\n /** Sr25519 public key of the remote (phone) account, 32 bytes. */\n remoteAccountId: Uint8Array;\n localMnemonic: string;\n localDerivation: string;\n remoteMnemonic: string;\n remoteDerivation: string;\n}\n\n/**\n * Write a valid persisted session to `storageDir`, as if the user had\n * completed QR pairing + attestation. A `TerminalAdapter` created with the\n * same `appId` + `storageDir` will emit the synthesized session from\n * `adapter.sessions.sessions`.\n *\n * @remarks\n * **Dev utility — not a stable contract.** This helper hand-rolls SCALE codecs\n * that mirror an internal on-disk format used by `@novasamatech/host-papp`'s\n * session repositories. The format is not part of host-papp's documented public\n * API and may change on minor version bumps. The `testing.interop.test.ts`\n * round-trip test fails loudly when the format drifts. Use this only in test code.\n *\n * ## Limits\n *\n * - **Signing does not round-trip.** Sending a request via `session.signRaw`\n * still goes out over the statement store and expects a real phone to\n * respond. Use this helper for flows that test session discovery,\n * persistence, and logout — not end-to-end signing.\n * - **Signing attempts surface `NoAllowanceError`.** The synthesized local\n * account was never registered on the People chain, so the first write\n * to the statement store fails with `NoAllowanceError`. That's the same\n * error path the CLI sees when a previously valid session's on-chain\n * attestation has expired, so tests that assert \"CLI handles an expired\n * session\" *can* be written against a synthesized session even though\n * there's no `expiresAt` knob to turn.\n * - **No `expiresAt`.** The on-disk codec has no expiry field; validity is\n * tracked via on-chain attestation state. See above for how expiry-path\n * tests still work in practice.\n * - **Corrupted-session cases** don't need a helper — write garbage to\n * `<storageDir>/<appId>_SsoSessionsV2.json` with `fs.writeFile` directly.\n * - **Repeated calls replace the session list.** Each call writes a fresh\n * single-entry `SsoSessionsV2` file, so calling twice on the same\n * `storageDir`+`appId` leaves only the second session on disk. Use a\n * fresh `mkdtempSync` per test to keep cases isolated.\n *\n * @example\n * ```ts\n * import { mkdtempSync } from \"node:fs\";\n * import { tmpdir } from \"node:os\";\n * import { join } from \"node:path\";\n * import { createTestSession } from \"@parity/product-sdk-terminal/testing\";\n * import { createTerminalAdapter, waitForSessions } from \"@parity/product-sdk-terminal\";\n *\n * const storageDir = mkdtempSync(join(tmpdir(), \"e2e-\"));\n * const { sessionId } = await createTestSession({ appId: \"dot-cli\", storageDir });\n *\n * const adapter = createTerminalAdapter({ appId: \"dot-cli\", storageDir });\n * const sessions = await waitForSessions(adapter);\n * // sessions[0].id === sessionId\n * ```\n */\nexport async function createTestSession(options: CreateTestSessionOptions): Promise<TestSession> {\n await mkdir(options.storageDir, { recursive: true });\n\n const localMnemonic = options.localMnemonic ?? generateMnemonic();\n const localDerivation = options.localDerivation ?? \"//wallet//sso\";\n const localEntropy = mnemonicToEntropy(localMnemonic);\n const localSecret = createSr25519Secret(localEntropy, localDerivation);\n const localPublicKey = deriveSr25519PublicKey(localSecret);\n const localEncrSecret = p256SecretFromEntropy(localEntropy);\n\n const remoteMnemonic = options.remoteMnemonic ?? generateMnemonic();\n const remoteDerivation = options.remoteDerivation ?? \"\";\n const remoteEntropy = mnemonicToEntropy(remoteMnemonic);\n const remoteSecret = createSr25519Secret(remoteEntropy, remoteDerivation);\n const remotePublicKey = deriveSr25519PublicKey(remoteSecret);\n const remoteEncrPublicKey = p256.getPublicKey(p256SecretFromEntropy(remoteEntropy), false);\n\n // In production, `remoteAccount.publicKey` is the ECDH shared secret\n // between the host's P256 encryption key and the phone's P256 encryption\n // key. We compute the same thing from two mnemonic-derived P256 keys so\n // the synthesized session is cryptographically well-formed.\n const sharedSecret = p256.getSharedSecret(localEncrSecret, remoteEncrPublicKey).slice(1, 33);\n\n const sessionId = options.sessionId ?? nanoid(12);\n\n // host-papp 0.8.6 (RFC-0007) requires all of identityAccountId,\n // identityChatPublicKey, ssoEncPubKey, and rootEntropySource on the\n // persisted session. For a synthesized test session we collapse the\n // identity-side keys onto the remote account (the wallet doubles as\n // identity), reuse the peer's P-256 encryption pubkey for both chat\n // and SSO transports, and use the remote entropy as the RFC-0007\n // root entropy source so the value is reproducible.\n const session = {\n id: sessionId,\n localAccount: createLocalSessionAccount(createAccountId(localPublicKey), undefined),\n remoteAccount: createRemoteSessionAccount(\n createAccountId(remotePublicKey),\n sharedSecret,\n undefined,\n ),\n rootAccountId: createAccountId(remotePublicKey),\n identityAccountId: createAccountId(remotePublicKey),\n identityChatPublicKey: remoteEncrPublicKey,\n ssoEncPubKey: remoteEncrPublicKey,\n rootEntropySource: remoteEntropy,\n };\n\n await writeFile(\n join(options.storageDir, `${sanitizeKey(options.appId, \"SsoSessionsV2\")}.json`),\n toHex(sessionsCodec.enc([session])),\n \"utf-8\",\n );\n\n const includeSecrets = options.includeSecrets ?? true;\n if (includeSecrets) {\n // host-papp 0.8.6 dropped `entropy` (now lives on the session as\n // `rootEntropySource`) and requires a 32-byte `identityChatPrivateKey`\n // (V2 — P-256 raw scalar). Reuse the local P-256 encryption secret\n // for both — same domain (P-256, 32-byte scalar).\n const encoded = storedUserSecretsCodec.enc({\n ssSecret: localSecret,\n encrSecret: localEncrSecret,\n identityChatPrivateKey: localEncrSecret,\n });\n await writeFile(\n join(\n options.storageDir,\n `${sanitizeKey(options.appId, `UserSecretsV2_${sessionId}`)}.json`,\n ),\n encryptSecrets(options.appId, encoded),\n \"utf-8\",\n );\n }\n\n return {\n sessionId,\n localAccountId: localPublicKey,\n remoteAccountId: remotePublicKey,\n localMnemonic,\n localDerivation,\n remoteMnemonic,\n remoteDerivation,\n };\n}\n\nif (import.meta.vitest) {\n const { describe, test, expect, beforeEach, afterAll } = import.meta.vitest;\n const { mkdtemp, readFile, rm } = await import(\"node:fs/promises\");\n const { tmpdir } = await import(\"node:os\");\n const { fromHex } = await import(\"@polkadot-api/utils\");\n\n // Stable dev mnemonics — deterministic inputs keep the tests hermetic.\n // The first is the well-known Substrate dev root (the source of `//Alice`\n // et al.); the second is the BIP39 all-abandon test vector.\n const LOCAL_MNEMONIC = \"bottom drive obey lake curtain smoke basket hold race lonely fit walk\";\n const REMOTE_MNEMONIC =\n \"abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about\";\n\n let storageDir: string;\n\n beforeEach(async () => {\n storageDir = await mkdtemp(join(tmpdir(), \"terminal-testing-\"));\n });\n\n afterAll(async () => {\n try {\n await rm(storageDir, { recursive: true });\n } catch {\n /* ignore */\n }\n });\n\n describe(\"createTestSession\", () => {\n test(\"writes both SsoSessionsV2 and UserSecretsV2 files by default\", async () => {\n const result = await createTestSession({\n appId: \"my-app\",\n storageDir,\n localMnemonic: LOCAL_MNEMONIC,\n remoteMnemonic: REMOTE_MNEMONIC,\n });\n\n const sessions = await readFile(join(storageDir, \"my-app_SsoSessionsV2.json\"), \"utf-8\");\n expect(sessions).toMatch(/^0x[0-9a-f]+$/);\n\n const secrets = await readFile(\n join(storageDir, `my-app_UserSecretsV2_${result.sessionId}.json`),\n \"utf-8\",\n );\n expect(secrets).toMatch(/^0x[0-9a-f]+$/);\n });\n\n test(\"omits UserSecretsV2 file when includeSecrets is false\", async () => {\n const result = await createTestSession({\n appId: \"my-app\",\n storageDir,\n localMnemonic: LOCAL_MNEMONIC,\n remoteMnemonic: REMOTE_MNEMONIC,\n includeSecrets: false,\n });\n\n await expect(\n readFile(join(storageDir, \"my-app_SsoSessionsV2.json\"), \"utf-8\"),\n ).resolves.toMatch(/^0x/);\n\n await expect(\n readFile(\n join(storageDir, `my-app_UserSecretsV2_${result.sessionId}.json`),\n \"utf-8\",\n ),\n ).rejects.toThrow(/ENOENT/);\n });\n\n test(\"SsoSessionsV2 file decodes with the host-papp session codec shape\", async () => {\n const result = await createTestSession({\n appId: \"my-app\",\n storageDir,\n localMnemonic: LOCAL_MNEMONIC,\n remoteMnemonic: REMOTE_MNEMONIC,\n sessionId: \"stable-test-id\",\n });\n\n const hex = await readFile(join(storageDir, \"my-app_SsoSessionsV2.json\"), \"utf-8\");\n const decoded = sessionsCodec.dec(fromHex(hex));\n\n expect(decoded).toHaveLength(1);\n expect(decoded[0].id).toBe(\"stable-test-id\");\n expect(decoded[0].localAccount.accountId).toEqual(result.localAccountId);\n expect(decoded[0].localAccount.pin).toBeUndefined();\n expect(decoded[0].remoteAccount.accountId).toEqual(result.remoteAccountId);\n expect(decoded[0].remoteAccount.publicKey).toHaveLength(32);\n expect(decoded[0].remoteAccount.pin).toBeUndefined();\n });\n\n test(\"localAccountId matches the Sr25519 public key derived from the mnemonic\", async () => {\n const result = await createTestSession({\n appId: \"my-app\",\n storageDir,\n localMnemonic: LOCAL_MNEMONIC,\n remoteMnemonic: REMOTE_MNEMONIC,\n });\n\n const expected = deriveSr25519PublicKey(\n createSr25519Secret(mnemonicToEntropy(LOCAL_MNEMONIC), \"//wallet//sso\"),\n );\n expect(result.localAccountId).toEqual(expected);\n expect(result.localAccountId).toHaveLength(32);\n });\n\n test(\"UserSecretsV2 file decrypts and decodes with the host-papp secret codec shape\", async () => {\n const appId = \"my-app\";\n const result = await createTestSession({\n appId,\n storageDir,\n localMnemonic: LOCAL_MNEMONIC,\n remoteMnemonic: REMOTE_MNEMONIC,\n });\n\n const hex = await readFile(\n join(storageDir, `${appId}_UserSecretsV2_${result.sessionId}.json`),\n \"utf-8\",\n );\n const key = blake2b(new TextEncoder().encode(appId), { dkLen: 16 });\n const nonce = blake2b(new TextEncoder().encode(\"nonce\"), { dkLen: 32 });\n const decrypted = gcm(key, nonce).decrypt(fromHex(hex));\n const decoded = storedUserSecretsCodec.dec(decrypted);\n\n // Sr25519 secret is 64 bytes (32-byte secret + 32-byte nonce).\n expect(decoded.ssSecret).toHaveLength(64);\n // P256 secret is 32 bytes.\n expect(decoded.encrSecret).toHaveLength(32);\n // V2 chat private key (P-256 raw scalar) is 32 bytes.\n expect(decoded.identityChatPrivateKey).toHaveLength(32);\n });\n\n test(\"different appIds produce files under different prefixes\", async () => {\n await createTestSession({ appId: \"app-a\", storageDir, sessionId: \"id\" });\n await createTestSession({ appId: \"app-b\", storageDir, sessionId: \"id\" });\n\n await expect(\n readFile(join(storageDir, \"app-a_SsoSessionsV2.json\"), \"utf-8\"),\n ).resolves.toMatch(/^0x/);\n await expect(\n readFile(join(storageDir, \"app-b_SsoSessionsV2.json\"), \"utf-8\"),\n ).resolves.toMatch(/^0x/);\n });\n\n test(\"sanitizes appIds with special characters\", async () => {\n await createTestSession({\n appId: \"app/with spaces\",\n storageDir,\n sessionId: \"id\",\n });\n await expect(\n readFile(join(storageDir, \"app_with_spaces_SsoSessionsV2.json\"), \"utf-8\"),\n ).resolves.toMatch(/^0x/);\n });\n\n test(\"creates storageDir when it does not yet exist\", async () => {\n const nested = join(storageDir, \"does\", \"not\", \"exist\");\n await createTestSession({ appId: \"my-app\", storageDir: nested });\n await expect(\n readFile(join(nested, \"my-app_SsoSessionsV2.json\"), \"utf-8\"),\n ).resolves.toMatch(/^0x/);\n });\n\n test(\"generates fresh mnemonics when none are supplied\", async () => {\n const a = await createTestSession({ appId: \"a\", storageDir });\n const b = await createTestSession({ appId: \"b\", storageDir });\n expect(a.localMnemonic).not.toBe(b.localMnemonic);\n expect(a.remoteMnemonic).not.toBe(b.remoteMnemonic);\n expect(a.sessionId).not.toBe(b.sessionId);\n });\n\n test(\"respects an explicit sessionId\", async () => {\n const result = await createTestSession({\n appId: \"my-app\",\n storageDir,\n sessionId: \"pinned-id\",\n });\n expect(result.sessionId).toBe(\"pinned-id\");\n await expect(\n readFile(join(storageDir, \"my-app_UserSecretsV2_pinned-id.json\"), \"utf-8\"),\n ).resolves.toMatch(/^0x/);\n });\n\n test(\"respects a custom localDerivation\", async () => {\n const result = await createTestSession({\n appId: \"my-app\",\n storageDir,\n localMnemonic: LOCAL_MNEMONIC,\n remoteMnemonic: REMOTE_MNEMONIC,\n localDerivation: \"//custom//path\",\n });\n expect(result.localDerivation).toBe(\"//custom//path\");\n const expected = deriveSr25519PublicKey(\n createSr25519Secret(mnemonicToEntropy(LOCAL_MNEMONIC), \"//custom//path\"),\n );\n expect(result.localAccountId).toEqual(expected);\n });\n\n test(\"repeated calls replace the session list\", async () => {\n // Documented behavior — second call leaves only the second session\n // on disk. Callers who want multiple sessions should use separate\n // storage dirs.\n const first = await createTestSession({\n appId: \"my-app\",\n storageDir,\n sessionId: \"first\",\n });\n const second = await createTestSession({\n appId: \"my-app\",\n storageDir,\n sessionId: \"second\",\n });\n\n const hex = await readFile(join(storageDir, \"my-app_SsoSessionsV2.json\"), \"utf-8\");\n const decoded = sessionsCodec.dec(fromHex(hex));\n expect(decoded).toHaveLength(1);\n expect(decoded[0].id).toBe(\"second\");\n // The first session's UserSecretsV2 file is left behind (not cleaned).\n // This matches a real logout flow as well, so we don't try to hide it.\n await expect(\n readFile(join(storageDir, `my-app_UserSecretsV2_${first.sessionId}.json`), \"utf-8\"),\n ).resolves.toMatch(/^0x/);\n await expect(\n readFile(\n join(storageDir, `my-app_UserSecretsV2_${second.sessionId}.json`),\n \"utf-8\",\n ),\n ).resolves.toMatch(/^0x/);\n });\n\n test(\"AccountIdCodec is Bytes(32) — keep this invariant if upstream changes\", () => {\n // Guards against silent upstream changes to the session-account\n // codec shape that would make our synthesized files unreadable.\n const encoded = AccountIdCodec.enc(createAccountId(new Uint8Array(32).fill(7)));\n expect(encoded).toHaveLength(32);\n });\n });\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/testing.ts"],"sourcesContent":["// Copyright 2026 Parity Technologies (UK) Ltd.\n// SPDX-License-Identifier: Apache-2.0\n/**\n * Test helpers for synthesizing persisted sessions.\n *\n * Production sessions are persisted to the storage directory by\n * `createNodeStorageAdapter` in the SCALE-encoded / AES-GCM-encrypted\n * format defined by `@novasamatech/host-papp`'s internal repositories.\n * There is no public API on the adapter to write a session without going\n * through the QR pairing + on-chain attestation flow, which makes E2E\n * testing of session-dependent CLI flows impossible without a real phone.\n *\n * This module mirrors that format using only public primitives, so E2E\n * tests can inject a known-good session file. The `testing.test.ts`\n * integration test reads the synthesized files back through the real\n * `ssoSessionRepository` + `userSecretRepository` from `@novasamatech/host-papp`\n * and fails if the upstream format ever drifts from our reproduction.\n */\nimport { gcm } from \"@noble/ciphers/aes.js\";\nimport { p256 } from \"@noble/curves/nist.js\";\nimport { blake2b } from \"@noble/hashes/blake2.js\";\nimport {\n AccountIdCodec,\n LocalSessionAccountCodec,\n RemoteSessionAccountCodec,\n createAccountId,\n createLocalSessionAccount,\n createRemoteSessionAccount,\n createSr25519Secret,\n deriveSr25519PublicKey,\n} from \"@novasamatech/statement-store\";\nimport { toHex } from \"@polkadot-api/utils\";\nimport {\n entropyToMiniSecret,\n generateMnemonic,\n mnemonicToEntropy,\n} from \"@polkadot-labs/hdkd-helpers\";\nimport { mkdir, writeFile } from \"node:fs/promises\";\nimport { join } from \"node:path\";\nimport { nanoid } from \"nanoid\";\nimport { Bytes, type Codec, Struct, Vector, str } from \"scale-ts\";\nimport type { StoredUserSession } from \"@novasamatech/host-papp\";\n\nimport { sanitizeKey } from \"./node-storage.js\";\n\n// Mirror of host-papp's internal stored-session codec (not exported — only the\n// `StoredUserSession` type is). `satisfies` guards drift at build time, the\n// interop test at runtime; field order and the fixed-width fields must match.\n//\n// host-papp 0.8.6 (RFC-0007, PR #205) dropped the `Option` wrapper on\n// `identityAccountId`, `identityChatPublicKey`, and `ssoEncPubKey` — all\n// three are now required — and appended `rootEntropySource: Bytes(32)`,\n// the layer-1 entropy the host consumes via `deriveProductEntropyFromSource`.\n//\n// host-papp 0.8.7-1 (PR #212) appended `deviceEncPubKey: Bytes(65)` — the\n// paired phone's long-lived ECDH key, lifted from `HandshakeResponseV2.\n// deviceEncPubKey`, used by the host's device-sync channel to address\n// the paired device. The storage key was renamed `SsoSessionsV2 → SsoSessionsV3`\n// in the same release; the old graceful-degrade for V2 blobs is gone.\nconst storedUserSessionCodec = Struct({\n id: str,\n localAccount: LocalSessionAccountCodec,\n remoteAccount: RemoteSessionAccountCodec,\n rootAccountId: AccountIdCodec,\n identityAccountId: AccountIdCodec,\n identityChatPublicKey: Bytes(65),\n ssoEncPubKey: Bytes(65),\n rootEntropySource: Bytes(32),\n deviceEncPubKey: Bytes(65),\n}) satisfies Codec<StoredUserSession>;\nconst sessionsCodec = Vector(storedUserSessionCodec);\n\n// Mirrors the internal StoredUserSecretsCodec in host-papp's userSecretRepository.\n//\n// host-papp 0.8.6 dropped `entropy` (root entropy moved onto the session as\n// `rootEntropySource`) and added the V2 `identityChatPrivateKey` field.\n// The storage key was also renamed `UserSecrets` → `UserSecretsV2`.\nconst storedUserSecretsCodec = Struct({\n ssSecret: Bytes(),\n encrSecret: Bytes(),\n identityChatPrivateKey: Bytes(32),\n});\n\n// Mirrors host-papp's userSecretRepository AES-GCM wrapper: the encryption\n// key is blake2b(appId, 16) and the nonce is blake2b(\"nonce\", 32). The appId\n// doubles as the salt, so sessions written by this helper are only readable\n// by a TerminalAdapter configured with the same appId.\nfunction encryptSecrets(appId: string, plaintext: Uint8Array): string {\n const key = blake2b(new TextEncoder().encode(appId), { dkLen: 16 });\n const nonce = blake2b(new TextEncoder().encode(\"nonce\"), { dkLen: 32 });\n return toHex(gcm(key, nonce).encrypt(plaintext));\n}\n\n/** Mirrors host-papp's createEncrSecret: pad the mini-secret to 48 bytes and feed it to P256 keygen. */\nfunction p256SecretFromEntropy(entropy: Uint8Array): Uint8Array {\n const seed = new Uint8Array(48);\n seed.set(entropyToMiniSecret(entropy));\n return p256.keygen(seed).secretKey;\n}\n\nexport interface CreateTestSessionOptions {\n /** Unique app identifier. Must match the one passed to `createTerminalAdapter`. */\n appId: string;\n /**\n * Directory where session files are written. Must match the one the\n * CLI reads from — pass the same value to `createTerminalAdapter`'s\n * `storageDir` option.\n */\n storageDir: string;\n /** BIP39 mnemonic for the local (host-side) account. Default: freshly generated. */\n localMnemonic?: string;\n /** Derivation path for the local account. Default: `\"//wallet//sso\"` (matches production). */\n localDerivation?: string;\n /** BIP39 mnemonic for the remote (phone) account. Default: freshly generated. */\n remoteMnemonic?: string;\n /** Derivation path for the remote account. Default: `\"\"`. */\n remoteDerivation?: string;\n /** Stable session id. Default: random nanoid(12). */\n sessionId?: string;\n /**\n * Whether to write the encrypted `UserSecretsV2_<id>` file. Default: `true`.\n * Set to `false` to exercise recovery paths where a session exists on disk\n * but its secrets are missing.\n */\n includeSecrets?: boolean;\n}\n\nexport interface TestSession {\n sessionId: string;\n /** Sr25519 public key of the local (host) account, 32 bytes. */\n localAccountId: Uint8Array;\n /** Sr25519 public key of the remote (phone) account, 32 bytes. */\n remoteAccountId: Uint8Array;\n localMnemonic: string;\n localDerivation: string;\n remoteMnemonic: string;\n remoteDerivation: string;\n}\n\n/**\n * Write a valid persisted session to `storageDir`, as if the user had\n * completed QR pairing + attestation. A `TerminalAdapter` created with the\n * same `appId` + `storageDir` will emit the synthesized session from\n * `adapter.sessions.sessions`.\n *\n * @remarks\n * **Dev utility — not a stable contract.** This helper hand-rolls SCALE codecs\n * that mirror an internal on-disk format used by `@novasamatech/host-papp`'s\n * session repositories. The format is not part of host-papp's documented public\n * API and may change on minor version bumps. The `testing.interop.test.ts`\n * round-trip test fails loudly when the format drifts. Use this only in test code.\n *\n * ## Limits\n *\n * - **Signing does not round-trip.** Sending a request via `session.signRaw`\n * still goes out over the statement store and expects a real phone to\n * respond. Use this helper for flows that test session discovery,\n * persistence, and logout — not end-to-end signing.\n * - **Signing attempts surface `NoAllowanceError`.** The synthesized local\n * account was never registered on the People chain, so the first write\n * to the statement store fails with `NoAllowanceError`. That's the same\n * error path the CLI sees when a previously valid session's on-chain\n * attestation has expired, so tests that assert \"CLI handles an expired\n * session\" *can* be written against a synthesized session even though\n * there's no `expiresAt` knob to turn.\n * - **No `expiresAt`.** The on-disk codec has no expiry field; validity is\n * tracked via on-chain attestation state. See above for how expiry-path\n * tests still work in practice.\n * - **Corrupted-session cases** don't need a helper — write garbage to\n * `<storageDir>/<appId>_SsoSessionsV3.json` with `fs.writeFile` directly.\n * - **Repeated calls replace the session list.** Each call writes a fresh\n * single-entry `SsoSessionsV3` file, so calling twice on the same\n * `storageDir`+`appId` leaves only the second session on disk. Use a\n * fresh `mkdtempSync` per test to keep cases isolated.\n *\n * @example\n * ```ts\n * import { mkdtempSync } from \"node:fs\";\n * import { tmpdir } from \"node:os\";\n * import { join } from \"node:path\";\n * import { createTestSession } from \"@parity/product-sdk-terminal/testing\";\n * import { createTerminalAdapter, waitForSessions } from \"@parity/product-sdk-terminal\";\n *\n * const storageDir = mkdtempSync(join(tmpdir(), \"e2e-\"));\n * const { sessionId } = await createTestSession({ appId: \"dot-cli\", storageDir });\n *\n * const adapter = createTerminalAdapter({ appId: \"dot-cli\", storageDir });\n * const sessions = await waitForSessions(adapter);\n * // sessions[0].id === sessionId\n * ```\n */\nexport async function createTestSession(options: CreateTestSessionOptions): Promise<TestSession> {\n await mkdir(options.storageDir, { recursive: true });\n\n const localMnemonic = options.localMnemonic ?? generateMnemonic();\n const localDerivation = options.localDerivation ?? \"//wallet//sso\";\n const localEntropy = mnemonicToEntropy(localMnemonic);\n const localSecret = createSr25519Secret(localEntropy, localDerivation);\n const localPublicKey = deriveSr25519PublicKey(localSecret);\n const localEncrSecret = p256SecretFromEntropy(localEntropy);\n\n const remoteMnemonic = options.remoteMnemonic ?? generateMnemonic();\n const remoteDerivation = options.remoteDerivation ?? \"\";\n const remoteEntropy = mnemonicToEntropy(remoteMnemonic);\n const remoteSecret = createSr25519Secret(remoteEntropy, remoteDerivation);\n const remotePublicKey = deriveSr25519PublicKey(remoteSecret);\n const remoteEncrPublicKey = p256.getPublicKey(p256SecretFromEntropy(remoteEntropy), false);\n\n // In production, `remoteAccount.publicKey` is the ECDH shared secret\n // between the host's P256 encryption key and the phone's P256 encryption\n // key. We compute the same thing from two mnemonic-derived P256 keys so\n // the synthesized session is cryptographically well-formed.\n const sharedSecret = p256.getSharedSecret(localEncrSecret, remoteEncrPublicKey).slice(1, 33);\n\n const sessionId = options.sessionId ?? nanoid(12);\n\n // host-papp 0.8.6 (RFC-0007) requires all of identityAccountId,\n // identityChatPublicKey, ssoEncPubKey, and rootEntropySource on the\n // persisted session. host-papp 0.8.7-1 added `deviceEncPubKey` (the\n // peer device's long-lived ECDH key). For a synthesized test session\n // we collapse the identity-side keys onto the remote account (the\n // wallet doubles as identity), reuse the peer's P-256 encryption\n // pubkey for chat, SSO, and the new device-sync transport, and use\n // the remote entropy as the RFC-0007 root entropy source so the value\n // is reproducible.\n const session = {\n id: sessionId,\n localAccount: createLocalSessionAccount(createAccountId(localPublicKey), undefined),\n remoteAccount: createRemoteSessionAccount(\n createAccountId(remotePublicKey),\n sharedSecret,\n undefined,\n ),\n rootAccountId: createAccountId(remotePublicKey),\n identityAccountId: createAccountId(remotePublicKey),\n identityChatPublicKey: remoteEncrPublicKey,\n ssoEncPubKey: remoteEncrPublicKey,\n rootEntropySource: remoteEntropy,\n deviceEncPubKey: remoteEncrPublicKey,\n };\n\n await writeFile(\n join(options.storageDir, `${sanitizeKey(options.appId, \"SsoSessionsV3\")}.json`),\n toHex(sessionsCodec.enc([session])),\n \"utf-8\",\n );\n\n const includeSecrets = options.includeSecrets ?? true;\n if (includeSecrets) {\n // host-papp 0.8.6 dropped `entropy` (now lives on the session as\n // `rootEntropySource`) and requires a 32-byte `identityChatPrivateKey`\n // (V2 — P-256 raw scalar). Reuse the local P-256 encryption secret\n // for both — same domain (P-256, 32-byte scalar).\n const encoded = storedUserSecretsCodec.enc({\n ssSecret: localSecret,\n encrSecret: localEncrSecret,\n identityChatPrivateKey: localEncrSecret,\n });\n await writeFile(\n join(\n options.storageDir,\n `${sanitizeKey(options.appId, `UserSecretsV2_${sessionId}`)}.json`,\n ),\n encryptSecrets(options.appId, encoded),\n \"utf-8\",\n );\n }\n\n return {\n sessionId,\n localAccountId: localPublicKey,\n remoteAccountId: remotePublicKey,\n localMnemonic,\n localDerivation,\n remoteMnemonic,\n remoteDerivation,\n };\n}\n\nif (import.meta.vitest) {\n const { describe, test, expect, beforeEach, afterAll } = import.meta.vitest;\n const { mkdtemp, readFile, rm } = await import(\"node:fs/promises\");\n const { tmpdir } = await import(\"node:os\");\n const { fromHex } = await import(\"@polkadot-api/utils\");\n\n // Stable dev mnemonics — deterministic inputs keep the tests hermetic.\n // The first is the well-known Substrate dev root (the source of `//Alice`\n // et al.); the second is the BIP39 all-abandon test vector.\n const LOCAL_MNEMONIC = \"bottom drive obey lake curtain smoke basket hold race lonely fit walk\";\n const REMOTE_MNEMONIC =\n \"abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about\";\n\n let storageDir: string;\n\n beforeEach(async () => {\n storageDir = await mkdtemp(join(tmpdir(), \"terminal-testing-\"));\n });\n\n afterAll(async () => {\n try {\n await rm(storageDir, { recursive: true });\n } catch {\n /* ignore */\n }\n });\n\n describe(\"createTestSession\", () => {\n test(\"writes both SsoSessionsV3 and UserSecretsV2 files by default\", async () => {\n const result = await createTestSession({\n appId: \"my-app\",\n storageDir,\n localMnemonic: LOCAL_MNEMONIC,\n remoteMnemonic: REMOTE_MNEMONIC,\n });\n\n const sessions = await readFile(join(storageDir, \"my-app_SsoSessionsV3.json\"), \"utf-8\");\n expect(sessions).toMatch(/^0x[0-9a-f]+$/);\n\n const secrets = await readFile(\n join(storageDir, `my-app_UserSecretsV2_${result.sessionId}.json`),\n \"utf-8\",\n );\n expect(secrets).toMatch(/^0x[0-9a-f]+$/);\n });\n\n test(\"omits UserSecretsV2 file when includeSecrets is false\", async () => {\n const result = await createTestSession({\n appId: \"my-app\",\n storageDir,\n localMnemonic: LOCAL_MNEMONIC,\n remoteMnemonic: REMOTE_MNEMONIC,\n includeSecrets: false,\n });\n\n await expect(\n readFile(join(storageDir, \"my-app_SsoSessionsV3.json\"), \"utf-8\"),\n ).resolves.toMatch(/^0x/);\n\n await expect(\n readFile(\n join(storageDir, `my-app_UserSecretsV2_${result.sessionId}.json`),\n \"utf-8\",\n ),\n ).rejects.toThrow(/ENOENT/);\n });\n\n test(\"SsoSessionsV3 file decodes with the host-papp session codec shape\", async () => {\n const result = await createTestSession({\n appId: \"my-app\",\n storageDir,\n localMnemonic: LOCAL_MNEMONIC,\n remoteMnemonic: REMOTE_MNEMONIC,\n sessionId: \"stable-test-id\",\n });\n\n const hex = await readFile(join(storageDir, \"my-app_SsoSessionsV3.json\"), \"utf-8\");\n const decoded = sessionsCodec.dec(fromHex(hex));\n\n expect(decoded).toHaveLength(1);\n expect(decoded[0].id).toBe(\"stable-test-id\");\n expect(decoded[0].localAccount.accountId).toEqual(result.localAccountId);\n expect(decoded[0].localAccount.pin).toBeUndefined();\n expect(decoded[0].remoteAccount.accountId).toEqual(result.remoteAccountId);\n expect(decoded[0].remoteAccount.publicKey).toHaveLength(32);\n expect(decoded[0].remoteAccount.pin).toBeUndefined();\n });\n\n test(\"localAccountId matches the Sr25519 public key derived from the mnemonic\", async () => {\n const result = await createTestSession({\n appId: \"my-app\",\n storageDir,\n localMnemonic: LOCAL_MNEMONIC,\n remoteMnemonic: REMOTE_MNEMONIC,\n });\n\n const expected = deriveSr25519PublicKey(\n createSr25519Secret(mnemonicToEntropy(LOCAL_MNEMONIC), \"//wallet//sso\"),\n );\n expect(result.localAccountId).toEqual(expected);\n expect(result.localAccountId).toHaveLength(32);\n });\n\n test(\"UserSecretsV2 file decrypts and decodes with the host-papp secret codec shape\", async () => {\n const appId = \"my-app\";\n const result = await createTestSession({\n appId,\n storageDir,\n localMnemonic: LOCAL_MNEMONIC,\n remoteMnemonic: REMOTE_MNEMONIC,\n });\n\n const hex = await readFile(\n join(storageDir, `${appId}_UserSecretsV2_${result.sessionId}.json`),\n \"utf-8\",\n );\n const key = blake2b(new TextEncoder().encode(appId), { dkLen: 16 });\n const nonce = blake2b(new TextEncoder().encode(\"nonce\"), { dkLen: 32 });\n const decrypted = gcm(key, nonce).decrypt(fromHex(hex));\n const decoded = storedUserSecretsCodec.dec(decrypted);\n\n // Sr25519 secret is 64 bytes (32-byte secret + 32-byte nonce).\n expect(decoded.ssSecret).toHaveLength(64);\n // P256 secret is 32 bytes.\n expect(decoded.encrSecret).toHaveLength(32);\n // V2 chat private key (P-256 raw scalar) is 32 bytes.\n expect(decoded.identityChatPrivateKey).toHaveLength(32);\n });\n\n test(\"different appIds produce files under different prefixes\", async () => {\n await createTestSession({ appId: \"app-a\", storageDir, sessionId: \"id\" });\n await createTestSession({ appId: \"app-b\", storageDir, sessionId: \"id\" });\n\n await expect(\n readFile(join(storageDir, \"app-a_SsoSessionsV3.json\"), \"utf-8\"),\n ).resolves.toMatch(/^0x/);\n await expect(\n readFile(join(storageDir, \"app-b_SsoSessionsV3.json\"), \"utf-8\"),\n ).resolves.toMatch(/^0x/);\n });\n\n test(\"sanitizes appIds with special characters\", async () => {\n await createTestSession({\n appId: \"app/with spaces\",\n storageDir,\n sessionId: \"id\",\n });\n await expect(\n readFile(join(storageDir, \"app_with_spaces_SsoSessionsV3.json\"), \"utf-8\"),\n ).resolves.toMatch(/^0x/);\n });\n\n test(\"creates storageDir when it does not yet exist\", async () => {\n const nested = join(storageDir, \"does\", \"not\", \"exist\");\n await createTestSession({ appId: \"my-app\", storageDir: nested });\n await expect(\n readFile(join(nested, \"my-app_SsoSessionsV3.json\"), \"utf-8\"),\n ).resolves.toMatch(/^0x/);\n });\n\n test(\"generates fresh mnemonics when none are supplied\", async () => {\n const a = await createTestSession({ appId: \"a\", storageDir });\n const b = await createTestSession({ appId: \"b\", storageDir });\n expect(a.localMnemonic).not.toBe(b.localMnemonic);\n expect(a.remoteMnemonic).not.toBe(b.remoteMnemonic);\n expect(a.sessionId).not.toBe(b.sessionId);\n });\n\n test(\"respects an explicit sessionId\", async () => {\n const result = await createTestSession({\n appId: \"my-app\",\n storageDir,\n sessionId: \"pinned-id\",\n });\n expect(result.sessionId).toBe(\"pinned-id\");\n await expect(\n readFile(join(storageDir, \"my-app_UserSecretsV2_pinned-id.json\"), \"utf-8\"),\n ).resolves.toMatch(/^0x/);\n });\n\n test(\"respects a custom localDerivation\", async () => {\n const result = await createTestSession({\n appId: \"my-app\",\n storageDir,\n localMnemonic: LOCAL_MNEMONIC,\n remoteMnemonic: REMOTE_MNEMONIC,\n localDerivation: \"//custom//path\",\n });\n expect(result.localDerivation).toBe(\"//custom//path\");\n const expected = deriveSr25519PublicKey(\n createSr25519Secret(mnemonicToEntropy(LOCAL_MNEMONIC), \"//custom//path\"),\n );\n expect(result.localAccountId).toEqual(expected);\n });\n\n test(\"repeated calls replace the session list\", async () => {\n // Documented behavior — second call leaves only the second session\n // on disk. Callers who want multiple sessions should use separate\n // storage dirs.\n const first = await createTestSession({\n appId: \"my-app\",\n storageDir,\n sessionId: \"first\",\n });\n const second = await createTestSession({\n appId: \"my-app\",\n storageDir,\n sessionId: \"second\",\n });\n\n const hex = await readFile(join(storageDir, \"my-app_SsoSessionsV3.json\"), \"utf-8\");\n const decoded = sessionsCodec.dec(fromHex(hex));\n expect(decoded).toHaveLength(1);\n expect(decoded[0].id).toBe(\"second\");\n // The first session's UserSecretsV2 file is left behind (not cleaned).\n // This matches a real logout flow as well, so we don't try to hide it.\n await expect(\n readFile(join(storageDir, `my-app_UserSecretsV2_${first.sessionId}.json`), \"utf-8\"),\n ).resolves.toMatch(/^0x/);\n await expect(\n readFile(\n join(storageDir, `my-app_UserSecretsV2_${second.sessionId}.json`),\n \"utf-8\",\n ),\n ).resolves.toMatch(/^0x/);\n });\n\n test(\"AccountIdCodec is Bytes(32) — keep this invariant if upstream changes\", () => {\n // Guards against silent upstream changes to the session-account\n // codec shape that would make our synthesized files unreadable.\n const encoded = AccountIdCodec.enc(createAccountId(new Uint8Array(32).fill(7)));\n expect(encoded).toHaveLength(32);\n });\n });\n}\n"],"mappings":"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","names":[]}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@parity/product-sdk-terminal",
|
|
3
3
|
"description": "QR code login and signing for CLI/terminal apps via Polkadot mobile wallet pairing",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.5.0",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"sideEffects": false,
|
|
7
7
|
"main": "./dist/index.js",
|
|
@@ -31,9 +31,9 @@
|
|
|
31
31
|
"@noble/ciphers": "^2.1.0",
|
|
32
32
|
"@noble/curves": "^2.0.1",
|
|
33
33
|
"@noble/hashes": "^2.2.0",
|
|
34
|
-
"@novasamatech/host-papp": "^0.8.
|
|
35
|
-
"@novasamatech/statement-store": "^0.8.
|
|
36
|
-
"@novasamatech/storage-adapter": "^0.8.
|
|
34
|
+
"@novasamatech/host-papp": "^0.8.7-2",
|
|
35
|
+
"@novasamatech/statement-store": "^0.8.7-2",
|
|
36
|
+
"@novasamatech/storage-adapter": "^0.8.7-2",
|
|
37
37
|
"@polkadot-api/substrate-bindings": "^0.20.2",
|
|
38
38
|
"@polkadot-api/utils": "^0.4.0",
|
|
39
39
|
"@polkadot-api/ws-provider": "^0.9.0",
|
|
@@ -46,7 +46,7 @@
|
|
|
46
46
|
"@parity/product-sdk-logger": "0.1.1"
|
|
47
47
|
},
|
|
48
48
|
"devDependencies": {
|
|
49
|
-
"@novasamatech/substrate-slot-sr25519-wasm": "^0.8.
|
|
49
|
+
"@novasamatech/substrate-slot-sr25519-wasm": "^0.8.7-2",
|
|
50
50
|
"@types/qrcode": "^1.5.5",
|
|
51
51
|
"tsup": "^8.5.1",
|
|
52
52
|
"typescript": "^5.9.3",
|