@novasamatech/statement-store 0.7.0-0 → 0.7.0-1
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/adapter/lazyClient.d.ts +1 -2
- package/dist/adapter/lazyClient.js +24 -3
- package/dist/adapter/rpc.js +22 -22
- package/dist/adapter/types.d.ts +11 -2
- package/dist/crypto.d.ts +1 -1
- package/dist/model/session.js +1 -1
- package/dist/session/encyption.js +1 -1
- package/dist/session/messageMapper.js +9 -9
- package/dist/session/session.js +37 -17
- package/dist/session/session.spec.js +164 -75
- package/dist/session/statementProver.js +1 -1
- package/package.json +9 -8
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import type { RequestFn, SubscribeFn } from '@novasamatech/sdk-statement';
|
|
2
|
-
import type { JsonRpcProvider } from '
|
|
3
|
-
import type { PolkadotClient } from 'polkadot-api';
|
|
2
|
+
import type { JsonRpcProvider, PolkadotClient } from 'polkadot-api';
|
|
4
3
|
export type LazyClient = ReturnType<typeof createLazyClient>;
|
|
5
4
|
export declare const createLazyClient: (provider: JsonRpcProvider) => {
|
|
6
5
|
getClient(): PolkadotClient;
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { createClient as createSubstrateClient } from '@polkadot-api/substrate-client';
|
|
2
2
|
import { createClient as createPolkadotClient } from 'polkadot-api';
|
|
3
|
+
// eslint-disable-next-line @typescript-eslint/no-empty-function
|
|
4
|
+
const noop = () => { };
|
|
3
5
|
export const createLazyClient = (provider) => {
|
|
4
6
|
let polkadotClient = null;
|
|
5
7
|
let substrateClient = null;
|
|
@@ -28,12 +30,31 @@ export const createLazyClient = (provider) => {
|
|
|
28
30
|
getSubscribeFn() {
|
|
29
31
|
const c = getSubstrateClient();
|
|
30
32
|
return (method, params, onMessage, onError) => {
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
33
|
+
let subscriptionId = null;
|
|
34
|
+
let unsubscribeLocal = null;
|
|
35
|
+
const cancelRequest = c._request(method, params, {
|
|
36
|
+
onSuccess: (subId, followSubscription) => {
|
|
37
|
+
subscriptionId = subId;
|
|
38
|
+
unsubscribeLocal = followSubscription(subId, { next: onMessage, error: onError });
|
|
34
39
|
},
|
|
35
40
|
onError,
|
|
36
41
|
});
|
|
42
|
+
// Derive the unsubscribe RPC method from the subscribe method name
|
|
43
|
+
// e.g. statement_subscribeStatement -> statement_unsubscribeStatement
|
|
44
|
+
const unsubscribeMethod = method.replace('subscribe', 'unsubscribe');
|
|
45
|
+
return () => {
|
|
46
|
+
if (unsubscribeLocal) {
|
|
47
|
+
unsubscribeLocal();
|
|
48
|
+
// Send the server-side unsubscribe RPC call
|
|
49
|
+
c._request(unsubscribeMethod, [subscriptionId], {
|
|
50
|
+
onSuccess: noop,
|
|
51
|
+
onError: noop,
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
else {
|
|
55
|
+
cancelRequest();
|
|
56
|
+
}
|
|
57
|
+
};
|
|
37
58
|
};
|
|
38
59
|
},
|
|
39
60
|
disconnect() {
|
package/dist/adapter/rpc.js
CHANGED
|
@@ -1,15 +1,19 @@
|
|
|
1
|
+
import { toHex } from '@novasamatech/scale';
|
|
1
2
|
import { createStatementSdk } from '@novasamatech/sdk-statement';
|
|
2
|
-
import { toHex } from '@polkadot-api/utils';
|
|
3
3
|
import { errAsync, fromPromise, okAsync } from 'neverthrow';
|
|
4
4
|
import { toError } from '../helpers.js';
|
|
5
5
|
import { AccountFullError, AlreadyExpiredError, BadProofError, DataTooLargeError, EncodingTooLargeError, ExpiryTooLowError, InternalStoreError, KnownExpiredError, NoAllowanceError, NoProofError, StorageFullError, } from './types.js';
|
|
6
|
-
function
|
|
7
|
-
|
|
6
|
+
function toSdkTopicFilter(filter) {
|
|
7
|
+
if ('matchAll' in filter) {
|
|
8
|
+
return { matchAll: filter.matchAll.map(toHex) };
|
|
9
|
+
}
|
|
10
|
+
return { matchAny: filter.matchAny.map(toHex) };
|
|
8
11
|
}
|
|
9
|
-
function
|
|
10
|
-
if (
|
|
11
|
-
return '
|
|
12
|
-
|
|
12
|
+
function createKey(filter) {
|
|
13
|
+
if ('matchAll' in filter) {
|
|
14
|
+
return `matchAll:${filter.matchAll.map(toHex).sort().join(',')}`;
|
|
15
|
+
}
|
|
16
|
+
return `matchAny:${filter.matchAny.map(toHex).sort().join(',')}`;
|
|
13
17
|
}
|
|
14
18
|
export function createPapiStatementStoreAdapter(lazyClient) {
|
|
15
19
|
const sdk = createStatementSdk(lazyClient.getRequestFn(), lazyClient.getSubscribeFn());
|
|
@@ -25,28 +29,24 @@ export function createPapiStatementStoreAdapter(lazyClient) {
|
|
|
25
29
|
return list;
|
|
26
30
|
}
|
|
27
31
|
function removeCallback(key, callback) {
|
|
28
|
-
|
|
32
|
+
const list = callbacks.get(key);
|
|
29
33
|
if (!list)
|
|
30
34
|
return [];
|
|
31
|
-
|
|
32
|
-
if (
|
|
35
|
+
const idx = list.indexOf(callback);
|
|
36
|
+
if (idx !== -1)
|
|
37
|
+
list.splice(idx, 1);
|
|
38
|
+
if (list.length === 0)
|
|
33
39
|
callbacks.delete(key);
|
|
34
|
-
}
|
|
35
|
-
else {
|
|
36
|
-
callbacks.set(key, list);
|
|
37
|
-
}
|
|
38
40
|
return list;
|
|
39
41
|
}
|
|
40
42
|
const adapter = {
|
|
41
|
-
queryStatements(
|
|
42
|
-
|
|
43
|
-
return fromPromise(sdk.getStatements(filter), toError);
|
|
43
|
+
queryStatements(filter) {
|
|
44
|
+
return fromPromise(sdk.getStatements(toSdkTopicFilter(filter)), toError);
|
|
44
45
|
},
|
|
45
|
-
subscribeStatements(
|
|
46
|
-
const key = createKey(
|
|
46
|
+
subscribeStatements(filter, callback) {
|
|
47
|
+
const key = createKey(filter);
|
|
47
48
|
const list = addCallback(key, callback);
|
|
48
49
|
if (list.length === 1) {
|
|
49
|
-
const filter = toTopicFilter(topics);
|
|
50
50
|
let batch = [];
|
|
51
51
|
let flushScheduled = false;
|
|
52
52
|
const flush = () => {
|
|
@@ -58,11 +58,11 @@ export function createPapiStatementStoreAdapter(lazyClient) {
|
|
|
58
58
|
const currentCallbacks = callbacks.get(key);
|
|
59
59
|
if (currentCallbacks) {
|
|
60
60
|
for (const fn of currentCallbacks) {
|
|
61
|
-
fn(statements);
|
|
61
|
+
fn({ statements, isComplete: true });
|
|
62
62
|
}
|
|
63
63
|
}
|
|
64
64
|
};
|
|
65
|
-
const unsub = sdk.subscribeStatements(filter, statement => {
|
|
65
|
+
const unsub = sdk.subscribeStatements(toSdkTopicFilter(filter), statement => {
|
|
66
66
|
batch.push(statement);
|
|
67
67
|
if (!flushScheduled) {
|
|
68
68
|
flushScheduled = true;
|
package/dist/adapter/types.d.ts
CHANGED
|
@@ -1,8 +1,17 @@
|
|
|
1
1
|
import type { SignedStatement, Statement } from '@novasamatech/sdk-statement';
|
|
2
2
|
import type { ResultAsync } from 'neverthrow';
|
|
3
|
+
export type TopicFilter = {
|
|
4
|
+
matchAll: Uint8Array[];
|
|
5
|
+
} | {
|
|
6
|
+
matchAny: Uint8Array[];
|
|
7
|
+
};
|
|
8
|
+
export type StatementsPage = {
|
|
9
|
+
statements: Statement[];
|
|
10
|
+
isComplete: boolean;
|
|
11
|
+
};
|
|
3
12
|
export type StatementStoreAdapter = {
|
|
4
|
-
queryStatements(
|
|
5
|
-
subscribeStatements(
|
|
13
|
+
queryStatements(filter: TopicFilter, destination?: Uint8Array): ResultAsync<Statement[], Error>;
|
|
14
|
+
subscribeStatements(filter: TopicFilter, callback: (page: StatementsPage) => unknown): VoidFunction;
|
|
6
15
|
submitStatement(statement: SignedStatement): ResultAsync<void, DataTooLargeError | ExpiryTooLowError | AccountFullError | StorageFullError | NoProofError | BadProofError | EncodingTooLargeError | NoAllowanceError | AlreadyExpiredError | KnownExpiredError | InternalStoreError | Error>;
|
|
7
16
|
};
|
|
8
17
|
export declare class DataTooLargeError extends Error {
|
package/dist/crypto.d.ts
CHANGED
|
@@ -4,7 +4,7 @@ export declare function stringToBytes(str: string): Uint8Array<ArrayBuffer>;
|
|
|
4
4
|
/**
|
|
5
5
|
* blake2b_256 with key
|
|
6
6
|
*/
|
|
7
|
-
export declare function khash(secret: Uint8Array, message: Uint8Array): Uint8Array<ArrayBufferLike>;
|
|
7
|
+
export declare function khash(secret: Uint8Array, message: Uint8Array): Uint8Array<ArrayBufferLike> & Uint8Array<ArrayBuffer>;
|
|
8
8
|
export declare function createSr25519Secret(entropy: Uint8Array, derivation?: string): Uint8Array<ArrayBufferLike>;
|
|
9
9
|
export declare function createSr25519Derivation(secret: Uint8Array, derivation: string): Uint8Array<ArrayBufferLike>;
|
|
10
10
|
export declare function deriveSr25519PublicKey(secret: Uint8Array): Uint8Array;
|
package/dist/model/session.js
CHANGED
|
@@ -2,8 +2,8 @@ import { gcm } from '@noble/ciphers/aes.js';
|
|
|
2
2
|
import { hkdf } from '@noble/hashes/hkdf.js';
|
|
3
3
|
import { sha256 } from '@noble/hashes/sha2.js';
|
|
4
4
|
import { randomBytes } from '@noble/hashes/utils.js';
|
|
5
|
-
import { mergeUint8 } from '@polkadot-api/utils';
|
|
6
5
|
import { Result, fromThrowable } from 'neverthrow';
|
|
6
|
+
import { mergeUint8 } from 'polkadot-api/utils';
|
|
7
7
|
export function createEncryption(sharedSecret) {
|
|
8
8
|
const salt = new Uint8Array(); // secure enough since P256 random keys provide enough entropy
|
|
9
9
|
const info = new Uint8Array(); // no need to introduce any context
|
|
@@ -1,20 +1,20 @@
|
|
|
1
|
+
function decode(payload, codec) {
|
|
2
|
+
try {
|
|
3
|
+
return { status: 'parsed', value: codec.dec(payload) };
|
|
4
|
+
}
|
|
5
|
+
catch {
|
|
6
|
+
return { status: 'failed', value: payload };
|
|
7
|
+
}
|
|
8
|
+
}
|
|
1
9
|
export function toMessage(statementData, codec) {
|
|
2
10
|
switch (statementData.tag) {
|
|
3
11
|
case 'request': {
|
|
4
|
-
const decode = (payload) => {
|
|
5
|
-
try {
|
|
6
|
-
return { status: 'parsed', value: codec.dec(payload) };
|
|
7
|
-
}
|
|
8
|
-
catch {
|
|
9
|
-
return { status: 'failed', value: payload };
|
|
10
|
-
}
|
|
11
|
-
};
|
|
12
12
|
return statementData.value.data.map((payload, index) => {
|
|
13
13
|
return {
|
|
14
14
|
type: 'request',
|
|
15
15
|
localId: `${statementData.value.requestId}-${index.toString()}`,
|
|
16
16
|
requestId: statementData.value.requestId,
|
|
17
|
-
payload: decode(payload),
|
|
17
|
+
payload: decode(payload, codec),
|
|
18
18
|
};
|
|
19
19
|
});
|
|
20
20
|
}
|
package/dist/session/session.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { createExpiryFromDuration } from '@novasamatech/sdk-statement';
|
|
2
|
-
import { toHex } from '@polkadot-api/utils';
|
|
3
2
|
import { nanoid } from 'nanoid';
|
|
4
3
|
import { ResultAsync, err, errAsync, fromPromise, fromThrowable, ok, okAsync } from 'neverthrow';
|
|
4
|
+
import { toHex } from 'polkadot-api/utils';
|
|
5
5
|
import { khash, stringToBytes } from '../crypto.js';
|
|
6
6
|
import { nonNullable, toError } from '../helpers.js';
|
|
7
7
|
import { createSessionId } from '../model/session.js';
|
|
@@ -30,6 +30,7 @@ export function createSession({ localAccount, remoteAccount, statementStore, enc
|
|
|
30
30
|
let subscribers = [];
|
|
31
31
|
const bufferedMessages = [];
|
|
32
32
|
let storeUnsub = null;
|
|
33
|
+
let responseStoreUnsub = null;
|
|
33
34
|
function submitStatementData(channel, topicSessionId, data) {
|
|
34
35
|
state.expiry = nextExpiry(state.expiry);
|
|
35
36
|
const expiry = state.expiry;
|
|
@@ -53,12 +54,14 @@ export function createSession({ localAccount, remoteAccount, statementStore, enc
|
|
|
53
54
|
});
|
|
54
55
|
}
|
|
55
56
|
function deliverStatementData(statementData) {
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
57
|
+
// Buffer 'request' statements unconditionally so that waitForRequestMessage
|
|
58
|
+
// registered after delivery (race condition) still receives them via subscribe() replay.
|
|
59
|
+
// Buffer everything else during initialization when there are no subscribers yet.
|
|
60
|
+
if (statementData.tag === 'request' || (subscribers.length === 0 && state.phase === 'initialization')) {
|
|
61
|
+
bufferedMessages.push(statementData);
|
|
61
62
|
}
|
|
63
|
+
if (subscribers.length === 0)
|
|
64
|
+
return;
|
|
62
65
|
for (const sub of subscribers) {
|
|
63
66
|
const messages = toMessage(statementData, sub.codec);
|
|
64
67
|
if (messages.length > 0)
|
|
@@ -76,7 +79,7 @@ export function createSession({ localAccount, remoteAccount, statementStore, enc
|
|
|
76
79
|
.map(decrypted => StatementData.dec(decrypted))
|
|
77
80
|
.orElse(() => ok(null));
|
|
78
81
|
}
|
|
79
|
-
function processIncomingStatement(statement) {
|
|
82
|
+
function processIncomingStatement(statement, responsesOnly = false) {
|
|
80
83
|
if (!statement.data)
|
|
81
84
|
return;
|
|
82
85
|
const key = toHex(statement.data);
|
|
@@ -87,6 +90,8 @@ export function createSession({ localAccount, remoteAccount, statementStore, enc
|
|
|
87
90
|
if (!statementData)
|
|
88
91
|
return;
|
|
89
92
|
if (statementData.tag === 'request') {
|
|
93
|
+
if (responsesOnly)
|
|
94
|
+
return;
|
|
90
95
|
if (statementData.value.requestId === state.incomingRequest?.requestId)
|
|
91
96
|
return;
|
|
92
97
|
state.incomingRequest = { requestId: statementData.value.requestId };
|
|
@@ -147,16 +152,24 @@ export function createSession({ localAccount, remoteAccount, statementStore, enc
|
|
|
147
152
|
function ensureStoreSubscription() {
|
|
148
153
|
if (storeUnsub)
|
|
149
154
|
return;
|
|
150
|
-
storeUnsub = statementStore.subscribeStatements([incomingSessionId],
|
|
151
|
-
for (const statement of statements) {
|
|
155
|
+
storeUnsub = statementStore.subscribeStatements({ matchAll: [incomingSessionId] }, page => {
|
|
156
|
+
for (const statement of page.statements) {
|
|
152
157
|
processIncomingStatement(statement);
|
|
153
158
|
}
|
|
154
159
|
});
|
|
160
|
+
// Subscribe to outgoing topic to receive peer ACK responses.
|
|
161
|
+
// Only process response-type statements — request-type statements on this topic
|
|
162
|
+
// are our own submissions echoed back and must be ignored.
|
|
163
|
+
responseStoreUnsub = statementStore.subscribeStatements({ matchAll: [outgoingSessionId] }, ({ statements }) => {
|
|
164
|
+
for (const statement of statements) {
|
|
165
|
+
processIncomingStatement(statement, true);
|
|
166
|
+
}
|
|
167
|
+
});
|
|
155
168
|
}
|
|
156
169
|
async function init() {
|
|
157
170
|
const [ownResult, peerResult] = await Promise.all([
|
|
158
|
-
statementStore.queryStatements([outgoingSessionId]),
|
|
159
|
-
statementStore.queryStatements([incomingSessionId]),
|
|
171
|
+
statementStore.queryStatements({ matchAll: [outgoingSessionId] }),
|
|
172
|
+
statementStore.queryStatements({ matchAll: [incomingSessionId] }),
|
|
160
173
|
]);
|
|
161
174
|
if (ownResult.isErr() || peerResult.isErr())
|
|
162
175
|
return;
|
|
@@ -179,7 +192,7 @@ export function createSession({ localAccount, remoteAccount, statementStore, enc
|
|
|
179
192
|
const peerRequest = peerDecoded.find(d => d.tag === 'request');
|
|
180
193
|
const peerResponse = peerDecoded.find(d => d.tag === 'response');
|
|
181
194
|
if (ownRequest?.tag === 'request') {
|
|
182
|
-
const hasResponse =
|
|
195
|
+
const hasResponse = ownResponse?.tag === 'response' && ownResponse.value.requestId === ownRequest.value.requestId;
|
|
183
196
|
if (!hasResponse) {
|
|
184
197
|
state.outgoingRequest = {
|
|
185
198
|
requestId: ownRequest.value.requestId,
|
|
@@ -191,7 +204,7 @@ export function createSession({ localAccount, remoteAccount, statementStore, enc
|
|
|
191
204
|
if (peerRequest?.tag === 'request') {
|
|
192
205
|
state.incomingRequest = { requestId: peerRequest.value.requestId };
|
|
193
206
|
state.respondedIncomingRequest =
|
|
194
|
-
|
|
207
|
+
peerResponse?.tag === 'response' && peerResponse.value.requestId === peerRequest.value.requestId;
|
|
195
208
|
}
|
|
196
209
|
// Notify app of any unresponded incoming request.
|
|
197
210
|
// Delivered while phase is still 'initialization' so that deliverStatementData
|
|
@@ -283,15 +296,23 @@ export function createSession({ localAccount, remoteAccount, statementStore, enc
|
|
|
283
296
|
}
|
|
284
297
|
return () => {
|
|
285
298
|
subscribers = subscribers.filter(s => s !== sub);
|
|
286
|
-
if (subscribers.length === 0
|
|
287
|
-
storeUnsub
|
|
288
|
-
|
|
299
|
+
if (subscribers.length === 0) {
|
|
300
|
+
if (storeUnsub) {
|
|
301
|
+
storeUnsub();
|
|
302
|
+
storeUnsub = null;
|
|
303
|
+
}
|
|
304
|
+
if (responseStoreUnsub) {
|
|
305
|
+
responseStoreUnsub();
|
|
306
|
+
responseStoreUnsub = null;
|
|
307
|
+
}
|
|
289
308
|
}
|
|
290
309
|
};
|
|
291
310
|
},
|
|
292
311
|
dispose() {
|
|
293
312
|
storeUnsub?.();
|
|
294
313
|
storeUnsub = null;
|
|
314
|
+
responseStoreUnsub?.();
|
|
315
|
+
responseStoreUnsub = null;
|
|
295
316
|
subscribers = [];
|
|
296
317
|
for (const [, deferred] of state.pendingDelivery) {
|
|
297
318
|
deferred.reject(new Error('Session disposed'));
|
|
@@ -302,7 +323,6 @@ export function createSession({ localAccount, remoteAccount, statementStore, enc
|
|
|
302
323
|
void init();
|
|
303
324
|
return session;
|
|
304
325
|
}
|
|
305
|
-
// ── module-level helpers ──────────────────────────────────────────────────────
|
|
306
326
|
function mapResponseCode(responseCode) {
|
|
307
327
|
switch (responseCode) {
|
|
308
328
|
case 'success':
|
|
@@ -5,33 +5,18 @@ import { describe, expect, it, vi } from 'vitest';
|
|
|
5
5
|
import { createAccountId, createLocalSessionAccount, createRemoteSessionAccount } from '../model/sessionAccount.js';
|
|
6
6
|
import { StatementData } from './scale/statementData.js';
|
|
7
7
|
import { createSession, nextExpiry } from './session.js';
|
|
8
|
-
|
|
8
|
+
import { createSr25519Prover } from './statementProver.js';
|
|
9
9
|
function makeAccounts() {
|
|
10
10
|
const localAccount = createLocalSessionAccount(createAccountId(new Uint8Array(32).fill(1)));
|
|
11
11
|
const remoteAccount = createRemoteSessionAccount(createAccountId(new Uint8Array(32).fill(2)), new Uint8Array(32).fill(3));
|
|
12
12
|
return { localAccount, remoteAccount };
|
|
13
13
|
}
|
|
14
|
-
function
|
|
14
|
+
function mockEncryption() {
|
|
15
15
|
return {
|
|
16
16
|
encrypt: (data) => ok(data),
|
|
17
17
|
decrypt: (data) => ok(data),
|
|
18
18
|
};
|
|
19
19
|
}
|
|
20
|
-
function makeProver() {
|
|
21
|
-
return {
|
|
22
|
-
generateMessageProof: s => okAsync({
|
|
23
|
-
...s,
|
|
24
|
-
proof: {
|
|
25
|
-
type: 'sr25519',
|
|
26
|
-
value: {
|
|
27
|
-
signature: `0x${'00'.repeat(64)}`,
|
|
28
|
-
signer: `0x${'00'.repeat(32)}`,
|
|
29
|
-
},
|
|
30
|
-
},
|
|
31
|
-
}),
|
|
32
|
-
verifyMessageProof: () => okAsync(true),
|
|
33
|
-
};
|
|
34
|
-
}
|
|
35
20
|
function makeAdapter() {
|
|
36
21
|
const unsub = vi.fn();
|
|
37
22
|
return {
|
|
@@ -56,14 +41,14 @@ function makeSession(overrides) {
|
|
|
56
41
|
localAccount,
|
|
57
42
|
remoteAccount,
|
|
58
43
|
statementStore: adapter,
|
|
59
|
-
encryption:
|
|
60
|
-
prover:
|
|
44
|
+
encryption: mockEncryption(),
|
|
45
|
+
prover: createSr25519Prover(new Uint8Array(64).fill(1)),
|
|
61
46
|
maxRequestSize,
|
|
62
47
|
});
|
|
63
48
|
return { session, adapter };
|
|
64
49
|
}
|
|
65
|
-
async function
|
|
66
|
-
await new Promise(resolve => setTimeout(resolve,
|
|
50
|
+
async function delay(ttl = 0) {
|
|
51
|
+
await new Promise(resolve => setTimeout(resolve, ttl));
|
|
67
52
|
}
|
|
68
53
|
describe('session', () => {
|
|
69
54
|
describe('nextExpiry', () => {
|
|
@@ -94,7 +79,7 @@ describe('session', () => {
|
|
|
94
79
|
describe('createSession initialization', () => {
|
|
95
80
|
it('queries own and peer statements on creation', async () => {
|
|
96
81
|
const { adapter } = makeSession();
|
|
97
|
-
await
|
|
82
|
+
await delay();
|
|
98
83
|
expect(adapter.queryStatements).toHaveBeenCalledTimes(2);
|
|
99
84
|
});
|
|
100
85
|
it('expiry is initialized from max own statement expiry', async () => {
|
|
@@ -110,11 +95,11 @@ describe('session', () => {
|
|
|
110
95
|
return okAsync([]);
|
|
111
96
|
});
|
|
112
97
|
const { session } = makeSession(adapter);
|
|
113
|
-
await
|
|
98
|
+
await delay();
|
|
114
99
|
// Submit a message to trigger a statement — its expiry must be greater than highExpiry
|
|
115
100
|
const rawCodec = Bytes();
|
|
116
101
|
void session.submitRequestMessage(rawCodec, new Uint8Array([1]));
|
|
117
|
-
await
|
|
102
|
+
await delay();
|
|
118
103
|
const submittedStatement = adapter.submitStatement.mock.calls[0]?.[0];
|
|
119
104
|
if (submittedStatement) {
|
|
120
105
|
expect(submittedStatement.expiry).toBeGreaterThan(highExpiry);
|
|
@@ -131,7 +116,7 @@ describe('session', () => {
|
|
|
131
116
|
return okAsync([]);
|
|
132
117
|
});
|
|
133
118
|
const { session } = makeSession(adapter);
|
|
134
|
-
await
|
|
119
|
+
await delay();
|
|
135
120
|
// Register subscriber AFTER init — buffered incoming request should be delivered
|
|
136
121
|
const callback = vi.fn();
|
|
137
122
|
session.subscribe(Bytes(), callback);
|
|
@@ -140,11 +125,10 @@ describe('session', () => {
|
|
|
140
125
|
it('transitions to active phase after queries complete', async () => {
|
|
141
126
|
const { session, adapter } = makeSession();
|
|
142
127
|
// Before init completes, submitRequestMessage queues the message
|
|
143
|
-
|
|
144
|
-
void session.submitRequestMessage(rawCodec, 'hello');
|
|
128
|
+
void session.submitRequestMessage(str, 'hello');
|
|
145
129
|
// Statement should NOT be submitted yet (still initializing)
|
|
146
130
|
expect(adapter.submitStatement).not.toHaveBeenCalled();
|
|
147
|
-
await
|
|
131
|
+
await delay();
|
|
148
132
|
// After init, queued messages are processed → submitStatement called
|
|
149
133
|
expect(adapter.submitStatement).toHaveBeenCalled();
|
|
150
134
|
});
|
|
@@ -162,11 +146,11 @@ describe('session', () => {
|
|
|
162
146
|
return okAsync([]); // no peer response
|
|
163
147
|
});
|
|
164
148
|
const { session } = makeSession(adapter);
|
|
165
|
-
await
|
|
149
|
+
await delay();
|
|
166
150
|
// If outgoingRequest was restored, a new message appends to it
|
|
167
151
|
const codec = str;
|
|
168
152
|
void session.submitRequestMessage(codec, 'hello');
|
|
169
|
-
await
|
|
153
|
+
await delay();
|
|
170
154
|
expect(adapter.submitStatement).toHaveBeenCalled();
|
|
171
155
|
});
|
|
172
156
|
it('clears outgoingRequest when peer has a matching response', async () => {
|
|
@@ -177,16 +161,17 @@ describe('session', () => {
|
|
|
177
161
|
let callCount = 0;
|
|
178
162
|
adapter.queryStatements.mockImplementation(() => {
|
|
179
163
|
callCount++;
|
|
164
|
+
// Outgoing topic contains both our request AND the peer's response
|
|
180
165
|
if (callCount === 1)
|
|
181
|
-
return okAsync([ownRequest]);
|
|
182
|
-
return okAsync([
|
|
166
|
+
return okAsync([ownRequest, peerResponse]);
|
|
167
|
+
return okAsync([]);
|
|
183
168
|
});
|
|
184
169
|
const { session } = makeSession(adapter);
|
|
185
|
-
await
|
|
170
|
+
await delay();
|
|
186
171
|
// No pending outgoing request — new message creates a brand new request
|
|
187
172
|
const codec = str;
|
|
188
173
|
void session.submitRequestMessage(codec, 'hi');
|
|
189
|
-
await
|
|
174
|
+
await delay();
|
|
190
175
|
// submitStatement called exactly once (for the new message only)
|
|
191
176
|
expect(adapter.submitStatement).toHaveBeenCalledTimes(1);
|
|
192
177
|
});
|
|
@@ -202,7 +187,7 @@ describe('session', () => {
|
|
|
202
187
|
return okAsync([]);
|
|
203
188
|
});
|
|
204
189
|
const { session } = makeSession(adapter);
|
|
205
|
-
await
|
|
190
|
+
await delay();
|
|
206
191
|
// Calling submitResponseMessage with restored requestId should succeed
|
|
207
192
|
const result = await session.submitResponseMessage(requestId, 'success');
|
|
208
193
|
expect(result.isOk()).toBe(true);
|
|
@@ -216,11 +201,12 @@ describe('session', () => {
|
|
|
216
201
|
adapter.queryStatements.mockImplementation(() => {
|
|
217
202
|
callCount++;
|
|
218
203
|
if (callCount === 1)
|
|
219
|
-
return okAsync([
|
|
220
|
-
|
|
204
|
+
return okAsync([]);
|
|
205
|
+
// Incoming topic contains both the peer's request AND our response
|
|
206
|
+
return okAsync([peerRequest, ownResponse]);
|
|
221
207
|
});
|
|
222
208
|
const { session } = makeSession(adapter);
|
|
223
|
-
await
|
|
209
|
+
await delay();
|
|
224
210
|
// Already responded — submitResponseMessage should return ok without submitting again
|
|
225
211
|
const submitsBefore = adapter.submitStatement.mock.calls.length;
|
|
226
212
|
const result = await session.submitResponseMessage(requestId, 'success');
|
|
@@ -242,7 +228,7 @@ describe('session', () => {
|
|
|
242
228
|
return okAsync([]);
|
|
243
229
|
});
|
|
244
230
|
const { session } = makeSession(adapter);
|
|
245
|
-
await
|
|
231
|
+
await delay(); // init completes
|
|
246
232
|
const callback = vi.fn();
|
|
247
233
|
session.subscribe(rawCodec, callback);
|
|
248
234
|
expect(callback).toHaveBeenCalledTimes(1);
|
|
@@ -264,7 +250,7 @@ describe('session', () => {
|
|
|
264
250
|
const { session } = makeSession(adapter);
|
|
265
251
|
const callback = vi.fn();
|
|
266
252
|
session.subscribe(rawCodec, callback); // before init completes
|
|
267
|
-
await
|
|
253
|
+
await delay();
|
|
268
254
|
expect(callback).toHaveBeenCalledTimes(1);
|
|
269
255
|
const messages2 = callback.mock.calls[0][0];
|
|
270
256
|
expect(messages2[0]?.requestId).toBe(requestId);
|
|
@@ -281,17 +267,17 @@ describe('session', () => {
|
|
|
281
267
|
return okAsync([]);
|
|
282
268
|
});
|
|
283
269
|
let subscribeCallback;
|
|
284
|
-
adapter.subscribeStatements.mockImplementation((
|
|
270
|
+
adapter.subscribeStatements.mockImplementation((_filter, cb) => {
|
|
285
271
|
subscribeCallback = cb;
|
|
286
272
|
return vi.fn();
|
|
287
273
|
});
|
|
288
274
|
const { session } = makeSession(adapter);
|
|
289
|
-
await
|
|
275
|
+
await delay(); // init sees peerRequest, adds to seenStatements
|
|
290
276
|
const appCallback = vi.fn();
|
|
291
277
|
session.subscribe(rawCodec, appCallback);
|
|
292
278
|
// Simulate subscription delivering the same statement again
|
|
293
|
-
subscribeCallback([peerRequest]);
|
|
294
|
-
await
|
|
279
|
+
subscribeCallback({ statements: [peerRequest], isComplete: true });
|
|
280
|
+
await delay();
|
|
295
281
|
// Should only be called once (from buffered init message), not again from subscription
|
|
296
282
|
expect(appCallback).toHaveBeenCalledTimes(1);
|
|
297
283
|
});
|
|
@@ -299,33 +285,139 @@ describe('session', () => {
|
|
|
299
285
|
const requestId = 'no-auto-resp';
|
|
300
286
|
const peerRequest = makeStatement({ tag: 'request', value: { requestId, data: [new Uint8Array([1])] } });
|
|
301
287
|
const adapter = makeAdapter();
|
|
302
|
-
|
|
303
|
-
adapter.subscribeStatements.mockImplementation((
|
|
304
|
-
|
|
288
|
+
const subscribeCallbacks = [];
|
|
289
|
+
adapter.subscribeStatements.mockImplementation((_filter, cb) => {
|
|
290
|
+
subscribeCallbacks.push(cb);
|
|
305
291
|
return vi.fn();
|
|
306
292
|
});
|
|
307
293
|
adapter.queryStatements.mockReturnValue(okAsync([]));
|
|
308
294
|
const { session } = makeSession(adapter);
|
|
309
|
-
await
|
|
295
|
+
await delay();
|
|
310
296
|
const callback = vi.fn();
|
|
311
297
|
session.subscribe(rawCodec, callback);
|
|
312
298
|
adapter.submitStatement.mockClear();
|
|
313
|
-
|
|
314
|
-
|
|
299
|
+
// Fire on the incoming topic callback (first subscription)
|
|
300
|
+
subscribeCallbacks[0]({ statements: [peerRequest], isComplete: true });
|
|
301
|
+
await delay();
|
|
315
302
|
// Message delivered to app callback but no automatic response submitted
|
|
316
303
|
expect(callback).toHaveBeenCalled();
|
|
317
304
|
expect(adapter.submitStatement).not.toHaveBeenCalled();
|
|
318
305
|
});
|
|
306
|
+
it('delivers peer request to a subscriber that registers after the batch notification (race condition)', async () => {
|
|
307
|
+
// Regression test for PB-439: when peer's request and the ACK response arrive in the
|
|
308
|
+
// same subscribeStatements batch, the request is processed before waitForRequestMessage
|
|
309
|
+
// has a chance to register its subscriber. The fix ensures request statements are always
|
|
310
|
+
// buffered so late subscribers (simulating waitForRequestMessage called in .andThen()
|
|
311
|
+
// after waitForResponseMessage resolves) still receive them.
|
|
312
|
+
const subscribeCallbacks = [];
|
|
313
|
+
const subscribeStatements = vi.fn().mockImplementation((_filter, cb) => {
|
|
314
|
+
subscribeCallbacks.push(cb);
|
|
315
|
+
return vi.fn();
|
|
316
|
+
});
|
|
317
|
+
const { session } = makeSession({ subscribeStatements });
|
|
318
|
+
await delay();
|
|
319
|
+
// Register a dummy subscriber to activate the store subscription (simulates
|
|
320
|
+
// any pre-existing subscriber in the session, e.g. the app listening for messages).
|
|
321
|
+
const dummyUnsub = session.subscribe(rawCodec, vi.fn());
|
|
322
|
+
const peerRequestId = 'race-condition-request';
|
|
323
|
+
const peerRequest = makeStatement({
|
|
324
|
+
tag: 'request',
|
|
325
|
+
value: { requestId: peerRequestId, data: [new Uint8Array([42])] },
|
|
326
|
+
});
|
|
327
|
+
// Peer request arrives on the incoming topic (first subscription) while the
|
|
328
|
+
// dummy subscriber is active but waitForRequestMessage hasn't registered its
|
|
329
|
+
// subscriber yet (the race condition scenario).
|
|
330
|
+
subscribeCallbacks[0]({ statements: [peerRequest], isComplete: true });
|
|
331
|
+
await delay();
|
|
332
|
+
// Now the late subscriber registers (simulates waitForRequestMessage being called
|
|
333
|
+
// in the .andThen() chain after waitForResponseMessage resolves).
|
|
334
|
+
const lateCallback = vi.fn();
|
|
335
|
+
session.subscribe(rawCodec, lateCallback);
|
|
336
|
+
// The late subscriber must receive the buffered peer request, otherwise
|
|
337
|
+
// waitForRequestMessage would hang indefinitely.
|
|
338
|
+
expect(lateCallback).toHaveBeenCalledTimes(1);
|
|
339
|
+
const messages = lateCallback.mock.calls[0][0];
|
|
340
|
+
expect(messages[0]?.type).toBe('request');
|
|
341
|
+
expect(messages[0]?.requestId).toBe(peerRequestId);
|
|
342
|
+
dummyUnsub();
|
|
343
|
+
});
|
|
319
344
|
it('unsubscribing last subscriber tears down the store subscription', () => {
|
|
320
345
|
const { session, adapter } = makeSession();
|
|
321
346
|
const unsub = session.subscribe(rawCodec, vi.fn());
|
|
322
|
-
expect(adapter.subscribeStatements).toHaveBeenCalledTimes(
|
|
347
|
+
expect(adapter.subscribeStatements).toHaveBeenCalledTimes(2);
|
|
323
348
|
unsub();
|
|
324
349
|
// subscribeStatements returns a mock unsubscribe fn — verify it was called
|
|
325
350
|
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
326
351
|
const storeMockUnsub = adapter.subscribeStatements.mock.results[0].value;
|
|
327
352
|
expect(storeMockUnsub).toHaveBeenCalled();
|
|
328
353
|
});
|
|
354
|
+
it('subscribes to outgoing topic for peer responses alongside incoming topic', () => {
|
|
355
|
+
const { session, adapter } = makeSession();
|
|
356
|
+
session.subscribe(rawCodec, vi.fn());
|
|
357
|
+
// Two subscriptions: one for incoming (peer requests), one for outgoing (peer responses)
|
|
358
|
+
expect(adapter.subscribeStatements).toHaveBeenCalledTimes(2);
|
|
359
|
+
});
|
|
360
|
+
it('tears down outgoing subscription when last subscriber leaves', () => {
|
|
361
|
+
const { session, adapter } = makeSession();
|
|
362
|
+
const unsub = session.subscribe(rawCodec, vi.fn());
|
|
363
|
+
unsub();
|
|
364
|
+
// Both unsubscribe functions should be called
|
|
365
|
+
for (const result of adapter.subscribeStatements.mock.results) {
|
|
366
|
+
const mockUnsub = result.value;
|
|
367
|
+
expect(mockUnsub).toHaveBeenCalled();
|
|
368
|
+
}
|
|
369
|
+
});
|
|
370
|
+
it('delivers peer response from outgoing topic subscription to subscribers', async () => {
|
|
371
|
+
const subscribeCallbacks = [];
|
|
372
|
+
const subscribeStatements = vi.fn().mockImplementation((_topics, cb) => {
|
|
373
|
+
subscribeCallbacks.push(cb);
|
|
374
|
+
return vi.fn();
|
|
375
|
+
});
|
|
376
|
+
const { session, adapter } = makeSession({ subscribeStatements });
|
|
377
|
+
await delay();
|
|
378
|
+
// Submit a request so the session has an outgoingRequest
|
|
379
|
+
void session.submitRequestMessage(rawCodec, new Uint8Array([1]));
|
|
380
|
+
await delay();
|
|
381
|
+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
382
|
+
const submitted = adapter.submitStatement.mock.calls[0][0];
|
|
383
|
+
const decoded = StatementData.dec(submitted.data);
|
|
384
|
+
const requestId = decoded.tag === 'request' ? decoded.value.requestId : '';
|
|
385
|
+
const callback = vi.fn();
|
|
386
|
+
session.subscribe(rawCodec, callback);
|
|
387
|
+
// Deliver the response via the SECOND subscription callback (outgoing topic)
|
|
388
|
+
// subscribeCallbacks[0] = incoming topic, subscribeCallbacks[1] = outgoing topic
|
|
389
|
+
const responseStatement = makeStatement({
|
|
390
|
+
tag: 'response',
|
|
391
|
+
value: { requestId, responseCode: 'success' },
|
|
392
|
+
});
|
|
393
|
+
subscribeCallbacks[1]({ statements: [responseStatement], isComplete: true });
|
|
394
|
+
await delay();
|
|
395
|
+
// Subscriber should receive the response
|
|
396
|
+
const allCalls = callback.mock.calls.flat();
|
|
397
|
+
const responseMessages = allCalls.flat().filter((m) => m.type === 'response');
|
|
398
|
+
expect(responseMessages.length).toBeGreaterThan(0);
|
|
399
|
+
});
|
|
400
|
+
it('ignores request-type statements from outgoing topic subscription', async () => {
|
|
401
|
+
const subscribeCallbacks = [];
|
|
402
|
+
const subscribeStatements = vi.fn().mockImplementation((_topics, cb) => {
|
|
403
|
+
subscribeCallbacks.push(cb);
|
|
404
|
+
return vi.fn();
|
|
405
|
+
});
|
|
406
|
+
const { session } = makeSession({ subscribeStatements });
|
|
407
|
+
await delay();
|
|
408
|
+
const callback = vi.fn();
|
|
409
|
+
session.subscribe(rawCodec, callback);
|
|
410
|
+
callback.mockClear(); // clear any buffered init messages
|
|
411
|
+
// Deliver a request via the outgoing topic subscription (would be our own echoed back)
|
|
412
|
+
const ownRequest = makeStatement({
|
|
413
|
+
tag: 'request',
|
|
414
|
+
value: { requestId: 'own-req', data: [new Uint8Array([1])] },
|
|
415
|
+
});
|
|
416
|
+
subscribeCallbacks[1]({ statements: [ownRequest], isComplete: true });
|
|
417
|
+
await delay();
|
|
418
|
+
// Should NOT be delivered to subscriber (filtered by responsesOnly flag)
|
|
419
|
+
expect(callback).not.toHaveBeenCalled();
|
|
420
|
+
});
|
|
329
421
|
});
|
|
330
422
|
describe('submitResponseMessage', () => {
|
|
331
423
|
it('is idempotent — second call does not submit again', async () => {
|
|
@@ -340,7 +432,7 @@ describe('session', () => {
|
|
|
340
432
|
return okAsync([]);
|
|
341
433
|
});
|
|
342
434
|
const { session } = makeSession(adapter);
|
|
343
|
-
await
|
|
435
|
+
await delay();
|
|
344
436
|
await session.submitResponseMessage(requestId, 'success');
|
|
345
437
|
const submitsAfterFirst = adapter.submitStatement.mock.calls.length;
|
|
346
438
|
await session.submitResponseMessage(requestId, 'success'); // second call
|
|
@@ -348,7 +440,7 @@ describe('session', () => {
|
|
|
348
440
|
});
|
|
349
441
|
it('returns error when requestId does not match incomingRequest', async () => {
|
|
350
442
|
const { session } = makeSession();
|
|
351
|
-
await
|
|
443
|
+
await delay();
|
|
352
444
|
const result = await session.submitResponseMessage('wrong-id', 'success');
|
|
353
445
|
expect(result.isErr()).toBe(true);
|
|
354
446
|
});
|
|
@@ -357,43 +449,41 @@ describe('session', () => {
|
|
|
357
449
|
const rawCodec = Bytes();
|
|
358
450
|
it('sends a single statement for the first message', async () => {
|
|
359
451
|
const { session, adapter } = makeSession();
|
|
360
|
-
await
|
|
452
|
+
await delay();
|
|
361
453
|
void session.submitRequestMessage(rawCodec, new Uint8Array([1, 2, 3]));
|
|
362
|
-
await
|
|
454
|
+
await delay();
|
|
363
455
|
expect(adapter.submitStatement).toHaveBeenCalledTimes(1);
|
|
364
456
|
});
|
|
365
457
|
it('appends second message to existing request (resubmits with new requestId)', async () => {
|
|
366
458
|
const { session, adapter } = makeSession();
|
|
367
|
-
await
|
|
459
|
+
await delay();
|
|
368
460
|
void session.submitRequestMessage(rawCodec, new Uint8Array([1]));
|
|
369
461
|
void session.submitRequestMessage(rawCodec, new Uint8Array([2]));
|
|
370
|
-
await
|
|
462
|
+
await delay();
|
|
371
463
|
// Two submits: first for msg1, second for msg1+msg2 batched
|
|
372
464
|
expect(adapter.submitStatement).toHaveBeenCalledTimes(2);
|
|
373
465
|
});
|
|
374
466
|
it('queues message that exceeds maxRequestSize', async () => {
|
|
375
467
|
const { session, adapter } = makeSession({ maxRequestSize: 5 });
|
|
376
|
-
await
|
|
468
|
+
await delay();
|
|
377
469
|
void session.submitRequestMessage(rawCodec, new Uint8Array([1, 2, 3])); // 3 bytes — fits
|
|
378
470
|
void session.submitRequestMessage(rawCodec, new Uint8Array([4, 5, 6, 7])); // 4 bytes — doesn't fit with existing
|
|
379
|
-
await
|
|
471
|
+
await delay();
|
|
380
472
|
// Only first message sent; second is queued
|
|
381
473
|
expect(adapter.submitStatement).toHaveBeenCalledTimes(1);
|
|
382
474
|
});
|
|
383
475
|
it('drains message queue after response received', async () => {
|
|
384
476
|
let subscribeCallback;
|
|
385
|
-
const subscribeStatements = vi
|
|
386
|
-
.fn()
|
|
387
|
-
.mockImplementation((_topics, cb) => {
|
|
477
|
+
const subscribeStatements = vi.fn().mockImplementation((_filter, cb) => {
|
|
388
478
|
subscribeCallback = cb;
|
|
389
479
|
return vi.fn();
|
|
390
480
|
});
|
|
391
481
|
const { session, adapter } = makeSession({ maxRequestSize: 5, subscribeStatements });
|
|
392
|
-
await
|
|
482
|
+
await delay();
|
|
393
483
|
session.subscribe(Bytes(), vi.fn()); // ensure store subscription is active
|
|
394
484
|
void session.submitRequestMessage(rawCodec, new Uint8Array([1, 2, 3])); // sent
|
|
395
485
|
void session.submitRequestMessage(rawCodec, new Uint8Array([4, 5, 6])); // queued (doesn't fit)
|
|
396
|
-
await
|
|
486
|
+
await delay();
|
|
397
487
|
const submitCountBefore = adapter.submitStatement.mock.calls.length;
|
|
398
488
|
// Simulate peer responding to the first request
|
|
399
489
|
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
@@ -404,34 +494,33 @@ describe('session', () => {
|
|
|
404
494
|
tag: 'response',
|
|
405
495
|
value: { requestId: respondingRequestId, responseCode: 'success' },
|
|
406
496
|
});
|
|
407
|
-
subscribeCallback([responseStatement]);
|
|
408
|
-
await
|
|
497
|
+
subscribeCallback({ statements: [responseStatement], isComplete: true });
|
|
498
|
+
await delay();
|
|
409
499
|
// Queued message should now be submitted
|
|
410
500
|
expect(adapter.submitStatement.mock.calls.length).toBeGreaterThan(submitCountBefore);
|
|
411
501
|
});
|
|
412
502
|
it('waitForResponseMessage resolves when response arrives for batch', async () => {
|
|
413
503
|
let subscribeCallback;
|
|
414
|
-
const subscribeStatements = vi
|
|
415
|
-
.fn()
|
|
416
|
-
.mockImplementation((_topics, cb) => {
|
|
504
|
+
const subscribeStatements = vi.fn().mockImplementation((_filter, cb) => {
|
|
417
505
|
subscribeCallback = cb;
|
|
418
506
|
return vi.fn();
|
|
419
507
|
});
|
|
420
508
|
const { session, adapter } = makeSession({ subscribeStatements });
|
|
421
|
-
await
|
|
509
|
+
await delay();
|
|
422
510
|
session.subscribe(Bytes(), vi.fn()); // ensure store subscription is active
|
|
423
511
|
const submitResult = await session.submitRequestMessage(rawCodec, new Uint8Array([1]));
|
|
424
512
|
const token = submitResult.unwrapOr({ requestId: '' }).requestId;
|
|
425
|
-
await
|
|
513
|
+
await delay();
|
|
426
514
|
const responsePromise = session.waitForResponseMessage(token);
|
|
427
515
|
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
428
516
|
const lastStatement = adapter.submitStatement.mock.calls[adapter.submitStatement.mock.calls.length - 1][0];
|
|
429
517
|
const decoded = StatementData.dec(lastStatement.data);
|
|
430
518
|
const respondingId = decoded.tag === 'request' ? decoded.value.requestId : '';
|
|
431
|
-
subscribeCallback(
|
|
432
|
-
makeStatement({ tag: 'response', value: { requestId: respondingId, responseCode: 'success' } }),
|
|
433
|
-
|
|
434
|
-
|
|
519
|
+
subscribeCallback({
|
|
520
|
+
statements: [makeStatement({ tag: 'response', value: { requestId: respondingId, responseCode: 'success' } })],
|
|
521
|
+
isComplete: true,
|
|
522
|
+
});
|
|
523
|
+
await delay();
|
|
435
524
|
const result = await responsePromise;
|
|
436
525
|
expect(result.isOk()).toBe(true);
|
|
437
526
|
expect(result.unwrapOr({ responseCode: 'unknown' }).responseCode).toBe('success');
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { getStatementSigner, statementCodec } from '@novasamatech/sdk-statement';
|
|
2
2
|
import { compact } from '@polkadot-api/substrate-bindings';
|
|
3
|
-
import { fromHex } from '@polkadot-api/utils';
|
|
4
3
|
import { errAsync, fromPromise, fromThrowable, okAsync } from 'neverthrow';
|
|
4
|
+
import { fromHex } from 'polkadot-api/utils';
|
|
5
5
|
import { deriveSr25519PublicKey, signWithSr25519Secret, verifySr25519Signature } from '../crypto.js';
|
|
6
6
|
import { toError } from '../helpers.js';
|
|
7
7
|
export function createSr25519Prover(secret) {
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@novasamatech/statement-store",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "0.7.0-
|
|
4
|
+
"version": "0.7.0-1",
|
|
5
5
|
"description": "Statement store integration",
|
|
6
6
|
"license": "Apache-2.0",
|
|
7
7
|
"repository": {
|
|
@@ -25,15 +25,16 @@
|
|
|
25
25
|
"README.md"
|
|
26
26
|
],
|
|
27
27
|
"dependencies": {
|
|
28
|
+
"@novasamatech/scale": "0.7.0-1",
|
|
28
29
|
"@novasamatech/sdk-statement": "^0.5.0",
|
|
29
|
-
"@polkadot-api/substrate-bindings": "^0.
|
|
30
|
-
"@polkadot-api/substrate-client": "^0.
|
|
31
|
-
"@polkadot-
|
|
32
|
-
"@
|
|
33
|
-
"@noble/
|
|
34
|
-
"@noble/ciphers": "2.1.1",
|
|
30
|
+
"@polkadot-api/substrate-bindings": "^0.20.0",
|
|
31
|
+
"@polkadot-api/substrate-client": "^0.7.0",
|
|
32
|
+
"@polkadot-labs/hdkd-helpers": "^0.0.29",
|
|
33
|
+
"@noble/hashes": "2.2.0",
|
|
34
|
+
"@noble/ciphers": "2.2.0",
|
|
35
35
|
"@scure/sr25519": "1.0.0",
|
|
36
|
-
"polkadot-api": "
|
|
36
|
+
"polkadot-api": ">=2",
|
|
37
|
+
"nanoid": "5.1.9",
|
|
37
38
|
"neverthrow": "^8.2.0",
|
|
38
39
|
"scale-ts": "1.6.1"
|
|
39
40
|
},
|