@novasamatech/host-papp 0.5.0-6 → 0.5.0-7
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/adapters/identity/rpc.d.ts +4 -2
- package/dist/adapters/identity/rpc.js +96 -26
- package/dist/adapters/identity/types.d.ts +3 -1
- package/dist/adapters/lazyClient/papi.js +5 -0
- package/dist/adapters/lazyClient/types.d.ts +1 -0
- package/dist/adapters/statement/rpc.js +58 -10
- package/dist/adapters/statement/types.d.ts +6 -3
- package/dist/adapters/storage/localStorage.js +26 -4
- package/dist/adapters/storage/memory.js +14 -4
- package/dist/adapters/storage/types.d.ts +5 -2
- package/dist/adapters/storage/types.js +1 -1
- package/dist/adapters/transport/rpc.d.ts +3 -0
- package/dist/adapters/transport/rpc.js +51 -0
- package/dist/adapters/transport/types.d.ts +6 -0
- package/dist/adapters/transport/types.js +1 -0
- package/dist/components/auth/codec.d.ts +9 -0
- package/dist/components/auth/codec.js +10 -0
- package/dist/components/auth/codecs.d.ts +9 -0
- package/dist/components/auth/codecs.js +10 -0
- package/dist/components/auth/index.d.ts +36 -0
- package/dist/components/auth/index.js +150 -0
- package/dist/components/auth/types.d.ts +15 -0
- package/dist/components/auth/types.js +1 -0
- package/dist/components/session.d.ts +34 -0
- package/dist/components/session.js +54 -0
- package/dist/components/transport.d.ts +27 -0
- package/dist/components/transport.js +57 -0
- package/dist/components/user/codec.d.ts +16 -0
- package/dist/components/user/codec.js +13 -0
- package/dist/components/user/index.d.ts +22 -0
- package/dist/components/user/index.js +58 -0
- package/dist/components/user/ssoMessageStream.d.ts +10 -0
- package/dist/components/user/ssoMessageStream.js +8 -0
- package/dist/components/user/ssoSession.d.ts +5 -0
- package/dist/components/user/ssoSession.js +5 -0
- package/dist/components/user/storage.d.ts +27 -0
- package/dist/components/user/storage.js +143 -0
- package/dist/components/user/types.d.ts +6 -0
- package/dist/components/user/types.js +1 -0
- package/dist/components/user/userSessionStorage.d.ts +20 -0
- package/dist/components/user/userSessionStorage.js +24 -0
- package/dist/components/user.d.ts +74 -0
- package/dist/components/user.js +188 -0
- package/dist/constants.d.ts +2 -1
- package/dist/constants.js +5 -1
- package/dist/helpers/abortError.d.ts +4 -0
- package/dist/helpers/abortError.js +8 -0
- package/dist/helpers/callbackRaceResolver.d.ts +1 -0
- package/dist/helpers/callbackRaceResolver.js +17 -0
- package/dist/helpers/result.d.ts +12 -0
- package/dist/helpers/result.js +15 -0
- package/dist/helpers/result.spec.d.ts +1 -0
- package/dist/helpers/result.spec.js +23 -0
- package/dist/helpers/utils.d.ts +2 -1
- package/dist/helpers/utils.js +11 -2
- package/dist/helpers.d.ts +1 -0
- package/dist/helpers.js +3 -0
- package/dist/index.d.ts +2 -1
- package/dist/modules/accounts.d.ts +1 -0
- package/dist/modules/accounts.js +2 -0
- package/dist/modules/crypto.d.ts +5 -2
- package/dist/modules/crypto.js +16 -5
- package/dist/modules/secretStorage.d.ts +7 -9
- package/dist/modules/secretStorage.js +20 -33
- package/dist/modules/session/helpers.d.ts +5 -0
- package/dist/modules/session/helpers.js +29 -0
- package/dist/modules/session/session.d.ts +12 -0
- package/dist/modules/session/session.js +46 -0
- package/dist/modules/session/types.d.ts +12 -0
- package/dist/modules/session/types.js +1 -0
- package/dist/modules/signIn.d.ts +32 -11
- package/dist/modules/signIn.js +95 -95
- package/dist/modules/state.d.ts +16 -0
- package/dist/modules/state.js +50 -0
- package/dist/modules/statementStore.d.ts +10 -11
- package/dist/modules/statementStore.js +14 -14
- package/dist/modules/statementTopic.d.ts +34 -0
- package/dist/modules/statementTopic.js +46 -0
- package/dist/modules/storageView.d.ts +25 -0
- package/dist/modules/storageView.js +51 -0
- package/dist/modules/syncStorage.d.ts +25 -0
- package/dist/modules/syncStorage.js +76 -0
- package/dist/modules/transport/codec.d.ts +24 -0
- package/dist/modules/transport/codec.js +36 -0
- package/dist/modules/transport/crypto.d.ts +2 -0
- package/dist/modules/transport/crypto.js +18 -0
- package/dist/modules/transport/transport.d.ts +27 -0
- package/dist/modules/transport/transport.js +56 -0
- package/dist/modules/user.d.ts +67 -0
- package/dist/modules/user.js +188 -0
- package/dist/modules/userManager.d.ts +15 -0
- package/dist/modules/userManager.js +105 -0
- package/dist/modules/userStorage.d.ts +19 -0
- package/dist/modules/userStorage.js +108 -0
- package/dist/modules/userStore.d.ts +15 -0
- package/dist/modules/userStore.js +105 -0
- package/dist/papp.d.ts +8 -7
- package/dist/papp.js +19 -31
- package/dist/structs.d.ts +10 -10
- package/dist/structs.js +17 -13
- package/dist/types.d.ts +1 -1
- package/package.json +8 -6
|
@@ -1,4 +1,6 @@
|
|
|
1
|
+
import { ResultAsync } from 'neverthrow';
|
|
1
2
|
import type { LazyClientAdapter } from '../lazyClient/types.js';
|
|
2
3
|
import type { StorageAdapter } from '../storage/types.js';
|
|
3
|
-
import type { IdentityAdapter } from './types.js';
|
|
4
|
-
export declare
|
|
4
|
+
import type { Identity, IdentityAdapter } from './types.js';
|
|
5
|
+
export declare function createCachedIdentityRequester(storage: StorageAdapter, getKey: (accountId: string) => string, request: (accounts: string[]) => ResultAsync<(Identity | null)[], Error>): (accounts: string[]) => ResultAsync<Record<string, Identity | null>, Error>;
|
|
6
|
+
export declare function createIdentityRpcAdapter(lazyClient: LazyClientAdapter, storage: StorageAdapter): IdentityAdapter;
|
|
@@ -1,31 +1,101 @@
|
|
|
1
|
+
import { Result, ResultAsync, err, errAsync, fromPromise, ok, okAsync } from 'neverthrow';
|
|
1
2
|
import { AccountId } from 'polkadot-api';
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
3
|
+
import { toError } from '../../helpers/utils.js';
|
|
4
|
+
function zipWith(arrays, iteratee) {
|
|
5
|
+
if (arrays.length === 0)
|
|
6
|
+
return [];
|
|
7
|
+
const minLength = Math.min(...arrays.map(arr => arr.length));
|
|
8
|
+
const result = [];
|
|
9
|
+
for (let i = 0; i < minLength; i++) {
|
|
10
|
+
const values = arrays.map(arr => arr[i]);
|
|
11
|
+
result.push(iteratee(values));
|
|
12
|
+
}
|
|
13
|
+
return result;
|
|
14
|
+
}
|
|
15
|
+
export function createCachedIdentityRequester(storage, getKey, request) {
|
|
16
|
+
function readSingleCacheRecord(accountId) {
|
|
17
|
+
return storage.read(getKey(accountId)).andThen(raw => {
|
|
18
|
+
if (!raw) {
|
|
19
|
+
return ok(null);
|
|
20
|
+
}
|
|
21
|
+
try {
|
|
22
|
+
return ok(JSON.parse(raw));
|
|
23
|
+
}
|
|
24
|
+
catch (e) {
|
|
25
|
+
return err(toError(e));
|
|
26
|
+
}
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
function writeSingleCacheRecord(accountId, identity) {
|
|
30
|
+
if (identity === null) {
|
|
31
|
+
return okAsync(undefined);
|
|
32
|
+
}
|
|
33
|
+
return storage.write(getKey(accountId), JSON.stringify(identity));
|
|
34
|
+
}
|
|
35
|
+
function readCache(accounts) {
|
|
36
|
+
if (accounts.length === 0) {
|
|
37
|
+
return okAsync({});
|
|
38
|
+
}
|
|
39
|
+
const identities = ResultAsync.combine(accounts.map(readSingleCacheRecord));
|
|
40
|
+
return identities.map(identities => {
|
|
41
|
+
return Object.fromEntries(identities.map((x, i) => {
|
|
42
|
+
const accountId = accounts.at(i);
|
|
43
|
+
if (!accountId) {
|
|
44
|
+
throw new Error(`Identity not found`);
|
|
14
45
|
}
|
|
46
|
+
return [accountId, x];
|
|
47
|
+
}));
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
function writeCache(identities) {
|
|
51
|
+
return ResultAsync.combine(Object.entries(identities).map(args => writeSingleCacheRecord(...args))).map(() => identities);
|
|
52
|
+
}
|
|
53
|
+
return (accounts) => {
|
|
54
|
+
return readCache(accounts).andThen(existing => {
|
|
55
|
+
const emptyIdentities = Object.entries(existing)
|
|
56
|
+
.filter(([, identity]) => identity === null)
|
|
57
|
+
.map(([accountId]) => accountId);
|
|
58
|
+
if (emptyIdentities.length === 0) {
|
|
59
|
+
return okAsync(existing);
|
|
15
60
|
}
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
61
|
+
return request(accounts)
|
|
62
|
+
.map(response => Object.fromEntries(zipWith([accounts, response], x => x)))
|
|
63
|
+
.andThen(writeCache)
|
|
64
|
+
.map(fetched => ({
|
|
65
|
+
...existing,
|
|
66
|
+
...fetched,
|
|
67
|
+
}));
|
|
68
|
+
});
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
export function createIdentityRpcAdapter(lazyClient, storage) {
|
|
72
|
+
const requester = createCachedIdentityRequester(storage, (accountId) => `identity_${accountId}`, accounts => {
|
|
73
|
+
const client = lazyClient.getClient();
|
|
74
|
+
const unsafeApi = client.getUnsafeApi();
|
|
75
|
+
const method = unsafeApi.query.Resources?.Consumers;
|
|
76
|
+
if (!method) {
|
|
77
|
+
return errAsync(new Error('Method Resources:Consumers not found'));
|
|
78
|
+
}
|
|
79
|
+
const results = fromPromise(method.getValues([accounts.map(accCodec.dec)]), toError);
|
|
80
|
+
return results.andThen(results => {
|
|
81
|
+
if (!results) {
|
|
82
|
+
return ok(accounts.map(() => null));
|
|
27
83
|
}
|
|
28
|
-
return
|
|
29
|
-
|
|
84
|
+
return ok(zipWith([accounts, results], x => x).map(([accountId, raw]) => {
|
|
85
|
+
if (!raw) {
|
|
86
|
+
return null;
|
|
87
|
+
}
|
|
88
|
+
return {
|
|
89
|
+
accountId: accountId,
|
|
90
|
+
fullUsername: raw.full_username ? raw.full_username.asText() : null,
|
|
91
|
+
liteUsername: raw.lite_username ? raw.lite_username.asText() : null,
|
|
92
|
+
credibility: raw.credibility.type,
|
|
93
|
+
};
|
|
94
|
+
}));
|
|
95
|
+
});
|
|
96
|
+
});
|
|
97
|
+
const accCodec = AccountId();
|
|
98
|
+
return {
|
|
99
|
+
readIdentities: requester,
|
|
30
100
|
};
|
|
31
|
-
}
|
|
101
|
+
}
|
|
@@ -1,8 +1,10 @@
|
|
|
1
|
+
import type { ResultAsync } from 'neverthrow';
|
|
1
2
|
export type Identity = {
|
|
3
|
+
accountId: string;
|
|
2
4
|
fullUsername: string | null;
|
|
3
5
|
liteUsername: string;
|
|
4
6
|
credibility: string;
|
|
5
7
|
};
|
|
6
8
|
export type IdentityAdapter = {
|
|
7
|
-
|
|
9
|
+
readIdentities(accounts: string[]): ResultAsync<Record<string, Identity | null>, Error>;
|
|
8
10
|
};
|
|
@@ -1,21 +1,68 @@
|
|
|
1
1
|
import { createStatementSdk } from '@polkadot-api/sdk-statement';
|
|
2
|
-
import {
|
|
2
|
+
import { Binary } from '@polkadot-api/substrate-bindings';
|
|
3
|
+
import { toHex } from '@polkadot-api/utils';
|
|
4
|
+
import { fromPromise } from 'neverthrow';
|
|
5
|
+
import { toError } from '../../helpers/utils.js';
|
|
6
|
+
const POLLING_INTERVAL = 1000;
|
|
7
|
+
function createKey(topics) {
|
|
8
|
+
return topics.map(toHex).sort().join('');
|
|
9
|
+
}
|
|
3
10
|
export function createPapiStatementAdapter(lazyClient) {
|
|
4
|
-
const POLLING_INTERVAL = 1000;
|
|
5
11
|
const sdk = createStatementSdk((method, params) => {
|
|
6
12
|
const client = lazyClient.getClient();
|
|
7
13
|
return client._request(method, params);
|
|
8
14
|
});
|
|
15
|
+
const pollings = new Map();
|
|
16
|
+
const subscriptions = new Map();
|
|
17
|
+
function addSubscription(key, subscription) {
|
|
18
|
+
let subs = subscriptions.get(key);
|
|
19
|
+
if (!subs) {
|
|
20
|
+
subs = [];
|
|
21
|
+
subscriptions.set(key, subs);
|
|
22
|
+
}
|
|
23
|
+
subs.push(subscription);
|
|
24
|
+
return subs;
|
|
25
|
+
}
|
|
26
|
+
function removeSubscription(key, subscription) {
|
|
27
|
+
let subs = subscriptions.get(key);
|
|
28
|
+
if (!subs) {
|
|
29
|
+
return [];
|
|
30
|
+
}
|
|
31
|
+
subs = subs.filter(x => x !== subscription);
|
|
32
|
+
return subs;
|
|
33
|
+
}
|
|
9
34
|
const transportProvider = {
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
35
|
+
queryStatements(topics, destination) {
|
|
36
|
+
return fromPromise(sdk.getStatements({
|
|
37
|
+
topics: topics.map(t => Binary.fromBytes(t)),
|
|
38
|
+
dest: destination ? Binary.fromBytes(destination) : null,
|
|
39
|
+
}), toError);
|
|
13
40
|
},
|
|
14
41
|
subscribeStatements(topics, callback) {
|
|
15
|
-
|
|
42
|
+
const key = createKey(topics);
|
|
43
|
+
const callbacks = addSubscription(key, callback);
|
|
44
|
+
if (callbacks.length === 1) {
|
|
45
|
+
const unsub = polling(POLLING_INTERVAL, () => transportProvider.queryStatements(topics), statements => {
|
|
46
|
+
const list = subscriptions.get(key);
|
|
47
|
+
if (list) {
|
|
48
|
+
for (const fn of list) {
|
|
49
|
+
fn(statements);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
});
|
|
53
|
+
pollings.set(key, unsub);
|
|
54
|
+
}
|
|
55
|
+
return () => {
|
|
56
|
+
const callbacks = removeSubscription(key, callback);
|
|
57
|
+
if (callbacks.length === 0) {
|
|
58
|
+
const stopPolling = pollings.get(key);
|
|
59
|
+
stopPolling?.();
|
|
60
|
+
pollings.delete(key);
|
|
61
|
+
}
|
|
62
|
+
};
|
|
16
63
|
},
|
|
17
64
|
submitStatement(statement) {
|
|
18
|
-
return sdk.submit(statement);
|
|
65
|
+
return fromPromise(sdk.submit(statement), toError);
|
|
19
66
|
},
|
|
20
67
|
};
|
|
21
68
|
return transportProvider;
|
|
@@ -28,9 +75,10 @@ function polling(interval, request, callback) {
|
|
|
28
75
|
if (!active) {
|
|
29
76
|
return;
|
|
30
77
|
}
|
|
31
|
-
request()
|
|
32
|
-
|
|
33
|
-
|
|
78
|
+
request().match(data => {
|
|
79
|
+
callback(data);
|
|
80
|
+
createCycle();
|
|
81
|
+
}, () => {
|
|
34
82
|
createCycle();
|
|
35
83
|
});
|
|
36
84
|
}, interval);
|
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
import type { SignedStatement, Statement } from '@polkadot-api/sdk-statement';
|
|
2
|
+
import type { ResultAsync } from 'neverthrow';
|
|
3
|
+
import type { Callback } from '../../types.js';
|
|
4
|
+
export type StatementsCallback = Callback<Statement[]>;
|
|
2
5
|
export type StatementAdapter = {
|
|
3
|
-
|
|
4
|
-
subscribeStatements(topics: Uint8Array[], callback:
|
|
5
|
-
submitStatement(statement: SignedStatement):
|
|
6
|
+
queryStatements(topics: Uint8Array[], destination?: Uint8Array): ResultAsync<Statement[], Error>;
|
|
7
|
+
subscribeStatements(topics: Uint8Array[], callback: StatementsCallback): VoidFunction;
|
|
8
|
+
submitStatement(statement: SignedStatement): ResultAsync<void, Error>;
|
|
6
9
|
};
|
|
@@ -1,12 +1,34 @@
|
|
|
1
|
+
import { createNanoEvents } from 'nanoevents';
|
|
2
|
+
import { fromAsyncThrowable } from 'neverthrow';
|
|
3
|
+
import { toError } from '../../helpers/utils.js';
|
|
1
4
|
export function createLocalStorageAdapter(prefix) {
|
|
5
|
+
const events = createNanoEvents();
|
|
2
6
|
const withPrefix = (key) => `PAPP_${prefix}_${key}`;
|
|
3
7
|
return {
|
|
4
|
-
async
|
|
8
|
+
write: fromAsyncThrowable(async (key, value) => {
|
|
5
9
|
localStorage.setItem(withPrefix(key), value);
|
|
6
|
-
|
|
7
|
-
},
|
|
8
|
-
async
|
|
10
|
+
events.emit(key, value);
|
|
11
|
+
}, toError),
|
|
12
|
+
read: fromAsyncThrowable(async (key) => {
|
|
9
13
|
return localStorage.getItem(withPrefix(key));
|
|
14
|
+
}, toError),
|
|
15
|
+
clear: fromAsyncThrowable(async (key) => {
|
|
16
|
+
localStorage.removeItem(withPrefix(key));
|
|
17
|
+
events.emit(key, null);
|
|
18
|
+
}, toError),
|
|
19
|
+
subscribe(key, callback) {
|
|
20
|
+
const prefixedKey = withPrefix(key);
|
|
21
|
+
const unsubscribeLocalListener = events.on(prefixedKey, callback);
|
|
22
|
+
const externalListener = (event) => {
|
|
23
|
+
if (event.storageArea === localStorage && event.key === prefixedKey) {
|
|
24
|
+
callback(event.newValue);
|
|
25
|
+
}
|
|
26
|
+
};
|
|
27
|
+
window.addEventListener('storage', externalListener);
|
|
28
|
+
return () => {
|
|
29
|
+
unsubscribeLocalListener();
|
|
30
|
+
window.removeEventListener('storage', externalListener);
|
|
31
|
+
};
|
|
10
32
|
},
|
|
11
33
|
};
|
|
12
34
|
}
|
|
@@ -1,12 +1,22 @@
|
|
|
1
|
+
import { createNanoEvents } from 'nanoevents';
|
|
2
|
+
import { fromAsyncThrowable } from 'neverthrow';
|
|
1
3
|
export function createMemoryAdapter(external) {
|
|
4
|
+
const events = createNanoEvents();
|
|
2
5
|
const storage = external ? { ...external } : {};
|
|
3
6
|
return {
|
|
4
|
-
async
|
|
7
|
+
write: fromAsyncThrowable(async (key, value) => {
|
|
5
8
|
storage[key] = value;
|
|
6
|
-
|
|
7
|
-
},
|
|
8
|
-
async
|
|
9
|
+
events.emit(key, value);
|
|
10
|
+
}),
|
|
11
|
+
read: fromAsyncThrowable(async (key) => {
|
|
9
12
|
return storage[key] ?? null;
|
|
13
|
+
}),
|
|
14
|
+
clear: fromAsyncThrowable(async (key) => {
|
|
15
|
+
delete storage[key];
|
|
16
|
+
events.emit(key, null);
|
|
17
|
+
}),
|
|
18
|
+
subscribe(key, callback) {
|
|
19
|
+
return events.on(key, callback);
|
|
10
20
|
},
|
|
11
21
|
};
|
|
12
22
|
}
|
|
@@ -1,4 +1,7 @@
|
|
|
1
|
+
import { ResultAsync } from 'neverthrow';
|
|
1
2
|
export type StorageAdapter = {
|
|
2
|
-
write(key: string, value: string):
|
|
3
|
-
read(key: string):
|
|
3
|
+
write(key: string, value: string): ResultAsync<void, Error>;
|
|
4
|
+
read(key: string): ResultAsync<string | null, Error>;
|
|
5
|
+
clear(key: string): ResultAsync<void, Error>;
|
|
6
|
+
subscribe(key: string, callback: (value: string | null) => unknown): VoidFunction;
|
|
4
7
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
|
|
1
|
+
import { ResultAsync } from 'neverthrow';
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { createClient } from '@polkadot-api/raw-client';
|
|
2
|
+
import { createStatementSdk } from '@polkadot-api/sdk-statement';
|
|
3
|
+
import { FixedSizeBinary } from '@polkadot-api/substrate-bindings';
|
|
4
|
+
export function createRpcTransport(rpcProvider) {
|
|
5
|
+
const POLLING_INTERVAL = 1000;
|
|
6
|
+
const client = createClient(rpcProvider);
|
|
7
|
+
const sdk = createStatementSdk((method, params) => {
|
|
8
|
+
return new Promise((resolve, reject) => {
|
|
9
|
+
client.request(method, params, {
|
|
10
|
+
onSuccess: resolve,
|
|
11
|
+
onError: reject,
|
|
12
|
+
});
|
|
13
|
+
});
|
|
14
|
+
});
|
|
15
|
+
const transportProvider = {
|
|
16
|
+
getStatements(topics) {
|
|
17
|
+
// @ts-expect-error lib versions mismatch
|
|
18
|
+
return sdk.getStatements({ topics: topics.map(topic => new FixedSizeBinary(topic)) });
|
|
19
|
+
},
|
|
20
|
+
subscribeStatements(topics, callback) {
|
|
21
|
+
return polling(POLLING_INTERVAL, () => transportProvider.getStatements(topics), callback);
|
|
22
|
+
},
|
|
23
|
+
submitStatement(statement) {
|
|
24
|
+
return sdk.submit(statement);
|
|
25
|
+
},
|
|
26
|
+
};
|
|
27
|
+
return transportProvider;
|
|
28
|
+
}
|
|
29
|
+
function polling(interval, request, callback) {
|
|
30
|
+
let active = true;
|
|
31
|
+
let tm = null;
|
|
32
|
+
function createCycle() {
|
|
33
|
+
tm = setTimeout(() => {
|
|
34
|
+
if (!active) {
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
request()
|
|
38
|
+
.then(callback)
|
|
39
|
+
.finally(() => {
|
|
40
|
+
createCycle();
|
|
41
|
+
});
|
|
42
|
+
}, interval);
|
|
43
|
+
}
|
|
44
|
+
createCycle();
|
|
45
|
+
return () => {
|
|
46
|
+
active = false;
|
|
47
|
+
if (tm !== null) {
|
|
48
|
+
clearTimeout(tm);
|
|
49
|
+
}
|
|
50
|
+
};
|
|
51
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { SignedStatement, Statement } from '@polkadot-api/sdk-statement';
|
|
2
|
+
export type Transport = {
|
|
3
|
+
getStatements(topics: Uint8Array[]): Promise<Statement[]>;
|
|
4
|
+
subscribeStatements(topics: Uint8Array[], callback: (response: Statement[]) => unknown): VoidFunction;
|
|
5
|
+
submitStatement(statement: SignedStatement): Promise<void>;
|
|
6
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export declare const HandshakeData: import("scale-ts").Codec<{
|
|
2
|
+
tag: "V1";
|
|
3
|
+
value: [import("../../modules/crypto.js").SsPublicKey, import("../../modules/crypto.js").EncrPublicKey, string];
|
|
4
|
+
}>;
|
|
5
|
+
export declare const HandshakeResponsePayload: import("scale-ts").Codec<{
|
|
6
|
+
tag: "V1";
|
|
7
|
+
value: [Uint8Array<ArrayBufferLike>, Uint8Array<ArrayBufferLike>];
|
|
8
|
+
}>;
|
|
9
|
+
export declare const HandshakeResponseSensitiveData: import("scale-ts").Codec<[Uint8Array<ArrayBufferLike>, Uint8Array<ArrayBufferLike>]>;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { Bytes, Enum, Tuple, str } from 'scale-ts';
|
|
2
|
+
import { EncrPubKey, SsPubKey } from '../../modules/crypto.js';
|
|
3
|
+
export const HandshakeData = Enum({
|
|
4
|
+
V1: Tuple(SsPubKey, EncrPubKey, str),
|
|
5
|
+
});
|
|
6
|
+
export const HandshakeResponsePayload = Enum({
|
|
7
|
+
// [encrypted, tmp_key]
|
|
8
|
+
V1: Tuple(Bytes(), Bytes(65)),
|
|
9
|
+
});
|
|
10
|
+
export const HandshakeResponseSensitiveData = Tuple(Bytes(65), Bytes(32));
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export declare const HandshakeData: import("scale-ts").Codec<{
|
|
2
|
+
tag: "V1";
|
|
3
|
+
value: [import("../../modules/crypto.js").SsPublicKey, import("../../modules/crypto.js").EncrPublicKey, string];
|
|
4
|
+
}>;
|
|
5
|
+
export declare const HandshakeResponsePayload: import("scale-ts").Codec<{
|
|
6
|
+
tag: "V1";
|
|
7
|
+
value: [Uint8Array<ArrayBufferLike>, Uint8Array<ArrayBufferLike>];
|
|
8
|
+
}>;
|
|
9
|
+
export declare const HandshakeResponseSensitiveData: import("scale-ts").Codec<[Uint8Array<ArrayBufferLike>, Uint8Array<ArrayBufferLike>]>;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { Bytes, Enum, Tuple, str } from 'scale-ts';
|
|
2
|
+
import { EncrPubKey, SsPubKey } from '../../modules/crypto.js';
|
|
3
|
+
export const HandshakeData = Enum({
|
|
4
|
+
V1: Tuple(SsPubKey, EncrPubKey, str),
|
|
5
|
+
});
|
|
6
|
+
export const HandshakeResponsePayload = Enum({
|
|
7
|
+
// [encrypted, tmp_key]
|
|
8
|
+
V1: Tuple(Bytes(), Bytes(65)),
|
|
9
|
+
});
|
|
10
|
+
export const HandshakeResponseSensitiveData = Tuple(Bytes(65), Bytes(32));
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import type { ResultAsync } from 'neverthrow';
|
|
2
|
+
import type { Transport } from '../../modules/transport/transport.js';
|
|
3
|
+
import type { UserSession, UserSessionStorage } from '../user/userSessionStorage.js';
|
|
4
|
+
import type { AuthentificationStatus } from './types.js';
|
|
5
|
+
export type AuthComponent = ReturnType<typeof createAuthComponent>;
|
|
6
|
+
type Params = {
|
|
7
|
+
/**
|
|
8
|
+
* Host app Id.
|
|
9
|
+
* CAUTION! This value should be stable.
|
|
10
|
+
*/
|
|
11
|
+
appId: string;
|
|
12
|
+
/**
|
|
13
|
+
* URL for additional metadata that will be displayed during pairing process.
|
|
14
|
+
* Content of provided json shound be
|
|
15
|
+
* ```ts
|
|
16
|
+
* interface Metadata {
|
|
17
|
+
* name: string;
|
|
18
|
+
* icon: string; // url for icon. Icon should be a rasterized image with min size 256x256 px.
|
|
19
|
+
* }
|
|
20
|
+
* ```
|
|
21
|
+
*/
|
|
22
|
+
metadata: string;
|
|
23
|
+
transport: Transport;
|
|
24
|
+
userSessionStorage: UserSessionStorage;
|
|
25
|
+
};
|
|
26
|
+
export declare function createAuthComponent({ appId, metadata, transport, userSessionStorage }: Params): {
|
|
27
|
+
status: {
|
|
28
|
+
read: () => AuthentificationStatus;
|
|
29
|
+
subscribe: (fn: (value: AuthentificationStatus) => void) => () => void;
|
|
30
|
+
onFirstSubscribe: (callback: VoidFunction) => import("nanoevents").Unsubscribe;
|
|
31
|
+
onLastUnsubscribe: (callback: VoidFunction) => import("nanoevents").Unsubscribe;
|
|
32
|
+
};
|
|
33
|
+
authenticate(): ResultAsync<UserSession | null, Error>;
|
|
34
|
+
abortAuthentication(): void;
|
|
35
|
+
};
|
|
36
|
+
export {};
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
import { toHex } from '@polkadot-api/utils';
|
|
2
|
+
import { err, errAsync, fromPromise, fromThrowable, ok } from 'neverthrow';
|
|
3
|
+
import { AbortError } from '../../helpers/abortError.js';
|
|
4
|
+
import { toError } from '../../helpers/utils.js';
|
|
5
|
+
import { ENCR_SECRET_SEED_SIZE, SS_SECRET_SEED_SIZE, createEncrSecret, createRandomSeed, createSharedSecret, createSsHardDerivation, createSsSecret, decrypt, getEncrPub, getSsPub, khash, mergeBytes, stringToBytes, } from '../../modules/crypto.js';
|
|
6
|
+
import { createAccount } from '../../modules/session/helpers.js';
|
|
7
|
+
import { createState, readonly } from '../../modules/state.js';
|
|
8
|
+
import { createUserSession } from '../../modules/statementStore.js';
|
|
9
|
+
import { HandshakeData, HandshakeResponsePayload, HandshakeResponseSensitiveData } from './codec.js';
|
|
10
|
+
export function createAuthComponent({ appId, metadata, transport, userSessionStorage }) {
|
|
11
|
+
const authStatus = createState({ step: 'none' });
|
|
12
|
+
let authResults = null;
|
|
13
|
+
let abort = null;
|
|
14
|
+
function handshake(signal) {
|
|
15
|
+
try {
|
|
16
|
+
authStatus.write({ step: 'initial' });
|
|
17
|
+
const { encrSecret, encrPublicKey, ssPublicKey } = getSecretKeys(appId);
|
|
18
|
+
const hostAccount = createAccount(ssPublicKey, encrPublicKey);
|
|
19
|
+
const handshakePayload = createHandshakePayloadV1({ ssPublicKey, encrPublicKey, metadata }).andTee(payload => authStatus.write({ step: 'pairing', payload: createDeeplink(payload) }));
|
|
20
|
+
const handshakeTopic = createHandshakeTopic(hostAccount);
|
|
21
|
+
const pappResponse = handshakePayload
|
|
22
|
+
.andThen(() => handshakeTopic)
|
|
23
|
+
.asyncAndThen(topic => waitForStatements(callback => transport.subscribeSession(topic, callback), signal, (statements, resolve) => {
|
|
24
|
+
for (const statement of [...statements].reverse()) {
|
|
25
|
+
if (!statement.data)
|
|
26
|
+
continue;
|
|
27
|
+
const session = retrieveSession({
|
|
28
|
+
hostAccount,
|
|
29
|
+
encrSecret,
|
|
30
|
+
payload: statement.data.asBytes(),
|
|
31
|
+
});
|
|
32
|
+
resolve(session);
|
|
33
|
+
break;
|
|
34
|
+
}
|
|
35
|
+
}));
|
|
36
|
+
const userCreated = pappResponse.andThen(userSessionStorage.add);
|
|
37
|
+
return userCreated
|
|
38
|
+
.orElse(e => (AbortError.isAbortError(e) ? ok(null) : err(toError(e))))
|
|
39
|
+
.andTee(session => {
|
|
40
|
+
if (session) {
|
|
41
|
+
authStatus.write({ step: 'finished', session });
|
|
42
|
+
}
|
|
43
|
+
else {
|
|
44
|
+
authStatus.write({ step: 'none' });
|
|
45
|
+
}
|
|
46
|
+
})
|
|
47
|
+
.orTee(e => authStatus.write({ step: 'error', message: e.message }));
|
|
48
|
+
}
|
|
49
|
+
catch (e) {
|
|
50
|
+
return errAsync(toError(e));
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
const authModule = {
|
|
54
|
+
status: readonly(authStatus),
|
|
55
|
+
authenticate() {
|
|
56
|
+
if (authResults) {
|
|
57
|
+
return authResults;
|
|
58
|
+
}
|
|
59
|
+
abort = new AbortController();
|
|
60
|
+
authResults = handshake(abort.signal);
|
|
61
|
+
return authResults;
|
|
62
|
+
},
|
|
63
|
+
abortAuthentication() {
|
|
64
|
+
if (abort) {
|
|
65
|
+
authResults = null;
|
|
66
|
+
authStatus.reset();
|
|
67
|
+
abort.abort(new AbortError('Aborted by user.'));
|
|
68
|
+
}
|
|
69
|
+
},
|
|
70
|
+
};
|
|
71
|
+
return authModule;
|
|
72
|
+
}
|
|
73
|
+
const createHandshakeTopic = fromThrowable((account) => khash(account.accountId, mergeBytes(account.publicKey, stringToBytes('topic'))), toError);
|
|
74
|
+
const createHandshakePayloadV1 = fromThrowable(({ encrPublicKey, ssPublicKey, metadata, }) => HandshakeData.enc({
|
|
75
|
+
tag: 'V1',
|
|
76
|
+
value: [ssPublicKey, encrPublicKey, metadata],
|
|
77
|
+
}), toError);
|
|
78
|
+
function parseHandshakePayload(payload) {
|
|
79
|
+
const decoded = HandshakeResponsePayload.dec(payload);
|
|
80
|
+
switch (decoded.tag) {
|
|
81
|
+
case 'V1':
|
|
82
|
+
return {
|
|
83
|
+
encrypted: decoded.value[0],
|
|
84
|
+
tmpKey: decoded.value[1],
|
|
85
|
+
};
|
|
86
|
+
default:
|
|
87
|
+
throw new Error('Unsupported handshake payload version');
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
function retrieveSession({ payload, encrSecret, hostAccount, }) {
|
|
91
|
+
const { encrypted, tmpKey } = parseHandshakePayload(payload);
|
|
92
|
+
const symmetricKey = createSharedSecret(encrSecret, tmpKey);
|
|
93
|
+
const decrypted = decrypt(symmetricKey, encrypted);
|
|
94
|
+
const [pappEncrPublicKey, pappAccountId] = HandshakeResponseSensitiveData.dec(decrypted);
|
|
95
|
+
const sharedSecret = createSharedSecret(encrSecret, pappEncrPublicKey);
|
|
96
|
+
const peerAccount = createAccount(pappAccountId, sharedSecret);
|
|
97
|
+
return createUserSession(hostAccount, peerAccount);
|
|
98
|
+
}
|
|
99
|
+
function getSsKeys(appId) {
|
|
100
|
+
const seed = createRandomSeed(appId, SS_SECRET_SEED_SIZE);
|
|
101
|
+
const ssSecret = createSsHardDerivation(createSsSecret(seed), '//wallet');
|
|
102
|
+
return {
|
|
103
|
+
ssSecret: ssSecret,
|
|
104
|
+
ssPublicKey: getSsPub(ssSecret),
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
function getEncrKeys(appId) {
|
|
108
|
+
const seed = createRandomSeed(appId, ENCR_SECRET_SEED_SIZE);
|
|
109
|
+
const encrSecret = createEncrSecret(seed);
|
|
110
|
+
return {
|
|
111
|
+
encrSecret,
|
|
112
|
+
encrPublicKey: getEncrPub(encrSecret),
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
function getSecretKeys(appId) {
|
|
116
|
+
const ss = getSsKeys(appId);
|
|
117
|
+
const encr = getEncrKeys(appId);
|
|
118
|
+
return {
|
|
119
|
+
...ss,
|
|
120
|
+
...encr,
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
function createDeeplink(payload) {
|
|
124
|
+
return `polkadotapp://pair?handshake=${toHex(payload)}`;
|
|
125
|
+
}
|
|
126
|
+
function waitForStatements(subscribe, abortSignal, callback) {
|
|
127
|
+
return fromPromise(new Promise((resolve, reject) => {
|
|
128
|
+
const unsubscribe = subscribe(statements => {
|
|
129
|
+
if (abortSignal?.aborted) {
|
|
130
|
+
unsubscribe();
|
|
131
|
+
try {
|
|
132
|
+
abortSignal.throwIfAborted();
|
|
133
|
+
}
|
|
134
|
+
catch (e) {
|
|
135
|
+
reject(e);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
try {
|
|
139
|
+
callback(statements, value => {
|
|
140
|
+
unsubscribe();
|
|
141
|
+
resolve(value);
|
|
142
|
+
});
|
|
143
|
+
}
|
|
144
|
+
catch (e) {
|
|
145
|
+
unsubscribe();
|
|
146
|
+
reject(e);
|
|
147
|
+
}
|
|
148
|
+
});
|
|
149
|
+
}), toError);
|
|
150
|
+
}
|