@novasamatech/product-sdk 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +82 -0
- package/dist/constants.d.ts +11 -0
- package/dist/constants.js +12 -0
- package/dist/createSpektrMetaProvider.d.ts +7 -0
- package/dist/createSpektrMetaProvider.js +21 -0
- package/dist/createSpektrPapiProvider.d.ts +11 -0
- package/dist/createSpektrPapiProvider.js +43 -0
- package/dist/createTransport.d.ts +21 -0
- package/dist/createTransport.js +111 -0
- package/dist/defaultTransport.d.ts +3 -0
- package/dist/defaultTransport.js +57 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.js +6 -0
- package/dist/inject.d.ts +2 -0
- package/dist/inject.js +60 -0
- package/dist/injectSpektrExtension.d.ts +24 -0
- package/dist/injectSpektrExtension.js +72 -0
- package/dist/transport.d.ts +3 -0
- package/dist/transport.js +51 -0
- package/dist/types.d.ts +8 -0
- package/dist/types.js +1 -0
- package/dist/utils.d.ts +2 -0
- package/dist/utils.js +14 -0
- package/package.json +37 -0
package/README.md
ADDED
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
# @novasamatech/product-sdk
|
|
2
|
+
|
|
3
|
+
Easy way to embed Polkadot host functionality into your dapp.
|
|
4
|
+
|
|
5
|
+
## Overview
|
|
6
|
+
|
|
7
|
+
Spektr SDK provides a set of tools to integrate your application with Spektr dapp browser.
|
|
8
|
+
Core features:
|
|
9
|
+
- Generic account provider similar to [polkadot-js extension](https://polkadot.js.org/extension/).
|
|
10
|
+
- Redirect [PAPI](https://papi.how/) requests to host application
|
|
11
|
+
- Receive additional information from host application - supported chains, theme, etc.
|
|
12
|
+
|
|
13
|
+
## Installation
|
|
14
|
+
|
|
15
|
+
```shell
|
|
16
|
+
npm install @novasamatech/product-sdk --save -E
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## Usage
|
|
20
|
+
|
|
21
|
+
### Injecting account provider into `injectedWeb3` interface
|
|
22
|
+
|
|
23
|
+
Spektr SDK can provide account information and signers with same interface as any other polkadot compatible wallet.
|
|
24
|
+
|
|
25
|
+
```ts
|
|
26
|
+
import { injectSpektrExtension, SpektrExtensionName } from '@novasamatech/product-sdk';
|
|
27
|
+
import { connectInjectedExtension, type InjectedPolkadotAccount } from '@polkadot-api/pjs-signer';
|
|
28
|
+
|
|
29
|
+
async function getSpektrExtension() {
|
|
30
|
+
const ready = await injectSpektrExtension();
|
|
31
|
+
|
|
32
|
+
if (ready) {
|
|
33
|
+
return connectInjectedExtension(SpektrExtensionName)
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
return null;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
async function getAccounts(): Promise<InjectedPolkadotAccount[]> {
|
|
40
|
+
const extension = await getSpektrExtension();
|
|
41
|
+
|
|
42
|
+
if (extension) {
|
|
43
|
+
return extension.getAccounts()
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// fallback to other providers
|
|
47
|
+
return [];
|
|
48
|
+
}
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
### Redirecting PAPI requests to host application
|
|
52
|
+
|
|
53
|
+
You can wrap your PAPI provider with spektr provider to support redirecting requests to host application.
|
|
54
|
+
|
|
55
|
+
```diff
|
|
56
|
+
import { createClient, type PolkadotClient } from 'polkadot-api';
|
|
57
|
+
import { getWsProvider } from 'polkadot-api/ws-provider';
|
|
58
|
+
import { createSpektrPapiProvider, WellKnownChain } from '@novasamatech/product-sdk';
|
|
59
|
+
|
|
60
|
+
function createPapiClient(): PolkadotClient {
|
|
61
|
+
const polkadotEndpoint = 'wss://...';
|
|
62
|
+
|
|
63
|
+
- const provider = getWsProvider(polkadotEndpoint);
|
|
64
|
+
+ const provider = createSpektrPapiProvider({
|
|
65
|
+
+ chainId: WellKnownChain.polkadotRelay,
|
|
66
|
+
+ fallback: getWsProvider(polkadotEndpoint),
|
|
67
|
+
+ });
|
|
68
|
+
|
|
69
|
+
return createClient(provider);
|
|
70
|
+
}
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
### Subscribing metadata and statuses
|
|
74
|
+
|
|
75
|
+
```ts
|
|
76
|
+
import { spektrMetaProvider } from '@novasamatech/product-sdk';
|
|
77
|
+
|
|
78
|
+
const unsubscribe = spektrMetaProvider.subscribeConnectionStatus((status) => {
|
|
79
|
+
console.log('connection status changed', status);
|
|
80
|
+
});
|
|
81
|
+
```
|
|
82
|
+
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export declare const Version: string;
|
|
2
|
+
export declare const SpektrExtensionName = "spektr";
|
|
3
|
+
export declare const WellKnownChain: {
|
|
4
|
+
readonly polkadotRelay: "0x91b171bb158e2d3848fa23a9f1c25182fb8e20313b2c1eb49219da7a70ce90c3";
|
|
5
|
+
readonly polkadotAssetHub: "0x68d56f15f85d3136970ec16946040bc1752654e906147f7e43e9d539d7c3de2f";
|
|
6
|
+
readonly kusamaRelay: "0xb0a8d493285c2df73290dfb7e61f870f17b41801197a149ca93654499ea3dafe";
|
|
7
|
+
readonly kusamaAssetHub: "0x48239ef607d7928874027a43a67689209727dfb3d3dc5e5b03a39bdc2eda771a";
|
|
8
|
+
readonly westendRelay: "0xe143f23803ac50e8f6f8e62695d1ce9e4e1d68aa36c1cd2cfd15340213f3423e";
|
|
9
|
+
readonly westendAssetHub: "0x67f9723393ef76214df0118c34bbbd3dbebc8ed46a10973a8c969d48fe7598c9";
|
|
10
|
+
readonly rococo: "0x6408de7737c59c238890533af25896a2c20608d8b380bb01029acb392781063e";
|
|
11
|
+
};
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import pkg from '../package.json';
|
|
2
|
+
export const Version = pkg.version;
|
|
3
|
+
export const SpektrExtensionName = 'spektr';
|
|
4
|
+
export const WellKnownChain = {
|
|
5
|
+
polkadotRelay: '0x91b171bb158e2d3848fa23a9f1c25182fb8e20313b2c1eb49219da7a70ce90c3',
|
|
6
|
+
polkadotAssetHub: '0x68d56f15f85d3136970ec16946040bc1752654e906147f7e43e9d539d7c3de2f',
|
|
7
|
+
kusamaRelay: '0xb0a8d493285c2df73290dfb7e61f870f17b41801197a149ca93654499ea3dafe',
|
|
8
|
+
kusamaAssetHub: '0x48239ef607d7928874027a43a67689209727dfb3d3dc5e5b03a39bdc2eda771a',
|
|
9
|
+
westendRelay: '0xe143f23803ac50e8f6f8e62695d1ce9e4e1d68aa36c1cd2cfd15340213f3423e',
|
|
10
|
+
westendAssetHub: '0x67f9723393ef76214df0118c34bbbd3dbebc8ed46a10973a8c969d48fe7598c9',
|
|
11
|
+
rococo: '0x6408de7737c59c238890533af25896a2c20608d8b380bb01029acb392781063e',
|
|
12
|
+
};
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { ConnectionStatus, Transport } from '@novasamatech/host-api';
|
|
2
|
+
export declare function createSpektrMetaProvider(transport?: Transport): {
|
|
3
|
+
subscribeConnectionStatus(callback: (connectionStatus: ConnectionStatus) => void): VoidFunction;
|
|
4
|
+
};
|
|
5
|
+
export declare const spektrMetaProvider: {
|
|
6
|
+
subscribeConnectionStatus(callback: (connectionStatus: ConnectionStatus) => void): VoidFunction;
|
|
7
|
+
};
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { defaultTransport } from './defaultTransport';
|
|
2
|
+
export function createSpektrMetaProvider(transport = defaultTransport) {
|
|
3
|
+
if (transport.isCorrectEnvironment() && typeof window !== 'undefined') {
|
|
4
|
+
const getUrl = () => {
|
|
5
|
+
return window.location.pathname + window.location.hash + window.location.search;
|
|
6
|
+
};
|
|
7
|
+
window.addEventListener('hashchange', () => {
|
|
8
|
+
transport.postMessage('_', { tag: 'locationChangedV1', value: getUrl() });
|
|
9
|
+
});
|
|
10
|
+
window.addEventListener('popstate', () => {
|
|
11
|
+
transport.postMessage('_', { tag: 'locationChangedV1', value: getUrl() });
|
|
12
|
+
});
|
|
13
|
+
transport.postMessage('_', { tag: 'locationChangedV1', value: getUrl() });
|
|
14
|
+
}
|
|
15
|
+
return {
|
|
16
|
+
subscribeConnectionStatus(callback) {
|
|
17
|
+
return transport.onConnectionStatusChange(callback);
|
|
18
|
+
},
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
export const spektrMetaProvider = createSpektrMetaProvider();
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { HexString, Transport } from '@novasamatech/host-api';
|
|
2
|
+
import type { JsonRpcProvider } from '@polkadot-api/json-rpc-provider';
|
|
3
|
+
type Params = {
|
|
4
|
+
chainId: HexString;
|
|
5
|
+
fallback: JsonRpcProvider;
|
|
6
|
+
};
|
|
7
|
+
type InternalParams = {
|
|
8
|
+
transport?: Transport;
|
|
9
|
+
};
|
|
10
|
+
export declare function createSpektrPapiProvider({ chainId, fallback }: Params, internal?: InternalParams): JsonRpcProvider;
|
|
11
|
+
export {};
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { unwrapResponseOrThrow } from '@novasamatech/host-api';
|
|
2
|
+
import { getSyncProvider } from '@polkadot-api/json-rpc-provider-proxy';
|
|
3
|
+
import { defaultTransport } from './defaultTransport';
|
|
4
|
+
export function createSpektrPapiProvider({ chainId, fallback }, internal) {
|
|
5
|
+
const transport = internal?.transport ?? defaultTransport;
|
|
6
|
+
if (!transport.isCorrectEnvironment())
|
|
7
|
+
return fallback;
|
|
8
|
+
const spektrProvider = onMessage => {
|
|
9
|
+
const unsubscribe = transport.subscribe('papiProviderReceiveMessageV1', (_, payload) => {
|
|
10
|
+
const unwrapped = unwrapResponseOrThrow(payload);
|
|
11
|
+
if (unwrapped.chainId === chainId) {
|
|
12
|
+
onMessage(unwrapped.message);
|
|
13
|
+
}
|
|
14
|
+
});
|
|
15
|
+
return {
|
|
16
|
+
send(message) {
|
|
17
|
+
transport.postMessage('_', { tag: 'papiProviderSendMessageV1', value: { chainId, message } });
|
|
18
|
+
},
|
|
19
|
+
disconnect() {
|
|
20
|
+
unsubscribe();
|
|
21
|
+
},
|
|
22
|
+
};
|
|
23
|
+
};
|
|
24
|
+
function checkIfReady() {
|
|
25
|
+
return transport.isReady().then(ready => {
|
|
26
|
+
if (!ready)
|
|
27
|
+
return false;
|
|
28
|
+
return transport
|
|
29
|
+
.request({ tag: 'supportFeatureRequestV1', value: { tag: 'chain', value: { chainId } } }, 'supportFeatureResponseV1')
|
|
30
|
+
.then(payload => {
|
|
31
|
+
const result = unwrapResponseOrThrow(payload);
|
|
32
|
+
if (result.tag === 'chain' && result.value.chainId === chainId) {
|
|
33
|
+
return result.value.result;
|
|
34
|
+
}
|
|
35
|
+
})
|
|
36
|
+
.catch(e => {
|
|
37
|
+
transport.provider.logger.error('Error checking chain support', e);
|
|
38
|
+
return false;
|
|
39
|
+
});
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
return getSyncProvider(() => checkIfReady().then(ready => (ready ? spektrProvider : fallback)));
|
|
43
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { type MessagePayloadSchema, type MessageType, type PickMessagePayload } from '@novasamatech/host-api';
|
|
2
|
+
export type Provider = {
|
|
3
|
+
send(message: Uint8Array): void;
|
|
4
|
+
on(callback: (message: Uint8Array) => void): () => void;
|
|
5
|
+
};
|
|
6
|
+
export declare const defaultProvider: Provider;
|
|
7
|
+
export type Transport = NonNullable<ReturnType<typeof createTransport>>;
|
|
8
|
+
export declare function createTransport(provider: Provider): {
|
|
9
|
+
isSpektrReady(): Promise<boolean>;
|
|
10
|
+
subscribeAny(callback: (id: string, payload: MessagePayloadSchema) => void): () => void;
|
|
11
|
+
subscribe<const Type extends MessageType>(type: Type, callback: (id: string, payload: PickMessagePayload<Type>) => void): () => void;
|
|
12
|
+
send(id: string, payload: MessagePayloadSchema): string;
|
|
13
|
+
request(payload: MessagePayloadSchema): Promise<MessagePayloadSchema>;
|
|
14
|
+
} | null;
|
|
15
|
+
export declare const defaultTransport: {
|
|
16
|
+
isSpektrReady(): Promise<boolean>;
|
|
17
|
+
subscribeAny(callback: (id: string, payload: MessagePayloadSchema) => void): () => void;
|
|
18
|
+
subscribe<const Type extends MessageType>(type: Type, callback: (id: string, payload: PickMessagePayload<Type>) => void): () => void;
|
|
19
|
+
send(id: string, payload: MessagePayloadSchema): string;
|
|
20
|
+
request(payload: MessagePayloadSchema): Promise<MessagePayloadSchema>;
|
|
21
|
+
} | null;
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import { HANDSHAKE_INTERVAL, isValidMessage, promiseWithResolvers } from '@novasamatech/product-sdk-shared';
|
|
2
|
+
import { messageEncoder, } from '@novasamatech/host-api';
|
|
3
|
+
import { nanoid } from 'nanoid';
|
|
4
|
+
import { getParentWindow, inIframe } from './utils';
|
|
5
|
+
export const defaultProvider = {
|
|
6
|
+
send(message) {
|
|
7
|
+
getParentWindow().postMessage(message, '*', [message.buffer]);
|
|
8
|
+
},
|
|
9
|
+
on(callback) {
|
|
10
|
+
const handle = (event) => {
|
|
11
|
+
if (!isValidMessage(event, getParentWindow(), window))
|
|
12
|
+
return;
|
|
13
|
+
callback(event.data);
|
|
14
|
+
};
|
|
15
|
+
window.addEventListener('message', handle);
|
|
16
|
+
return () => {
|
|
17
|
+
window.removeEventListener('message', handle);
|
|
18
|
+
};
|
|
19
|
+
},
|
|
20
|
+
};
|
|
21
|
+
export function createTransport(provider) {
|
|
22
|
+
if (!inIframe()) {
|
|
23
|
+
return null;
|
|
24
|
+
}
|
|
25
|
+
const readyAbortController = new AbortController();
|
|
26
|
+
let connected = null;
|
|
27
|
+
const api = {
|
|
28
|
+
isSpektrReady() {
|
|
29
|
+
if (connected !== null) {
|
|
30
|
+
return Promise.resolve(connected);
|
|
31
|
+
}
|
|
32
|
+
let resolved = false;
|
|
33
|
+
const request = new Promise(resolve => {
|
|
34
|
+
const interval = setInterval(() => {
|
|
35
|
+
if (readyAbortController.signal.aborted) {
|
|
36
|
+
clearInterval(interval);
|
|
37
|
+
resolve(false);
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
const id = nanoid();
|
|
41
|
+
const encoded = messageEncoder.enc({ id, payload: { tag: 'handshakeRequestV1', value: undefined } });
|
|
42
|
+
const unsubscribe = api.subscribe('handshakeResponseV1', receivedId => {
|
|
43
|
+
if (receivedId !== id)
|
|
44
|
+
return;
|
|
45
|
+
clearInterval(interval);
|
|
46
|
+
unsubscribe();
|
|
47
|
+
resolved = true;
|
|
48
|
+
resolve(true);
|
|
49
|
+
});
|
|
50
|
+
provider.send(encoded);
|
|
51
|
+
}, HANDSHAKE_INTERVAL);
|
|
52
|
+
});
|
|
53
|
+
return Promise.race([
|
|
54
|
+
request,
|
|
55
|
+
new Promise(resolve => {
|
|
56
|
+
setTimeout(() => {
|
|
57
|
+
if (!resolved) {
|
|
58
|
+
readyAbortController.abort();
|
|
59
|
+
resolve(false);
|
|
60
|
+
}
|
|
61
|
+
}, 1_000);
|
|
62
|
+
}).then(result => {
|
|
63
|
+
connected = result;
|
|
64
|
+
return result;
|
|
65
|
+
}),
|
|
66
|
+
]);
|
|
67
|
+
},
|
|
68
|
+
subscribeAny(callback) {
|
|
69
|
+
return provider.on(message => {
|
|
70
|
+
let result;
|
|
71
|
+
try {
|
|
72
|
+
result = messageEncoder.dec(message);
|
|
73
|
+
}
|
|
74
|
+
catch {
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
callback(result.id, result.payload);
|
|
78
|
+
});
|
|
79
|
+
},
|
|
80
|
+
subscribe(type, callback) {
|
|
81
|
+
return api.subscribeAny((id, message) => {
|
|
82
|
+
if (message.tag == type) {
|
|
83
|
+
callback(id, message);
|
|
84
|
+
}
|
|
85
|
+
});
|
|
86
|
+
},
|
|
87
|
+
send(id, payload) {
|
|
88
|
+
const encoded = messageEncoder.enc({ id, payload });
|
|
89
|
+
provider.send(encoded);
|
|
90
|
+
return id;
|
|
91
|
+
},
|
|
92
|
+
async request(payload) {
|
|
93
|
+
const ready = await api.isSpektrReady();
|
|
94
|
+
if (!ready) {
|
|
95
|
+
throw new Error('Spektr is not ready');
|
|
96
|
+
}
|
|
97
|
+
const id = nanoid();
|
|
98
|
+
const { resolve, promise } = promiseWithResolvers();
|
|
99
|
+
const unsubscribe = api.subscribeAny((receivedId, message) => {
|
|
100
|
+
if (receivedId === id) {
|
|
101
|
+
unsubscribe();
|
|
102
|
+
resolve(message);
|
|
103
|
+
}
|
|
104
|
+
});
|
|
105
|
+
api.send(id, payload);
|
|
106
|
+
return promise;
|
|
107
|
+
},
|
|
108
|
+
};
|
|
109
|
+
return api;
|
|
110
|
+
}
|
|
111
|
+
export const defaultTransport = createTransport(defaultProvider);
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { createDefaultLogger, createTransport } from '@novasamatech/host-api';
|
|
2
|
+
function getParentWindow() {
|
|
3
|
+
if (window.top) {
|
|
4
|
+
return window.top;
|
|
5
|
+
}
|
|
6
|
+
throw new Error('No parent window found');
|
|
7
|
+
}
|
|
8
|
+
function isIframe() {
|
|
9
|
+
try {
|
|
10
|
+
return window !== window.top;
|
|
11
|
+
}
|
|
12
|
+
catch {
|
|
13
|
+
return false;
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
function isValidMessage(event, sourceEnv, currentEnv) {
|
|
17
|
+
return (event.source !== currentEnv &&
|
|
18
|
+
event.source === sourceEnv &&
|
|
19
|
+
event.data &&
|
|
20
|
+
event.data.constructor.name === 'Uint8Array');
|
|
21
|
+
}
|
|
22
|
+
function createDefaultSdkProvider() {
|
|
23
|
+
const subscribers = new Set();
|
|
24
|
+
const handleMessage = (event) => {
|
|
25
|
+
if (!isValidMessage(event, getParentWindow(), window))
|
|
26
|
+
return;
|
|
27
|
+
for (const subscriber of subscribers) {
|
|
28
|
+
subscriber(event.data);
|
|
29
|
+
}
|
|
30
|
+
};
|
|
31
|
+
if (isIframe()) {
|
|
32
|
+
window.addEventListener('message', handleMessage);
|
|
33
|
+
}
|
|
34
|
+
return {
|
|
35
|
+
logger: createDefaultLogger(),
|
|
36
|
+
isCorrectEnvironment() {
|
|
37
|
+
return isIframe();
|
|
38
|
+
},
|
|
39
|
+
postMessage(message) {
|
|
40
|
+
getParentWindow().postMessage(message, '*', [message.buffer]);
|
|
41
|
+
},
|
|
42
|
+
subscribe(callback) {
|
|
43
|
+
subscribers.add(callback);
|
|
44
|
+
return () => {
|
|
45
|
+
subscribers.delete(callback);
|
|
46
|
+
};
|
|
47
|
+
},
|
|
48
|
+
dispose() {
|
|
49
|
+
subscribers.clear();
|
|
50
|
+
if (isIframe()) {
|
|
51
|
+
window.removeEventListener('message', handleMessage);
|
|
52
|
+
}
|
|
53
|
+
},
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
export const defaultProvider = createDefaultSdkProvider();
|
|
57
|
+
export const defaultTransport = createTransport(defaultProvider, { handshakeTimeout: 1_000 });
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export { SpektrExtensionName, WellKnownChain } from './constants';
|
|
2
|
+
export { defaultProvider, defaultTransport } from './defaultTransport';
|
|
3
|
+
export { spektrMetaProvider } from './createSpektrMetaProvider';
|
|
4
|
+
export { createSpektrMetaProvider } from './createSpektrMetaProvider';
|
|
5
|
+
export { createExtensionEnableFactory, injectSpektrExtension } from './injectSpektrExtension';
|
|
6
|
+
export { createSpektrPapiProvider } from './createSpektrPapiProvider';
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export { SpektrExtensionName, WellKnownChain } from './constants';
|
|
2
|
+
export { defaultProvider, defaultTransport } from './defaultTransport';
|
|
3
|
+
export { spektrMetaProvider } from './createSpektrMetaProvider';
|
|
4
|
+
export { createSpektrMetaProvider } from './createSpektrMetaProvider';
|
|
5
|
+
export { createExtensionEnableFactory, injectSpektrExtension } from './injectSpektrExtension';
|
|
6
|
+
export { createSpektrPapiProvider } from './createSpektrPapiProvider';
|
package/dist/inject.d.ts
ADDED
package/dist/inject.js
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { injectExtension } from '@polkadot/extension-inject';
|
|
2
|
+
import { unwrapResponseOrThrow } from '@spektr/sdk-transport';
|
|
3
|
+
import { SpektrExtensionName, Version } from './constants';
|
|
4
|
+
import { createTransport, defaultProvider } from './createTransport';
|
|
5
|
+
function injectPolkadotExtension(transport) {
|
|
6
|
+
async function enable() {
|
|
7
|
+
return {
|
|
8
|
+
accounts: {
|
|
9
|
+
get() {
|
|
10
|
+
return transport.request({ tag: 'getAccountsRequestV1', value: undefined }).then(e => {
|
|
11
|
+
if (e.tag === 'getAccountsResponseV1') {
|
|
12
|
+
return unwrapResponseOrThrow(e.value);
|
|
13
|
+
}
|
|
14
|
+
throw new Error(`Invalid response, got ${e.tag} message`);
|
|
15
|
+
});
|
|
16
|
+
},
|
|
17
|
+
subscribe(callback) {
|
|
18
|
+
return transport.subscribe('getAccountsResponseV1', (_, message) => {
|
|
19
|
+
try {
|
|
20
|
+
const accounts = unwrapResponseOrThrow(message.value);
|
|
21
|
+
callback(accounts);
|
|
22
|
+
}
|
|
23
|
+
catch {
|
|
24
|
+
console.error('Invalid account response, got', message.value.value);
|
|
25
|
+
}
|
|
26
|
+
});
|
|
27
|
+
},
|
|
28
|
+
},
|
|
29
|
+
signer: {
|
|
30
|
+
signRaw(raw) {
|
|
31
|
+
return transport.request({ tag: 'signRawRequestV1', value: raw }).then(response => {
|
|
32
|
+
if (response.tag === 'signPayloadResponseV1') {
|
|
33
|
+
return unwrapResponseOrThrow(response.value);
|
|
34
|
+
}
|
|
35
|
+
throw new Error(`Invalid response, got ${response.tag} message`);
|
|
36
|
+
});
|
|
37
|
+
},
|
|
38
|
+
signPayload(payload) {
|
|
39
|
+
return transport.request({ tag: 'signPayloadRequestV1', value: payload }).then(response => {
|
|
40
|
+
if (response.tag === 'signPayloadResponseV1') {
|
|
41
|
+
return unwrapResponseOrThrow(response.value);
|
|
42
|
+
}
|
|
43
|
+
throw new Error(`Invalid response, got ${response.tag} message`);
|
|
44
|
+
});
|
|
45
|
+
},
|
|
46
|
+
},
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
injectExtension(enable, { name: SpektrExtensionName, version: Version });
|
|
50
|
+
}
|
|
51
|
+
export async function inject(provider = defaultProvider) {
|
|
52
|
+
const transport = createTransport(provider);
|
|
53
|
+
if (!transport)
|
|
54
|
+
return false;
|
|
55
|
+
const ready = await transport.isSpektrReady();
|
|
56
|
+
if (!ready)
|
|
57
|
+
return false;
|
|
58
|
+
injectPolkadotExtension(transport);
|
|
59
|
+
return true;
|
|
60
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { HexString, Transport, TxPayloadV1 } from '@novasamatech/host-api';
|
|
2
|
+
import type { InjectedAccounts } from '@polkadot/extension-inject/types';
|
|
3
|
+
import type { SignerPayloadJSON, SignerPayloadRaw, SignerResult } from '@polkadot/types/types/extrinsic';
|
|
4
|
+
interface Signer {
|
|
5
|
+
/**
|
|
6
|
+
* @description signs an extrinsic payload from a serialized form
|
|
7
|
+
*/
|
|
8
|
+
signPayload?: (payload: SignerPayloadJSON) => Promise<SignerResult>;
|
|
9
|
+
/**
|
|
10
|
+
* @description signs a raw payload, only the bytes data as supplied
|
|
11
|
+
*/
|
|
12
|
+
signRaw?: (raw: SignerPayloadRaw) => Promise<SignerResult>;
|
|
13
|
+
/**
|
|
14
|
+
* @description signs a transaction according to https://github.com/polkadot-js/api/issues/6213
|
|
15
|
+
*/
|
|
16
|
+
createTransaction?: (payload: TxPayloadV1) => Promise<HexString>;
|
|
17
|
+
}
|
|
18
|
+
interface Injected {
|
|
19
|
+
accounts: InjectedAccounts;
|
|
20
|
+
signer: Signer;
|
|
21
|
+
}
|
|
22
|
+
export declare function createExtensionEnableFactory(transport: Transport): Promise<(() => Promise<Injected>) | null>;
|
|
23
|
+
export declare function injectSpektrExtension(transport?: Transport | null): Promise<boolean>;
|
|
24
|
+
export {};
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { unwrapResponseOrThrow } from '@novasamatech/host-api';
|
|
2
|
+
import { injectExtension } from '@polkadot/extension-inject';
|
|
3
|
+
import { SpektrExtensionName, Version } from './constants';
|
|
4
|
+
import { defaultTransport } from './defaultTransport';
|
|
5
|
+
export async function createExtensionEnableFactory(transport) {
|
|
6
|
+
const ready = await transport.isReady();
|
|
7
|
+
if (!ready)
|
|
8
|
+
return null;
|
|
9
|
+
async function enable() {
|
|
10
|
+
return {
|
|
11
|
+
accounts: {
|
|
12
|
+
get() {
|
|
13
|
+
return transport
|
|
14
|
+
.request({ tag: 'getAccountsRequestV1', value: undefined }, 'getAccountsResponseV1')
|
|
15
|
+
.then(unwrapResponseOrThrow);
|
|
16
|
+
},
|
|
17
|
+
subscribe(callback) {
|
|
18
|
+
const unsubscribe = transport.subscribe('getAccountsResponseV1', (_, payload) => {
|
|
19
|
+
try {
|
|
20
|
+
const accounts = unwrapResponseOrThrow(payload);
|
|
21
|
+
callback(accounts);
|
|
22
|
+
}
|
|
23
|
+
catch {
|
|
24
|
+
transport.provider.logger.error('Failed response on account subscription', payload.value);
|
|
25
|
+
}
|
|
26
|
+
});
|
|
27
|
+
transport.postMessage('_', { tag: 'accountSubscriptionV1', value: undefined });
|
|
28
|
+
return () => {
|
|
29
|
+
transport.postMessage('_', { tag: 'accountUnsubscriptionV1', value: undefined });
|
|
30
|
+
unsubscribe();
|
|
31
|
+
};
|
|
32
|
+
},
|
|
33
|
+
},
|
|
34
|
+
signer: {
|
|
35
|
+
signRaw(raw) {
|
|
36
|
+
return transport
|
|
37
|
+
.request({ tag: 'signRawRequestV1', value: raw }, 'signResponseV1')
|
|
38
|
+
.then(unwrapResponseOrThrow);
|
|
39
|
+
},
|
|
40
|
+
signPayload(payload) {
|
|
41
|
+
return transport
|
|
42
|
+
.request({ tag: 'signPayloadRequestV1', value: payload }, 'signResponseV1')
|
|
43
|
+
.then(unwrapResponseOrThrow);
|
|
44
|
+
},
|
|
45
|
+
createTransaction(payload) {
|
|
46
|
+
return transport
|
|
47
|
+
.request({ tag: 'createTransactionRequestV1', value: payload }, 'createTransactionResponseV1')
|
|
48
|
+
.then(unwrapResponseOrThrow);
|
|
49
|
+
},
|
|
50
|
+
},
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
return enable;
|
|
54
|
+
}
|
|
55
|
+
export async function injectSpektrExtension(transport = defaultTransport) {
|
|
56
|
+
if (!transport)
|
|
57
|
+
return false;
|
|
58
|
+
try {
|
|
59
|
+
const enable = await createExtensionEnableFactory(transport);
|
|
60
|
+
if (enable) {
|
|
61
|
+
injectExtension(enable, { name: SpektrExtensionName, version: Version });
|
|
62
|
+
return true;
|
|
63
|
+
}
|
|
64
|
+
else {
|
|
65
|
+
return false;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
catch (e) {
|
|
69
|
+
transport.provider.logger.error('Error injecting extension', e);
|
|
70
|
+
return false;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { isValidMessage } from '@novasamatech/product-sdk-shared';
|
|
2
|
+
import { createTransport } from '@novasamatech/host-api';
|
|
3
|
+
function getParentWindow() {
|
|
4
|
+
if (window.top) {
|
|
5
|
+
return window.top;
|
|
6
|
+
}
|
|
7
|
+
throw new Error('No parent window found');
|
|
8
|
+
}
|
|
9
|
+
function isIframe() {
|
|
10
|
+
try {
|
|
11
|
+
return window !== window.top;
|
|
12
|
+
}
|
|
13
|
+
catch {
|
|
14
|
+
return false;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
function createDefaultSdkProvider() {
|
|
18
|
+
const subscribers = new Set();
|
|
19
|
+
const handleMessage = (event) => {
|
|
20
|
+
if (!isValidMessage(event, getParentWindow(), window))
|
|
21
|
+
return;
|
|
22
|
+
for (const subscriber of subscribers) {
|
|
23
|
+
subscriber(event.data);
|
|
24
|
+
}
|
|
25
|
+
};
|
|
26
|
+
if (isIframe()) {
|
|
27
|
+
window.addEventListener('message', handleMessage);
|
|
28
|
+
}
|
|
29
|
+
return {
|
|
30
|
+
isCorrectEnvironment() {
|
|
31
|
+
return isIframe();
|
|
32
|
+
},
|
|
33
|
+
postMessage(message) {
|
|
34
|
+
getParentWindow().postMessage(message, '*', [message.buffer]);
|
|
35
|
+
},
|
|
36
|
+
subscribe(callback) {
|
|
37
|
+
subscribers.add(callback);
|
|
38
|
+
return () => {
|
|
39
|
+
subscribers.delete(callback);
|
|
40
|
+
};
|
|
41
|
+
},
|
|
42
|
+
dispose() {
|
|
43
|
+
subscribers.clear();
|
|
44
|
+
if (isIframe()) {
|
|
45
|
+
window.removeEventListener('message', handleMessage);
|
|
46
|
+
}
|
|
47
|
+
},
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
export const defaultProvider = createDefaultSdkProvider();
|
|
51
|
+
export const defaultTransport = createTransport(defaultProvider, { handshakeTimeout: 1_000 });
|
package/dist/types.d.ts
ADDED
package/dist/types.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/dist/utils.d.ts
ADDED
package/dist/utils.js
ADDED
package/package.json
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@novasamatech/product-sdk",
|
|
3
|
+
"type": "module",
|
|
4
|
+
"version": "0.4.0",
|
|
5
|
+
"description": "Polkadot product SDK: integrate and run your product inside Polkadot browser.",
|
|
6
|
+
"license": "Apache-2.0",
|
|
7
|
+
"repository": {
|
|
8
|
+
"type": "git",
|
|
9
|
+
"url": "git+https://github.com/novasamatech/spektr-sdk.git"
|
|
10
|
+
},
|
|
11
|
+
"keywords": [
|
|
12
|
+
"polkadot",
|
|
13
|
+
"spektr",
|
|
14
|
+
"dapp"
|
|
15
|
+
],
|
|
16
|
+
"main": "dist/index.js",
|
|
17
|
+
"exports": {
|
|
18
|
+
"./package.json": "./package.json",
|
|
19
|
+
".": {
|
|
20
|
+
"types": "./dist/index.d.ts",
|
|
21
|
+
"default": "./dist/index.js"
|
|
22
|
+
}
|
|
23
|
+
},
|
|
24
|
+
"files": [
|
|
25
|
+
"dist",
|
|
26
|
+
"README.md"
|
|
27
|
+
],
|
|
28
|
+
"dependencies": {
|
|
29
|
+
"@polkadot/extension-inject": "0.62.5",
|
|
30
|
+
"@polkadot-api/json-rpc-provider": "0.0.4",
|
|
31
|
+
"@polkadot-api/json-rpc-provider-proxy": "0.2.7",
|
|
32
|
+
"@novasamatech/host-api": "0.4.0"
|
|
33
|
+
},
|
|
34
|
+
"publishConfig": {
|
|
35
|
+
"access": "public"
|
|
36
|
+
}
|
|
37
|
+
}
|