windkit 0.1.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/LICENSE +21 -0
- package/README.md +80 -0
- package/index.js +4 -0
- package/package.json +34 -0
- package/src/WalletSession.js +246 -0
- package/src/WindConnector.js +199 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Wind Stack | Vexanium
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
# WindKit
|
|
2
|
+
A protocol to connect Vexanium DApps to the Wind wallet.
|
|
3
|
+
|
|
4
|
+
## Features
|
|
5
|
+
- Cross-device login via VSR (Vexanium Signing Request)
|
|
6
|
+
- Transaction signing (single or multiple actions)
|
|
7
|
+
- Message signing
|
|
8
|
+
- Shared secret (ECDH) for encryption
|
|
9
|
+
|
|
10
|
+
## Initialize the Connector
|
|
11
|
+
```javascript
|
|
12
|
+
import { WindConnector } from "windkit";
|
|
13
|
+
|
|
14
|
+
const connector = new WindConnector();
|
|
15
|
+
connector.on("session", onSession); // fires after wallet approves login
|
|
16
|
+
await connector.connect();
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## Create a Login Request and Open the Wallet
|
|
20
|
+
```javascript
|
|
21
|
+
// Build a VSR and deep-link / QR it to the wallet
|
|
22
|
+
const vsr = connector.createLoginRequest("Vexanium DApp", "https://example.com/icon.png");
|
|
23
|
+
const request = vsr.split(":")[1];
|
|
24
|
+
|
|
25
|
+
// Open Wind Wallet login (adjust the URL/host as needed)
|
|
26
|
+
const walletUrl = `https://windwallet.app/login?vsr=${request}`;
|
|
27
|
+
window.open(walletUrl, "Wind Wallet");
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
## Receive a WalletSession
|
|
31
|
+
```javascript
|
|
32
|
+
import { ABICache } from "@wharfkit/abicache";
|
|
33
|
+
|
|
34
|
+
const abiCache = new ABICache({/* optional custom fetch */});
|
|
35
|
+
|
|
36
|
+
function onSession(session, proof) {
|
|
37
|
+
// Optional on first login: verify IdentityProof as needed
|
|
38
|
+
const account = proof?.signer?.toString?.(); // e.g., "userxyz@active"
|
|
39
|
+
|
|
40
|
+
session.setABICache(abiCache); // faster ABI (de)serialization
|
|
41
|
+
session.onClose(() => console.log("Disconnected from wallet"));
|
|
42
|
+
|
|
43
|
+
// Store for later use
|
|
44
|
+
Store.session = session;
|
|
45
|
+
}
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
## Send a Transaction
|
|
49
|
+
```javascript
|
|
50
|
+
import { Action } from "@wharfkit/antelope";
|
|
51
|
+
|
|
52
|
+
// Example: transfer VEX
|
|
53
|
+
const abi = await abiCache.getAbi("vex.token");
|
|
54
|
+
const data = {
|
|
55
|
+
from: "aiueo",
|
|
56
|
+
to: "babibu",
|
|
57
|
+
quantity: "1.0000 VEX",
|
|
58
|
+
memo: "test transfer"
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
const action = Action.from(
|
|
62
|
+
{
|
|
63
|
+
account: "vex.token",
|
|
64
|
+
name: "transfer",
|
|
65
|
+
data,
|
|
66
|
+
authorization: [Store.session.permissionLevel]
|
|
67
|
+
},
|
|
68
|
+
abi
|
|
69
|
+
);
|
|
70
|
+
|
|
71
|
+
// By default, transact() broadcasts. Set { broadcast: false } to sign only.
|
|
72
|
+
const result = await Store.session.transact({ action });
|
|
73
|
+
// result is either SendTransactionResponse (broadcast) or SignedTransaction (sign-only)
|
|
74
|
+
console.log(result.transaction_id ?? result.id);
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
> Notes
|
|
78
|
+
> - Class names: `WindConnector` (connector) and `WalletSession` (active session).
|
|
79
|
+
> - Chain ID is handled internally by `WalletSession.ChainID` (Vexanium mainnet).
|
|
80
|
+
> - For re-login, the `session` event may be called without `proof` (use `session.permissionLevel`).
|
package/index.js
ADDED
package/package.json
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "windkit",
|
|
3
|
+
"version": "0.1.1",
|
|
4
|
+
"description": "A protocol for connecting Vexanium DApps to the Wind wallet, enabling secure communication and transaction signing.",
|
|
5
|
+
"repository": {
|
|
6
|
+
"type": "git",
|
|
7
|
+
"url": "git+https://github.com/windvex/windkit.git"
|
|
8
|
+
},
|
|
9
|
+
"main": "index.js",
|
|
10
|
+
"scripts": {
|
|
11
|
+
"test": "echo \"Error: no test specified\" && exit 1"
|
|
12
|
+
},
|
|
13
|
+
"keywords": [
|
|
14
|
+
"cryptocurrency",
|
|
15
|
+
"web3",
|
|
16
|
+
"dapp",
|
|
17
|
+
"walletconnect",
|
|
18
|
+
"windkit",
|
|
19
|
+
"web-rtc",
|
|
20
|
+
"peerjs",
|
|
21
|
+
"vexanium",
|
|
22
|
+
"vex",
|
|
23
|
+
"blockchain"
|
|
24
|
+
],
|
|
25
|
+
"license": "MIT",
|
|
26
|
+
"type": "module",
|
|
27
|
+
"dependencies": {
|
|
28
|
+
"@wharfkit/abicache": "^1.2.2",
|
|
29
|
+
"@wharfkit/antelope": "^1.0.13",
|
|
30
|
+
"@wharfkit/signing-request": "^3.2.0",
|
|
31
|
+
"pako": "^2.1.0",
|
|
32
|
+
"peerjs": "^1.5.4"
|
|
33
|
+
}
|
|
34
|
+
}
|
|
@@ -0,0 +1,246 @@
|
|
|
1
|
+
import { SigningRequest } from "@wharfkit/signing-request";
|
|
2
|
+
import {
|
|
3
|
+
Action,
|
|
4
|
+
Checksum512,
|
|
5
|
+
Name,
|
|
6
|
+
PermissionLevel,
|
|
7
|
+
PublicKey,
|
|
8
|
+
Signature,
|
|
9
|
+
SignedTransaction,
|
|
10
|
+
Transaction
|
|
11
|
+
} from "@wharfkit/antelope";
|
|
12
|
+
import { ABICache } from "@wharfkit/abicache";
|
|
13
|
+
import zlib from "pako";
|
|
14
|
+
|
|
15
|
+
export class WalletSession {
|
|
16
|
+
// Vexanium mainnet Chain ID (constant)
|
|
17
|
+
static ChainID = "f9f432b1851b5c179d2091a96f593aaed50ec7466b74f89301f957a83e56ce1f";
|
|
18
|
+
|
|
19
|
+
#connection;
|
|
20
|
+
#callbacks;
|
|
21
|
+
#encodingOptions;
|
|
22
|
+
/**
|
|
23
|
+
* @type {PermissionLevel}
|
|
24
|
+
*/
|
|
25
|
+
#permissionLevel;
|
|
26
|
+
#closeListener;
|
|
27
|
+
#errorListener;
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Wraps a DataConnection to communicate with the wallet.
|
|
31
|
+
* @param {import('peerjs').DataConnection} connection
|
|
32
|
+
*/
|
|
33
|
+
constructor(connection) {
|
|
34
|
+
this.#connection = connection;
|
|
35
|
+
this.#callbacks = new Map();
|
|
36
|
+
|
|
37
|
+
connection.on("data", this.#onDataReceived.bind(this));
|
|
38
|
+
connection.on("close", () => {
|
|
39
|
+
if (this.#closeListener) this.#closeListener();
|
|
40
|
+
});
|
|
41
|
+
connection.on("error", (error) => {
|
|
42
|
+
if (this.#errorListener) this.#errorListener(error);
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Sets the ABI cache to use for request (de)serialization.
|
|
48
|
+
* @param {ABICache} cache
|
|
49
|
+
*/
|
|
50
|
+
setABICache(cache) {
|
|
51
|
+
this.#encodingOptions = { zlib, abiProvider: cache };
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Registers a listener invoked when the wallet connection closes.
|
|
56
|
+
* @param {() => void} listener
|
|
57
|
+
*/
|
|
58
|
+
onClose(listener) {
|
|
59
|
+
this.#closeListener = listener;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Registers a listener invoked when a connection error occurs.
|
|
64
|
+
* @param {(err: unknown) => void} listener
|
|
65
|
+
*/
|
|
66
|
+
onError(listener) {
|
|
67
|
+
this.#errorListener = listener;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Indicates whether the wallet connection is still open.
|
|
72
|
+
* @return {boolean}
|
|
73
|
+
*/
|
|
74
|
+
isOpen() {
|
|
75
|
+
return this.#connection.open;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Closes the connection to the wallet.
|
|
80
|
+
*/
|
|
81
|
+
close() {
|
|
82
|
+
this.#connection.close();
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* The active permission level granted by the wallet.
|
|
87
|
+
* @return {PermissionLevel}
|
|
88
|
+
*/
|
|
89
|
+
get permissionLevel() {
|
|
90
|
+
return this.#permissionLevel;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* @param {PermissionLevel} value
|
|
95
|
+
*/
|
|
96
|
+
set permissionLevel(value) {
|
|
97
|
+
this.#permissionLevel = value;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Convenience accessor for the actor name.
|
|
102
|
+
* @return {Name}
|
|
103
|
+
*/
|
|
104
|
+
get actor() {
|
|
105
|
+
return this.#permissionLevel.actor;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Convenience accessor for the permission name.
|
|
110
|
+
* @return {Name}
|
|
111
|
+
*/
|
|
112
|
+
get permission() {
|
|
113
|
+
return this.#permissionLevel.permission;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* @typedef TransactArguments
|
|
118
|
+
* @property {Action} [action] Single-action transaction
|
|
119
|
+
* @property {Action[]} [actions] Multi-action transaction
|
|
120
|
+
* @property {Transaction} [transaction] Fully-formed transaction
|
|
121
|
+
*/
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* @typedef TransactOptions
|
|
125
|
+
* @property {boolean} [broadcast=true] If true, broadcast on-chain; otherwise sign only.
|
|
126
|
+
*/
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Builds and sends a transaction request to the wallet.
|
|
130
|
+
* @param {TransactArguments} args
|
|
131
|
+
* @param {TransactOptions} [options]
|
|
132
|
+
* @return {Promise<SignedTransaction|import('@wharfkit/antelope').SendTransactionResponse>}
|
|
133
|
+
*/
|
|
134
|
+
async transact(args, options) {
|
|
135
|
+
args.chainId = WalletSession.ChainID;
|
|
136
|
+
const willBroadcast =
|
|
137
|
+
options && typeof options.broadcast !== "undefined"
|
|
138
|
+
? options.broadcast
|
|
139
|
+
: true;
|
|
140
|
+
|
|
141
|
+
const request = await SigningRequest.create(args, this.#encodingOptions);
|
|
142
|
+
request.setBroadcast(willBroadcast);
|
|
143
|
+
|
|
144
|
+
const vsr = request.encode(true, false, "vsr:");
|
|
145
|
+
return this.signingRequest(vsr);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* Sends a VSR (Vexanium Signing Request) to the wallet.
|
|
150
|
+
* The wallet may broadcast the transaction immediately or return a signature only.
|
|
151
|
+
* @param {string} vsr
|
|
152
|
+
* @return {Promise<import('@wharfkit/antelope').SendTransactionResponse|SignedTransaction>}
|
|
153
|
+
*/
|
|
154
|
+
signingRequest(vsr) {
|
|
155
|
+
const callback = window.crypto.randomUUID();
|
|
156
|
+
const data = {
|
|
157
|
+
method: "signingRequest",
|
|
158
|
+
id: callback,
|
|
159
|
+
params: { vsr }
|
|
160
|
+
};
|
|
161
|
+
|
|
162
|
+
return new Promise((resolve, reject) => {
|
|
163
|
+
const func = (reply) => {
|
|
164
|
+
if (reply.code === "SENT") {
|
|
165
|
+
resolve(reply.result);
|
|
166
|
+
} else if (reply.code === "SIGNED") {
|
|
167
|
+
resolve(SignedTransaction.from(reply.result));
|
|
168
|
+
} else {
|
|
169
|
+
// ERROR | REJECT
|
|
170
|
+
if (typeof reply.error === "string") {
|
|
171
|
+
reject(new Error(reply.error));
|
|
172
|
+
} else {
|
|
173
|
+
reject(reply.error);
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
};
|
|
177
|
+
this.#callbacks.set(callback, func);
|
|
178
|
+
this.#connection.send(data);
|
|
179
|
+
});
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
/**
|
|
183
|
+
* Requests a signature for an arbitrary message.
|
|
184
|
+
* @param {string} message The message to be signed
|
|
185
|
+
* @return {Promise<Signature>}
|
|
186
|
+
*/
|
|
187
|
+
signMessage(message) {
|
|
188
|
+
const callback = window.crypto.randomUUID();
|
|
189
|
+
const data = {
|
|
190
|
+
method: "signMessage",
|
|
191
|
+
id: callback,
|
|
192
|
+
params: { message }
|
|
193
|
+
};
|
|
194
|
+
|
|
195
|
+
return new Promise((resolve, reject) => {
|
|
196
|
+
const func = (reply) => {
|
|
197
|
+
if (reply.code === "SIGNED") {
|
|
198
|
+
resolve(Signature.from(reply.result.signature));
|
|
199
|
+
} else {
|
|
200
|
+
reject(new Error(reply.error.message));
|
|
201
|
+
}
|
|
202
|
+
};
|
|
203
|
+
this.#callbacks.set(callback, func);
|
|
204
|
+
this.#connection.send(data);
|
|
205
|
+
});
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* Derives a shared secret using ECDH with the wallet's key.
|
|
210
|
+
* @param {PublicKey} publicKey
|
|
211
|
+
* @return {Promise<Checksum512>}
|
|
212
|
+
*/
|
|
213
|
+
sharedSecret(publicKey) {
|
|
214
|
+
const callback = window.crypto.randomUUID();
|
|
215
|
+
const data = {
|
|
216
|
+
method: "sharedSecret",
|
|
217
|
+
id: callback,
|
|
218
|
+
params: { key: publicKey.toString() }
|
|
219
|
+
};
|
|
220
|
+
|
|
221
|
+
return new Promise((resolve, reject) => {
|
|
222
|
+
const func = (reply) => {
|
|
223
|
+
if (reply.code === "CREATED") {
|
|
224
|
+
resolve(Checksum512.from(reply.result.secret));
|
|
225
|
+
} else if (reply.code === "ERROR") {
|
|
226
|
+
reject(new Error(reply.error));
|
|
227
|
+
}
|
|
228
|
+
};
|
|
229
|
+
this.#callbacks.set(callback, func);
|
|
230
|
+
this.#connection.send(data);
|
|
231
|
+
});
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
/**
|
|
235
|
+
* Handles incoming data from the wallet and resolves the pending callback.
|
|
236
|
+
* @param {{ id: string }} data
|
|
237
|
+
* @private
|
|
238
|
+
*/
|
|
239
|
+
#onDataReceived(data) {
|
|
240
|
+
const callback = this.#callbacks.get(data.id);
|
|
241
|
+
if (callback) {
|
|
242
|
+
callback(data);
|
|
243
|
+
this.#callbacks.delete(data.id);
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
}
|
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
import { Base64u, IdentityProof, SigningRequest } from "@wharfkit/signing-request";
|
|
2
|
+
import { Int64, PermissionLevel, Serializer } from "@wharfkit/antelope";
|
|
3
|
+
import { Peer } from "peerjs";
|
|
4
|
+
import zlib from "pako";
|
|
5
|
+
import { WalletSession } from "./WalletSession.js";
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Handles communication with the signaling server
|
|
9
|
+
* and manages secure DApp ↔ Wallet sessions.
|
|
10
|
+
*/
|
|
11
|
+
export class WindConnector {
|
|
12
|
+
#peer;
|
|
13
|
+
#peerOptions = {};
|
|
14
|
+
#peerId;
|
|
15
|
+
#listeners = new Map();
|
|
16
|
+
#session = new Map();
|
|
17
|
+
#identityArgs = {};
|
|
18
|
+
|
|
19
|
+
constructor() {
|
|
20
|
+
this.#peerOptions.config = {
|
|
21
|
+
iceServers: [
|
|
22
|
+
{ urls: "stun:stun.l.google.com:19302" },
|
|
23
|
+
{ urls: "stun:stun1.l.google.com:3478" },
|
|
24
|
+
{ urls: "stun:stun.relay.metered.ca:80" },
|
|
25
|
+
{
|
|
26
|
+
urls: "turn:asia.relay.metered.ca:80",
|
|
27
|
+
username: "b66cd40a117bddb5cde924ab",
|
|
28
|
+
credential: "4jRmuTehVCZ2a/S+"
|
|
29
|
+
}
|
|
30
|
+
],
|
|
31
|
+
sdpSemantics: "unified-plan"
|
|
32
|
+
};
|
|
33
|
+
this.#identityArgs.scope = "vexanium";
|
|
34
|
+
this.#identityArgs.chainId = WalletSession.ChainID;
|
|
35
|
+
this.#loadSession();
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Adds an additional STUN or TURN server to the peer connection.
|
|
40
|
+
* @param {RTCIceServer} server
|
|
41
|
+
*/
|
|
42
|
+
addIceServer(server) {
|
|
43
|
+
this.#peerOptions.config.iceServers.push(server);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Sets the signaling server address and port.
|
|
48
|
+
* @param {string} host
|
|
49
|
+
* @param {number} [port]
|
|
50
|
+
*/
|
|
51
|
+
setServer(host, port) {
|
|
52
|
+
this.#peerOptions.host = host;
|
|
53
|
+
if (port) this.#peerOptions.port = port;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Registers a listener for specific peer events.
|
|
58
|
+
* Supported events: `open`, `close`, `disconnected`, `error`, `session`
|
|
59
|
+
* @param {string} event
|
|
60
|
+
* @param {Function} func
|
|
61
|
+
*/
|
|
62
|
+
on(event, func) {
|
|
63
|
+
this.#listeners.set(event, func);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Connects to the signaling server.
|
|
68
|
+
* The wallet's response can be captured using the `session` event listener.
|
|
69
|
+
*
|
|
70
|
+
* @see on
|
|
71
|
+
* @see createLoginRequest
|
|
72
|
+
*/
|
|
73
|
+
async connect() {
|
|
74
|
+
if (!this.#peerId) throw new Error("Peer ID is not set");
|
|
75
|
+
this.#peer = new Peer(this.#peerId, this.#peerOptions);
|
|
76
|
+
this.#peer.on("connection", this.#onConnection.bind(this));
|
|
77
|
+
this.#listeners.forEach((func, key) => {
|
|
78
|
+
this.#peer.on(key, func);
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
disconnect() {
|
|
83
|
+
this.#peer.disconnect();
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
destroy() {
|
|
87
|
+
this.#peer.destroy();
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
reconnect() {
|
|
91
|
+
this.#peer.reconnect();
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
isDisconnected() {
|
|
95
|
+
return this.#peer.disconnected;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
isDestroyed() {
|
|
99
|
+
return this.#peer.destroyed;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Creates a VSR (Vexanium Signing Request) for login.
|
|
104
|
+
* @param {string} name - Application name
|
|
105
|
+
* @param {string} icon - Application icon URL
|
|
106
|
+
* @returns {string} Encoded VSR string for use in QR codes or query URLs
|
|
107
|
+
*/
|
|
108
|
+
createLoginRequest(name, icon) {
|
|
109
|
+
const session = this.#getLastSession();
|
|
110
|
+
if (session) {
|
|
111
|
+
const [actor, perm] = session.permission.split("@");
|
|
112
|
+
this.#identityArgs.account = actor;
|
|
113
|
+
this.#identityArgs.permission = perm;
|
|
114
|
+
this.#peerId = session.peerId;
|
|
115
|
+
} else {
|
|
116
|
+
// Generate a new peer ID
|
|
117
|
+
this.#peerId = `VEX-${window.crypto.randomUUID()}`;
|
|
118
|
+
}
|
|
119
|
+
let req = SigningRequest.identity(this.#identityArgs, { zlib });
|
|
120
|
+
req.setInfoKey("pi", this.#peerId);
|
|
121
|
+
req.setInfoKey("na", name);
|
|
122
|
+
req.setInfoKey("ic", icon);
|
|
123
|
+
req.setInfoKey("do", window.location.origin);
|
|
124
|
+
if (session) {
|
|
125
|
+
req.setInfoKey("exp", Int64.from(session.exp));
|
|
126
|
+
req.setInfoKey("sig", session.signature);
|
|
127
|
+
}
|
|
128
|
+
return req.encode(true, false, "vsr:");
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
#getLastSession() {
|
|
132
|
+
const domain = window.location.origin;
|
|
133
|
+
let current = this.#session.get(domain);
|
|
134
|
+
if (current && current.exp < Date.now()) { // Expired
|
|
135
|
+
this.#session.delete(domain);
|
|
136
|
+
current = null;
|
|
137
|
+
}
|
|
138
|
+
return current;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
#addSession(permission, exp, signature) {
|
|
142
|
+
const domain = window.location.origin;
|
|
143
|
+
let current = this.#session.get(domain);
|
|
144
|
+
if (current) {
|
|
145
|
+
current.permission = permission;
|
|
146
|
+
current.exp = exp;
|
|
147
|
+
current.signature = signature;
|
|
148
|
+
} else {
|
|
149
|
+
current = {
|
|
150
|
+
permission, exp, signature, domain, peerId: this.#peerId
|
|
151
|
+
};
|
|
152
|
+
}
|
|
153
|
+
this.#session.set(domain, current);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
#saveSession() {
|
|
157
|
+
const data = Array.from(this.#session.values());
|
|
158
|
+
sessionStorage.setItem("session", JSON.stringify(data));
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
#loadSession() {
|
|
162
|
+
const raw = sessionStorage.getItem("session");
|
|
163
|
+
if (raw) {
|
|
164
|
+
let data = JSON.parse(raw);
|
|
165
|
+
data.forEach(it => {
|
|
166
|
+
this.#session.set(it.domain, it);
|
|
167
|
+
});
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* Handles incoming peer connections from the wallet.
|
|
173
|
+
* @param {DataConnection} conn
|
|
174
|
+
* @private
|
|
175
|
+
*/
|
|
176
|
+
#onConnection(conn) {
|
|
177
|
+
conn.once("data", payload => {
|
|
178
|
+
if (payload.code === 'LOGIN_OK') {
|
|
179
|
+
const auth = Base64u.decode(payload.result.auth);
|
|
180
|
+
const proof = Serializer.decode({ data: auth, type: IdentityProof });
|
|
181
|
+
const session = new WalletSession(conn);
|
|
182
|
+
session.permissionLevel = proof.signer;
|
|
183
|
+
|
|
184
|
+
// Store session
|
|
185
|
+
this.#addSession(proof.signer.toString(), payload.result.exp, payload.result.signature);
|
|
186
|
+
this.#saveSession();
|
|
187
|
+
|
|
188
|
+
const func = this.#listeners.get("session");
|
|
189
|
+
if (func) func(session, proof);
|
|
190
|
+
} else if (payload.code === 'RE_LOGIN_OK') {
|
|
191
|
+
const session = new WalletSession(conn);
|
|
192
|
+
session.permissionLevel = PermissionLevel.from(payload.result.permission);
|
|
193
|
+
|
|
194
|
+
const func = this.#listeners.get("session");
|
|
195
|
+
if (func) func(session); // Only one parameter for re-login
|
|
196
|
+
}
|
|
197
|
+
});
|
|
198
|
+
}
|
|
199
|
+
}
|