@novasamatech/host-papp 0.7.9-5 → 0.7.9-6
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/debug-public.d.ts +8 -0
- package/dist/debug-public.js +7 -0
- package/dist/debugBus.d.ts +20 -0
- package/dist/debugBus.js +61 -0
- package/dist/debugTypes.d.ts +204 -0
- package/dist/debugTypes.js +1 -0
- package/dist/sso/auth/impl.js +55 -4
- package/dist/sso/sessionManager/impl.js +15 -0
- package/dist/sso/sessionManager/userSession.js +89 -19
- package/package.json +10 -5
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Lightweight public entry for the debug bus. Lives outside the main
|
|
3
|
+
* `index.ts` so consumers can import the debug surface without
|
|
4
|
+
* loading the attestation / session-manager modules, which pull in
|
|
5
|
+
* verifiablejs WASM that can't initialise in all environments.
|
|
6
|
+
*/
|
|
7
|
+
export { emitHostPappDebugMessage, hasHostPappDebugListeners, onHostPappDebugMessage } from './debugBus.js';
|
|
8
|
+
export type { AttestationDebugEvent, HostPappDebugEvent, SessionDebugEvent, SsoDebugEvent } from './debugTypes.js';
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Lightweight public entry for the debug bus. Lives outside the main
|
|
3
|
+
* `index.ts` so consumers can import the debug surface without
|
|
4
|
+
* loading the attestation / session-manager modules, which pull in
|
|
5
|
+
* verifiablejs WASM that can't initialise in all environments.
|
|
6
|
+
*/
|
|
7
|
+
export { emitHostPappDebugMessage, hasHostPappDebugListeners, onHostPappDebugMessage } from './debugBus.js';
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { HostPappDebugEvent } from './debugTypes.js';
|
|
2
|
+
/** @internal For host-papp emitters. */
|
|
3
|
+
export declare function emitHostPappDebugMessage(event: HostPappDebugEvent): void;
|
|
4
|
+
/**
|
|
5
|
+
* @internal Lets call sites skip non-trivial payload construction
|
|
6
|
+
* when nobody is listening. Equivalent in spirit to the lazy
|
|
7
|
+
* subscribe pattern in host-api's transport-level hook.
|
|
8
|
+
*/
|
|
9
|
+
export declare function hasHostPappDebugListeners(): boolean;
|
|
10
|
+
/**
|
|
11
|
+
* EXPERIMENTAL. Subscribe to every host-papp debug event across all
|
|
12
|
+
* adapters in the current process. Returns an unsubscribe function.
|
|
13
|
+
*
|
|
14
|
+
* Each listener is isolated: a throw inside one callback is logged
|
|
15
|
+
* to `console.error` and does not starve sibling subscribers — the
|
|
16
|
+
* same pattern host-api uses in its transport-level debug hook.
|
|
17
|
+
*/
|
|
18
|
+
export declare function onHostPappDebugMessage(callback: (event: HostPappDebugEvent) => void): VoidFunction;
|
|
19
|
+
/** @internal Convenience for creating flow identifiers. */
|
|
20
|
+
export declare function createFlowId(): string;
|
package/dist/debugBus.js
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { createNanoEvents } from 'nanoevents';
|
|
2
|
+
import { nanoid } from 'nanoid';
|
|
3
|
+
/**
|
|
4
|
+
* EXPERIMENTAL. Module-level bus that aggregates debug events from
|
|
5
|
+
* every host-papp adapter created in this process. Mirrors the
|
|
6
|
+
* pattern used by `onHostApiDebugMessage` in
|
|
7
|
+
* `@novasamatech/host-container` so a single subscriber can observe
|
|
8
|
+
* pairing / attestation / session activity alongside TrUAPI traffic.
|
|
9
|
+
*
|
|
10
|
+
* Subscription is *lazy in spirit*: emit sites should call
|
|
11
|
+
* `hasHostPappDebugListeners()` before constructing payloads that
|
|
12
|
+
* are non-trivial to compute.
|
|
13
|
+
*/
|
|
14
|
+
const bus = createNanoEvents();
|
|
15
|
+
let listenerCount = 0;
|
|
16
|
+
/** @internal For host-papp emitters. */
|
|
17
|
+
export function emitHostPappDebugMessage(event) {
|
|
18
|
+
if (listenerCount === 0)
|
|
19
|
+
return;
|
|
20
|
+
bus.emit('message', event);
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* @internal Lets call sites skip non-trivial payload construction
|
|
24
|
+
* when nobody is listening. Equivalent in spirit to the lazy
|
|
25
|
+
* subscribe pattern in host-api's transport-level hook.
|
|
26
|
+
*/
|
|
27
|
+
export function hasHostPappDebugListeners() {
|
|
28
|
+
return listenerCount > 0;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* EXPERIMENTAL. Subscribe to every host-papp debug event across all
|
|
32
|
+
* adapters in the current process. Returns an unsubscribe function.
|
|
33
|
+
*
|
|
34
|
+
* Each listener is isolated: a throw inside one callback is logged
|
|
35
|
+
* to `console.error` and does not starve sibling subscribers — the
|
|
36
|
+
* same pattern host-api uses in its transport-level debug hook.
|
|
37
|
+
*/
|
|
38
|
+
export function onHostPappDebugMessage(callback) {
|
|
39
|
+
listenerCount++;
|
|
40
|
+
const safeCallback = (event) => {
|
|
41
|
+
try {
|
|
42
|
+
callback(event);
|
|
43
|
+
}
|
|
44
|
+
catch (e) {
|
|
45
|
+
console.error('host-papp debug listener threw', e);
|
|
46
|
+
}
|
|
47
|
+
};
|
|
48
|
+
const unsubscribe = bus.on('message', safeCallback);
|
|
49
|
+
let disposed = false;
|
|
50
|
+
return () => {
|
|
51
|
+
if (disposed)
|
|
52
|
+
return;
|
|
53
|
+
disposed = true;
|
|
54
|
+
listenerCount = Math.max(0, listenerCount - 1);
|
|
55
|
+
unsubscribe();
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
/** @internal Convenience for creating flow identifiers. */
|
|
59
|
+
export function createFlowId() {
|
|
60
|
+
return nanoid();
|
|
61
|
+
}
|
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* EXPERIMENTAL. Discriminated union of every host-papp debug event.
|
|
3
|
+
* The taxonomy is split into three "layers": SSO pairing, guest
|
|
4
|
+
* identity attestation, and post-pairing session activity. Every
|
|
5
|
+
* variant carries `{ layer, event, flowId, timestamp, payload }`.
|
|
6
|
+
*
|
|
7
|
+
* `flowId` correlates steps that belong to the same logical flow
|
|
8
|
+
* (e.g. one full pairing dance shares a flowId across all of its
|
|
9
|
+
* steps). Conventions per layer:
|
|
10
|
+
* - `sso.*` and `attestation.*`: one fresh flowId per pairing /
|
|
11
|
+
* attestation attempt, shared across all of its steps.
|
|
12
|
+
* - `session.opened` / `session.terminated`: reuse `sessionId` as the
|
|
13
|
+
* flowId so that a `(opened, terminated)` pair stays queryable.
|
|
14
|
+
* - `session.peer_action_*` and `session.host_action_*`: use the
|
|
15
|
+
* per-message `messageId` as flowId so that received→processed /
|
|
16
|
+
* received→failed and sent→response→failed triples line up.
|
|
17
|
+
*
|
|
18
|
+
* Mark anything subscribed to this taxonomy as EXPERIMENTAL on the
|
|
19
|
+
* consumer side — events and payloads may evolve across minor
|
|
20
|
+
* versions.
|
|
21
|
+
*/
|
|
22
|
+
export type SsoDebugEvent = {
|
|
23
|
+
layer: 'sso';
|
|
24
|
+
event: 'pairing_started';
|
|
25
|
+
flowId: string;
|
|
26
|
+
timestamp: number;
|
|
27
|
+
payload: {
|
|
28
|
+
metadata: string;
|
|
29
|
+
};
|
|
30
|
+
} | {
|
|
31
|
+
layer: 'sso';
|
|
32
|
+
event: 'deeplink_generated';
|
|
33
|
+
flowId: string;
|
|
34
|
+
timestamp: number;
|
|
35
|
+
payload: {
|
|
36
|
+
deeplink: string;
|
|
37
|
+
};
|
|
38
|
+
} | {
|
|
39
|
+
layer: 'sso';
|
|
40
|
+
event: 'awaiting_response';
|
|
41
|
+
flowId: string;
|
|
42
|
+
timestamp: number;
|
|
43
|
+
payload: {
|
|
44
|
+
topic: string;
|
|
45
|
+
};
|
|
46
|
+
} | {
|
|
47
|
+
layer: 'sso';
|
|
48
|
+
event: 'response_received';
|
|
49
|
+
flowId: string;
|
|
50
|
+
timestamp: number;
|
|
51
|
+
payload: {
|
|
52
|
+
sessionId: string;
|
|
53
|
+
};
|
|
54
|
+
} | {
|
|
55
|
+
layer: 'sso';
|
|
56
|
+
event: 'session_established';
|
|
57
|
+
flowId: string;
|
|
58
|
+
timestamp: number;
|
|
59
|
+
payload: {
|
|
60
|
+
sessionId: string;
|
|
61
|
+
};
|
|
62
|
+
} | {
|
|
63
|
+
layer: 'sso';
|
|
64
|
+
event: 'pairing_failed';
|
|
65
|
+
flowId: string;
|
|
66
|
+
timestamp: number;
|
|
67
|
+
payload: {
|
|
68
|
+
reason: string;
|
|
69
|
+
};
|
|
70
|
+
};
|
|
71
|
+
export type AttestationDebugEvent = {
|
|
72
|
+
layer: 'attestation';
|
|
73
|
+
event: 'started';
|
|
74
|
+
flowId: string;
|
|
75
|
+
timestamp: number;
|
|
76
|
+
payload: {
|
|
77
|
+
candidateAccountId: string;
|
|
78
|
+
};
|
|
79
|
+
} | {
|
|
80
|
+
layer: 'attestation';
|
|
81
|
+
event: 'username_claimed';
|
|
82
|
+
flowId: string;
|
|
83
|
+
timestamp: number;
|
|
84
|
+
payload: {
|
|
85
|
+
username: string;
|
|
86
|
+
};
|
|
87
|
+
} | {
|
|
88
|
+
layer: 'attestation';
|
|
89
|
+
event: 'allowance_granted';
|
|
90
|
+
flowId: string;
|
|
91
|
+
timestamp: number;
|
|
92
|
+
payload: {
|
|
93
|
+
verifierAccountId: string;
|
|
94
|
+
};
|
|
95
|
+
} | {
|
|
96
|
+
layer: 'attestation';
|
|
97
|
+
event: 'vrf_proof_generated';
|
|
98
|
+
flowId: string;
|
|
99
|
+
timestamp: number;
|
|
100
|
+
payload: {
|
|
101
|
+
candidateAccountId: string;
|
|
102
|
+
};
|
|
103
|
+
} | {
|
|
104
|
+
layer: 'attestation';
|
|
105
|
+
event: 'person_registered';
|
|
106
|
+
flowId: string;
|
|
107
|
+
timestamp: number;
|
|
108
|
+
payload: {
|
|
109
|
+
username: string;
|
|
110
|
+
candidateAccountId: string;
|
|
111
|
+
};
|
|
112
|
+
} | {
|
|
113
|
+
layer: 'attestation';
|
|
114
|
+
event: 'completed';
|
|
115
|
+
flowId: string;
|
|
116
|
+
timestamp: number;
|
|
117
|
+
payload: {
|
|
118
|
+
username: string;
|
|
119
|
+
};
|
|
120
|
+
} | {
|
|
121
|
+
layer: 'attestation';
|
|
122
|
+
event: 'failed';
|
|
123
|
+
flowId: string;
|
|
124
|
+
timestamp: number;
|
|
125
|
+
payload: {
|
|
126
|
+
reason: string;
|
|
127
|
+
};
|
|
128
|
+
};
|
|
129
|
+
export type SessionDebugEvent = {
|
|
130
|
+
layer: 'session';
|
|
131
|
+
event: 'opened';
|
|
132
|
+
flowId: string;
|
|
133
|
+
timestamp: number;
|
|
134
|
+
payload: {
|
|
135
|
+
sessionId: string;
|
|
136
|
+
};
|
|
137
|
+
} | {
|
|
138
|
+
layer: 'session';
|
|
139
|
+
event: 'peer_action_received';
|
|
140
|
+
flowId: string;
|
|
141
|
+
timestamp: number;
|
|
142
|
+
payload: {
|
|
143
|
+
sessionId: string;
|
|
144
|
+
messageId: string;
|
|
145
|
+
actionKind: string;
|
|
146
|
+
};
|
|
147
|
+
} | {
|
|
148
|
+
layer: 'session';
|
|
149
|
+
event: 'peer_action_processed';
|
|
150
|
+
flowId: string;
|
|
151
|
+
timestamp: number;
|
|
152
|
+
payload: {
|
|
153
|
+
sessionId: string;
|
|
154
|
+
messageId: string;
|
|
155
|
+
};
|
|
156
|
+
} | {
|
|
157
|
+
layer: 'session';
|
|
158
|
+
event: 'peer_action_failed';
|
|
159
|
+
flowId: string;
|
|
160
|
+
timestamp: number;
|
|
161
|
+
payload: {
|
|
162
|
+
sessionId: string;
|
|
163
|
+
messageId: string;
|
|
164
|
+
reason: string;
|
|
165
|
+
};
|
|
166
|
+
} | {
|
|
167
|
+
layer: 'session';
|
|
168
|
+
event: 'host_action_sent';
|
|
169
|
+
flowId: string;
|
|
170
|
+
timestamp: number;
|
|
171
|
+
payload: {
|
|
172
|
+
sessionId: string;
|
|
173
|
+
messageId: string;
|
|
174
|
+
actionKind: string;
|
|
175
|
+
};
|
|
176
|
+
} | {
|
|
177
|
+
layer: 'session';
|
|
178
|
+
event: 'host_action_response_received';
|
|
179
|
+
flowId: string;
|
|
180
|
+
timestamp: number;
|
|
181
|
+
payload: {
|
|
182
|
+
sessionId: string;
|
|
183
|
+
messageId: string;
|
|
184
|
+
};
|
|
185
|
+
} | {
|
|
186
|
+
layer: 'session';
|
|
187
|
+
event: 'host_action_failed';
|
|
188
|
+
flowId: string;
|
|
189
|
+
timestamp: number;
|
|
190
|
+
payload: {
|
|
191
|
+
sessionId: string;
|
|
192
|
+
messageId: string;
|
|
193
|
+
reason: string;
|
|
194
|
+
};
|
|
195
|
+
} | {
|
|
196
|
+
layer: 'session';
|
|
197
|
+
event: 'terminated';
|
|
198
|
+
flowId: string;
|
|
199
|
+
timestamp: number;
|
|
200
|
+
payload: {
|
|
201
|
+
sessionId: string;
|
|
202
|
+
};
|
|
203
|
+
};
|
|
204
|
+
export type HostPappDebugEvent = SsoDebugEvent | AttestationDebugEvent | SessionDebugEvent;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/dist/sso/auth/impl.js
CHANGED
|
@@ -4,6 +4,7 @@ import { generateMnemonic } from '@polkadot-labs/hdkd-helpers';
|
|
|
4
4
|
import { Result, ResultAsync, err, fromPromise, fromThrowable, ok } from 'neverthrow';
|
|
5
5
|
import { mergeUint8, toHex } from 'polkadot-api/utils';
|
|
6
6
|
import { createEncrSecret, createSharedSecret, deriveSr25519Account, getEncrPub, stringToBytes } from '../../crypto.js';
|
|
7
|
+
import { createFlowId, emitHostPappDebugMessage } from '../../debugBus.js';
|
|
7
8
|
import { AbortError } from '../../helpers/abortError.js';
|
|
8
9
|
import { createState, readonly } from '../../helpers/state.js';
|
|
9
10
|
import { toError } from '../../helpers/utils.js';
|
|
@@ -13,7 +14,7 @@ export function createAuth({ metadata, hostMetadata, statementStore, ssoSessionR
|
|
|
13
14
|
const pairingStatus = createState({ step: 'none' });
|
|
14
15
|
let authResult = null;
|
|
15
16
|
let abort = null;
|
|
16
|
-
function handshake(account, signal) {
|
|
17
|
+
function handshake(account, signal, flowId) {
|
|
17
18
|
const localAccount = createLocalSessionAccount(createAccountId(account.publicKey));
|
|
18
19
|
pairingStatus.write({ step: 'initial' });
|
|
19
20
|
const encrKeys = createEncrKeys(account.entropy);
|
|
@@ -24,9 +25,26 @@ export function createAuth({ metadata, hostMetadata, statementStore, ssoSessionR
|
|
|
24
25
|
hostMetadata,
|
|
25
26
|
}));
|
|
26
27
|
const handshakeTopic = encrKeys.andThen(({ publicKey }) => createHandshakeTopic(localAccount, publicKey));
|
|
27
|
-
const dataPrepared = Result.combine([handshakePayload, handshakeTopic, encrKeys]).andTee(([payload]) =>
|
|
28
|
+
const dataPrepared = Result.combine([handshakePayload, handshakeTopic, encrKeys]).andTee(([payload]) => {
|
|
29
|
+
const deeplink = createDeeplink(payload);
|
|
30
|
+
pairingStatus.write({ step: 'pairing', payload: deeplink });
|
|
31
|
+
emitHostPappDebugMessage({
|
|
32
|
+
layer: 'sso',
|
|
33
|
+
event: 'deeplink_generated',
|
|
34
|
+
flowId,
|
|
35
|
+
timestamp: Date.now(),
|
|
36
|
+
payload: { deeplink },
|
|
37
|
+
});
|
|
38
|
+
});
|
|
28
39
|
return dataPrepared
|
|
29
40
|
.asyncAndThen(([, handshakeTopic, encrKeys]) => {
|
|
41
|
+
emitHostPappDebugMessage({
|
|
42
|
+
layer: 'sso',
|
|
43
|
+
event: 'awaiting_response',
|
|
44
|
+
flowId,
|
|
45
|
+
timestamp: Date.now(),
|
|
46
|
+
payload: { topic: toHex(handshakeTopic) },
|
|
47
|
+
});
|
|
30
48
|
const pappResponse = waitForStatements(callback => statementStore.subscribeStatements({ matchAll: [handshakeTopic] }, page => callback(page.statements)), signal, (statements, resolve) => {
|
|
31
49
|
for (const statement of statements) {
|
|
32
50
|
if (!statement.data)
|
|
@@ -37,6 +55,13 @@ export function createAuth({ metadata, hostMetadata, statementStore, ssoSessionR
|
|
|
37
55
|
payload: statement.data,
|
|
38
56
|
}).unwrapOr(null);
|
|
39
57
|
if (session) {
|
|
58
|
+
emitHostPappDebugMessage({
|
|
59
|
+
layer: 'sso',
|
|
60
|
+
event: 'response_received',
|
|
61
|
+
flowId,
|
|
62
|
+
timestamp: Date.now(),
|
|
63
|
+
payload: { sessionId: session.id },
|
|
64
|
+
});
|
|
40
65
|
resolve(session);
|
|
41
66
|
break;
|
|
42
67
|
}
|
|
@@ -69,7 +94,15 @@ export function createAuth({ metadata, hostMetadata, statementStore, ssoSessionR
|
|
|
69
94
|
}
|
|
70
95
|
abort = new AbortController();
|
|
71
96
|
const account = deriveSr25519Account(generateMnemonic(), '//wallet//sso');
|
|
72
|
-
|
|
97
|
+
const ssoFlowId = createFlowId();
|
|
98
|
+
emitHostPappDebugMessage({
|
|
99
|
+
layer: 'sso',
|
|
100
|
+
event: 'pairing_started',
|
|
101
|
+
flowId: ssoFlowId,
|
|
102
|
+
timestamp: Date.now(),
|
|
103
|
+
payload: { metadata },
|
|
104
|
+
});
|
|
105
|
+
authResult = handshake(account, abort.signal, ssoFlowId)
|
|
73
106
|
.andThen(({ session, secretsPayload }) => {
|
|
74
107
|
return userSecretRepository
|
|
75
108
|
.write(secretsPayload.id, {
|
|
@@ -79,14 +112,32 @@ export function createAuth({ metadata, hostMetadata, statementStore, ssoSessionR
|
|
|
79
112
|
})
|
|
80
113
|
.andThen(() => ssoSessionRepository.add(session))
|
|
81
114
|
.map(() => session);
|
|
115
|
+
})
|
|
116
|
+
.andTee(session => {
|
|
117
|
+
if (session) {
|
|
118
|
+
emitHostPappDebugMessage({
|
|
119
|
+
layer: 'sso',
|
|
120
|
+
event: 'session_established',
|
|
121
|
+
flowId: ssoFlowId,
|
|
122
|
+
timestamp: Date.now(),
|
|
123
|
+
payload: { sessionId: session.id },
|
|
124
|
+
});
|
|
125
|
+
}
|
|
82
126
|
})
|
|
83
127
|
.orElse(e => (e instanceof AbortError ? ok(null) : err(e)))
|
|
84
128
|
.andTee(() => {
|
|
85
129
|
abort = null;
|
|
86
130
|
})
|
|
87
|
-
.orTee(
|
|
131
|
+
.orTee(e => {
|
|
88
132
|
authResult = null;
|
|
89
133
|
abort = null;
|
|
134
|
+
emitHostPappDebugMessage({
|
|
135
|
+
layer: 'sso',
|
|
136
|
+
event: 'pairing_failed',
|
|
137
|
+
flowId: ssoFlowId,
|
|
138
|
+
timestamp: Date.now(),
|
|
139
|
+
payload: { reason: e.message },
|
|
140
|
+
});
|
|
90
141
|
});
|
|
91
142
|
return authResult;
|
|
92
143
|
},
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { createEncryption } from '@novasamatech/statement-store';
|
|
2
2
|
import { okAsync } from 'neverthrow';
|
|
3
|
+
import { emitHostPappDebugMessage } from '../../debugBus.js';
|
|
3
4
|
import { createState } from '../../helpers/state.js';
|
|
4
5
|
import { createSsoStatementProver } from '../ssoSessionProver.js';
|
|
5
6
|
import { createUserSession } from './userSession.js';
|
|
@@ -23,6 +24,13 @@ export function createSsoSessionManager({ ssoSessionRepository, userSecretReposi
|
|
|
23
24
|
continue;
|
|
24
25
|
const session = createSession(userSession, statementStore, storage, userSecretRepository);
|
|
25
26
|
toAdd.add(session);
|
|
27
|
+
emitHostPappDebugMessage({
|
|
28
|
+
layer: 'session',
|
|
29
|
+
event: 'opened',
|
|
30
|
+
flowId: userSession.id,
|
|
31
|
+
timestamp: Date.now(),
|
|
32
|
+
payload: { sessionId: userSession.id },
|
|
33
|
+
});
|
|
26
34
|
const unsubscribe = session.subscribe(message => {
|
|
27
35
|
switch (message.data.tag) {
|
|
28
36
|
case 'v1': {
|
|
@@ -38,6 +46,13 @@ export function createSsoSessionManager({ ssoSessionRepository, userSecretReposi
|
|
|
38
46
|
}
|
|
39
47
|
if (toRemove.size > 0) {
|
|
40
48
|
for (const id of toRemove) {
|
|
49
|
+
emitHostPappDebugMessage({
|
|
50
|
+
layer: 'session',
|
|
51
|
+
event: 'terminated',
|
|
52
|
+
flowId: id,
|
|
53
|
+
timestamp: Date.now(),
|
|
54
|
+
payload: { sessionId: id },
|
|
55
|
+
});
|
|
41
56
|
releaseSession(id);
|
|
42
57
|
activeSessions[id]?.dispose();
|
|
43
58
|
}
|
|
@@ -4,6 +4,7 @@ import { createSession } from '@novasamatech/statement-store';
|
|
|
4
4
|
import { fieldListView } from '@novasamatech/storage-adapter';
|
|
5
5
|
import { nanoid } from 'nanoid';
|
|
6
6
|
import { ResultAsync, err, ok, okAsync } from 'neverthrow';
|
|
7
|
+
import { emitHostPappDebugMessage } from '../../debugBus.js';
|
|
7
8
|
import { createAsyncTaskPool } from '../../helpers/createAsyncTaskPool.js';
|
|
8
9
|
import { toError } from '../../helpers/utils.js';
|
|
9
10
|
import { RemoteMessageCodec } from './scale/remoteMessage.js';
|
|
@@ -16,6 +17,51 @@ function withQueueTimeout(resultAsync, label) {
|
|
|
16
17
|
const timeoutPromise = new Promise(resolve => setTimeout(() => resolve(err(new Error(`${label} timed out — queue freed`))), QUEUE_TASK_TIMEOUT_MS));
|
|
17
18
|
return ResultAsync.fromPromise(Promise.race([resultAsync, timeoutPromise]), toError).andThen(r => r);
|
|
18
19
|
}
|
|
20
|
+
/**
|
|
21
|
+
* Derive a stable `actionKind` label from a remote-message envelope.
|
|
22
|
+
* Shape: `OuterTag` for flat variants, `OuterTag:InnerTag` for variants
|
|
23
|
+
* whose payload is itself an enum (currently just `SignRequest`).
|
|
24
|
+
* The receive side and the send side both go through here so debug
|
|
25
|
+
* consumers see the same shape regardless of direction.
|
|
26
|
+
*/
|
|
27
|
+
function actionKindFromMessageData(data) {
|
|
28
|
+
if (data.tag !== 'v1')
|
|
29
|
+
return data.tag;
|
|
30
|
+
const inner = data.value;
|
|
31
|
+
if (inner.tag === 'SignRequest')
|
|
32
|
+
return `SignRequest:${inner.value.tag}`;
|
|
33
|
+
return inner.tag;
|
|
34
|
+
}
|
|
35
|
+
function emitHostAction(messageId, actionKind, sessionId) {
|
|
36
|
+
emitHostPappDebugMessage({
|
|
37
|
+
layer: 'session',
|
|
38
|
+
event: 'host_action_sent',
|
|
39
|
+
flowId: messageId,
|
|
40
|
+
timestamp: Date.now(),
|
|
41
|
+
payload: { sessionId, messageId, actionKind },
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
function withHostActionTrace(result, messageId, sessionId) {
|
|
45
|
+
return result
|
|
46
|
+
.andTee(() => {
|
|
47
|
+
emitHostPappDebugMessage({
|
|
48
|
+
layer: 'session',
|
|
49
|
+
event: 'host_action_response_received',
|
|
50
|
+
flowId: messageId,
|
|
51
|
+
timestamp: Date.now(),
|
|
52
|
+
payload: { sessionId, messageId },
|
|
53
|
+
});
|
|
54
|
+
})
|
|
55
|
+
.orTee(error => {
|
|
56
|
+
emitHostPappDebugMessage({
|
|
57
|
+
layer: 'session',
|
|
58
|
+
event: 'host_action_failed',
|
|
59
|
+
flowId: messageId,
|
|
60
|
+
timestamp: Date.now(),
|
|
61
|
+
payload: { sessionId, messageId, reason: error.message },
|
|
62
|
+
});
|
|
63
|
+
});
|
|
64
|
+
}
|
|
19
65
|
export function createUserSession({ userSession, statementStore, encryption, storage, prover, }) {
|
|
20
66
|
const requestQueue = createAsyncTaskPool({ poolSize: 1, retryCount: 0, retryDelay: 0 });
|
|
21
67
|
const session = createSession({
|
|
@@ -39,10 +85,9 @@ export function createUserSession({ userSession, statementStore, encryption, sto
|
|
|
39
85
|
signPayload(payload) {
|
|
40
86
|
return requestQueue.call(() => {
|
|
41
87
|
const messageId = nanoid();
|
|
42
|
-
const
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
});
|
|
88
|
+
const data = enumValue('v1', enumValue('SignRequest', enumValue('Payload', payload)));
|
|
89
|
+
emitHostAction(messageId, actionKindFromMessageData(data), userSession.id);
|
|
90
|
+
const request = session.request(RemoteMessageCodec, { messageId, data });
|
|
46
91
|
const responseFilter = (message) => {
|
|
47
92
|
if (message.data.tag === 'v1' &&
|
|
48
93
|
message.data.value.tag === 'SignResponse' &&
|
|
@@ -60,16 +105,15 @@ export function createUserSession({ userSession, statementStore, encryption, sto
|
|
|
60
105
|
return err(new Error(message.value));
|
|
61
106
|
}
|
|
62
107
|
});
|
|
63
|
-
return withQueueTimeout(inner, 'signPayload');
|
|
108
|
+
return withHostActionTrace(withQueueTimeout(inner, 'signPayload'), messageId, userSession.id);
|
|
64
109
|
});
|
|
65
110
|
},
|
|
66
111
|
signRaw(payload) {
|
|
67
112
|
return requestQueue.call(() => {
|
|
68
113
|
const messageId = nanoid();
|
|
69
|
-
const
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
});
|
|
114
|
+
const data = enumValue('v1', enumValue('SignRequest', enumValue('Raw', payload)));
|
|
115
|
+
emitHostAction(messageId, actionKindFromMessageData(data), userSession.id);
|
|
116
|
+
const request = session.request(RemoteMessageCodec, { messageId, data });
|
|
73
117
|
const responseFilter = (message) => {
|
|
74
118
|
if (message.data.tag === 'v1' &&
|
|
75
119
|
message.data.value.tag === 'SignResponse' &&
|
|
@@ -87,7 +131,7 @@ export function createUserSession({ userSession, statementStore, encryption, sto
|
|
|
87
131
|
return err(new Error(message.value));
|
|
88
132
|
}
|
|
89
133
|
});
|
|
90
|
-
return withQueueTimeout(inner, 'signRaw');
|
|
134
|
+
return withHostActionTrace(withQueueTimeout(inner, 'signRaw'), messageId, userSession.id);
|
|
91
135
|
});
|
|
92
136
|
},
|
|
93
137
|
createTransaction(payload) {
|
|
@@ -128,13 +172,12 @@ export function createUserSession({ userSession, statementStore, encryption, sto
|
|
|
128
172
|
getRingVrfAlias(productAccountId, productId) {
|
|
129
173
|
return requestQueue.call(() => {
|
|
130
174
|
const messageId = nanoid();
|
|
131
|
-
const
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
});
|
|
175
|
+
const data = enumValue('v1', enumValue('RingVrfAliasRequest', {
|
|
176
|
+
productAccountId,
|
|
177
|
+
productId,
|
|
178
|
+
}));
|
|
179
|
+
emitHostAction(messageId, actionKindFromMessageData(data), userSession.id);
|
|
180
|
+
const request = session.request(RemoteMessageCodec, { messageId, data });
|
|
138
181
|
const responseFilter = (message) => {
|
|
139
182
|
if (message.data.tag === 'v1' &&
|
|
140
183
|
message.data.value.tag === 'RingVrfAliasResponse' &&
|
|
@@ -142,9 +185,9 @@ export function createUserSession({ userSession, statementStore, encryption, sto
|
|
|
142
185
|
return message.data.value.value.payload;
|
|
143
186
|
}
|
|
144
187
|
};
|
|
145
|
-
return request
|
|
188
|
+
return withHostActionTrace(request
|
|
146
189
|
.andThen(() => session.waitForRequestMessage(RemoteMessageCodec, responseFilter))
|
|
147
|
-
.andThen(result => (result.success ? ok(result.value) : err(new Error(result.value))));
|
|
190
|
+
.andThen(result => (result.success ? ok(result.value) : err(new Error(result.value)))), messageId, userSession.id);
|
|
148
191
|
});
|
|
149
192
|
},
|
|
150
193
|
requestResourceAllocation(request) {
|
|
@@ -179,9 +222,36 @@ export function createUserSession({ userSession, statementStore, encryption, sto
|
|
|
179
222
|
if (isMessageProcessed) {
|
|
180
223
|
return okAsync({ processed: false });
|
|
181
224
|
}
|
|
225
|
+
const messageId = payload.value.messageId;
|
|
226
|
+
const actionKind = actionKindFromMessageData(payload.value.data);
|
|
227
|
+
emitHostPappDebugMessage({
|
|
228
|
+
layer: 'session',
|
|
229
|
+
event: 'peer_action_received',
|
|
230
|
+
flowId: messageId,
|
|
231
|
+
timestamp: Date.now(),
|
|
232
|
+
payload: { sessionId: userSession.id, messageId, actionKind },
|
|
233
|
+
});
|
|
182
234
|
return callback(payload.value)
|
|
235
|
+
.andTee(processed => {
|
|
236
|
+
if (processed) {
|
|
237
|
+
emitHostPappDebugMessage({
|
|
238
|
+
layer: 'session',
|
|
239
|
+
event: 'peer_action_processed',
|
|
240
|
+
flowId: messageId,
|
|
241
|
+
timestamp: Date.now(),
|
|
242
|
+
payload: { sessionId: userSession.id, messageId },
|
|
243
|
+
});
|
|
244
|
+
}
|
|
245
|
+
})
|
|
183
246
|
.orTee(error => {
|
|
184
247
|
console.error('Error while processing sso message:', error);
|
|
248
|
+
emitHostPappDebugMessage({
|
|
249
|
+
layer: 'session',
|
|
250
|
+
event: 'peer_action_failed',
|
|
251
|
+
flowId: messageId,
|
|
252
|
+
timestamp: Date.now(),
|
|
253
|
+
payload: { sessionId: userSession.id, messageId, reason: error.message },
|
|
254
|
+
});
|
|
185
255
|
})
|
|
186
256
|
.orElse(() => okAsync(false))
|
|
187
257
|
.map(processed => (processed ? { processed, message: payload.value } : { processed }));
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@novasamatech/host-papp",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "0.7.9-
|
|
4
|
+
"version": "0.7.9-6",
|
|
5
5
|
"description": "Polkadot app integration",
|
|
6
6
|
"license": "Apache-2.0",
|
|
7
7
|
"repository": {
|
|
@@ -18,6 +18,11 @@
|
|
|
18
18
|
"#/source": "./src/index.ts",
|
|
19
19
|
"types": "./dist/index.d.ts",
|
|
20
20
|
"default": "./dist/index.js"
|
|
21
|
+
},
|
|
22
|
+
"./debug": {
|
|
23
|
+
"#/source": "./src/debug-public.ts",
|
|
24
|
+
"types": "./dist/debug-public.d.ts",
|
|
25
|
+
"default": "./dist/debug-public.js"
|
|
21
26
|
}
|
|
22
27
|
},
|
|
23
28
|
"files": [
|
|
@@ -29,10 +34,10 @@
|
|
|
29
34
|
"@noble/ciphers": "2.2.0",
|
|
30
35
|
"@noble/curves": "2.2.0",
|
|
31
36
|
"@noble/hashes": "2.2.0",
|
|
32
|
-
"@novasamatech/host-api": "0.7.9-
|
|
33
|
-
"@novasamatech/scale": "0.7.9-
|
|
34
|
-
"@novasamatech/statement-store": "0.7.9-
|
|
35
|
-
"@novasamatech/storage-adapter": "0.7.9-
|
|
37
|
+
"@novasamatech/host-api": "0.7.9-6",
|
|
38
|
+
"@novasamatech/scale": "0.7.9-6",
|
|
39
|
+
"@novasamatech/statement-store": "0.7.9-6",
|
|
40
|
+
"@novasamatech/storage-adapter": "0.7.9-6",
|
|
36
41
|
"@polkadot-labs/hdkd-helpers": "^0.0.30",
|
|
37
42
|
"nanoevents": "9.1.0",
|
|
38
43
|
"nanoid": "5.1.9",
|