unwallet-client-sdk 0.15.0 → 0.16.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/dist/index.d.mts CHANGED
@@ -1,20 +1,48 @@
1
1
  declare const VALID_ENVS: readonly ["prod", "dev"];
2
-
3
2
  type Env = (typeof VALID_ENVS)[number];
4
3
  interface Config {
5
- clientID: string;
6
4
  env?: Env;
7
- }
8
- interface UnWalletConfig {
9
- frontend: UnWalletFrontendConfig;
10
- xapi: UnWalletXAPIConfig;
11
- }
12
- interface UnWalletFrontendConfig {
13
- baseURL: string;
5
+ clientID: string;
14
6
  }
15
7
  interface UnWalletXAPIConfig {
16
8
  url: string;
9
+ connectionTimeout: number;
10
+ }
11
+
12
+ declare const VALID_UW_ERROR_CODES: readonly ["CONNECTION_TIMEOUT", "CONNECTION_FAILED", "CONNECTION_NOT_OPENED", "CONNECTION_CLOSED", "INVALID_REQUEST", "REQUEST_IN_PROGRESS", "REQUEST_REJECTED", "INVALID_RESPONSE"];
13
+ type UWErrorCode = (typeof VALID_UW_ERROR_CODES)[number];
14
+ declare class UWError extends Error {
15
+ readonly code: UWErrorCode;
16
+ constructor(code: UWErrorCode, message?: string);
17
17
  }
18
+
19
+ declare const VALID_X_RESPONSE_TYPES: readonly ["connectionID", "signature", "transactionID", "error"];
20
+ type XResponseType = (typeof VALID_X_RESPONSE_TYPES)[number];
21
+ interface XResponse {
22
+ readonly type: XResponseType;
23
+ readonly value: string;
24
+ }
25
+ interface XResponseHandler {
26
+ resolve: (resp: XResponse) => void;
27
+ reject: (err: UWError) => void;
28
+ }
29
+ declare class XConnection {
30
+ private socket;
31
+ private responseHandler;
32
+ readonly id: string;
33
+ constructor(args: {
34
+ socket: WebSocket;
35
+ id: string;
36
+ });
37
+ private initListeners;
38
+ get readyState(): number;
39
+ get hasResponseHandler(): boolean;
40
+ setResponseHandler(handler: XResponseHandler | null): void;
41
+ static init(config: UnWalletXAPIConfig): Promise<XConnection>;
42
+ }
43
+
44
+ declare const VALID_AUTHORIZATION_RESPONSE_MODES: readonly ["fragment", "form_post"];
45
+ type AuthorizationResponseMode = (typeof VALID_AUTHORIZATION_RESPONSE_MODES)[number];
18
46
  interface SignResult {
19
47
  digest: string;
20
48
  signature: string;
@@ -22,19 +50,19 @@ interface SignResult {
22
50
  interface SendTransactionResult {
23
51
  transactionID: string;
24
52
  }
25
-
26
53
  declare class UnWallet {
27
- private config;
28
- private unWalletConfig;
29
- private ws;
30
- private connectionID;
31
- private resolve;
32
- private reject;
33
- constructor(config: Config, unWalletConfig: UnWalletConfig, ws: WebSocket);
34
- private initPromiseArgs;
54
+ private env;
55
+ private clientID;
56
+ private xConnection;
57
+ constructor(args: {
58
+ env: Env;
59
+ clientID: string;
60
+ xConnection: XConnection;
61
+ });
35
62
  static init(config: Config): Promise<UnWallet>;
63
+ private get unWalletConfig();
36
64
  authorize(args: {
37
- responseMode?: string;
65
+ responseMode?: AuthorizationResponseMode;
38
66
  redirectURL: string;
39
67
  nonce?: string;
40
68
  isVirtual?: boolean;
@@ -51,10 +79,6 @@ declare class UnWallet {
51
79
  data?: string;
52
80
  ticketToken: string;
53
81
  }): Promise<SendTransactionResult>;
54
- private getConnectionID;
55
- private sendWSMessage;
56
- private handleWSMessage;
57
- private openWindow;
58
82
  }
59
83
 
60
- export { type Config, type SendTransactionResult, type SignResult, UnWallet };
84
+ export { type AuthorizationResponseMode, type Config, type Env, type SendTransactionResult, type SignResult, UWError, type UWErrorCode, UnWallet };
package/dist/index.d.ts CHANGED
@@ -1,20 +1,48 @@
1
1
  declare const VALID_ENVS: readonly ["prod", "dev"];
2
-
3
2
  type Env = (typeof VALID_ENVS)[number];
4
3
  interface Config {
5
- clientID: string;
6
4
  env?: Env;
7
- }
8
- interface UnWalletConfig {
9
- frontend: UnWalletFrontendConfig;
10
- xapi: UnWalletXAPIConfig;
11
- }
12
- interface UnWalletFrontendConfig {
13
- baseURL: string;
5
+ clientID: string;
14
6
  }
15
7
  interface UnWalletXAPIConfig {
16
8
  url: string;
9
+ connectionTimeout: number;
10
+ }
11
+
12
+ declare const VALID_UW_ERROR_CODES: readonly ["CONNECTION_TIMEOUT", "CONNECTION_FAILED", "CONNECTION_NOT_OPENED", "CONNECTION_CLOSED", "INVALID_REQUEST", "REQUEST_IN_PROGRESS", "REQUEST_REJECTED", "INVALID_RESPONSE"];
13
+ type UWErrorCode = (typeof VALID_UW_ERROR_CODES)[number];
14
+ declare class UWError extends Error {
15
+ readonly code: UWErrorCode;
16
+ constructor(code: UWErrorCode, message?: string);
17
17
  }
18
+
19
+ declare const VALID_X_RESPONSE_TYPES: readonly ["connectionID", "signature", "transactionID", "error"];
20
+ type XResponseType = (typeof VALID_X_RESPONSE_TYPES)[number];
21
+ interface XResponse {
22
+ readonly type: XResponseType;
23
+ readonly value: string;
24
+ }
25
+ interface XResponseHandler {
26
+ resolve: (resp: XResponse) => void;
27
+ reject: (err: UWError) => void;
28
+ }
29
+ declare class XConnection {
30
+ private socket;
31
+ private responseHandler;
32
+ readonly id: string;
33
+ constructor(args: {
34
+ socket: WebSocket;
35
+ id: string;
36
+ });
37
+ private initListeners;
38
+ get readyState(): number;
39
+ get hasResponseHandler(): boolean;
40
+ setResponseHandler(handler: XResponseHandler | null): void;
41
+ static init(config: UnWalletXAPIConfig): Promise<XConnection>;
42
+ }
43
+
44
+ declare const VALID_AUTHORIZATION_RESPONSE_MODES: readonly ["fragment", "form_post"];
45
+ type AuthorizationResponseMode = (typeof VALID_AUTHORIZATION_RESPONSE_MODES)[number];
18
46
  interface SignResult {
19
47
  digest: string;
20
48
  signature: string;
@@ -22,19 +50,19 @@ interface SignResult {
22
50
  interface SendTransactionResult {
23
51
  transactionID: string;
24
52
  }
25
-
26
53
  declare class UnWallet {
27
- private config;
28
- private unWalletConfig;
29
- private ws;
30
- private connectionID;
31
- private resolve;
32
- private reject;
33
- constructor(config: Config, unWalletConfig: UnWalletConfig, ws: WebSocket);
34
- private initPromiseArgs;
54
+ private env;
55
+ private clientID;
56
+ private xConnection;
57
+ constructor(args: {
58
+ env: Env;
59
+ clientID: string;
60
+ xConnection: XConnection;
61
+ });
35
62
  static init(config: Config): Promise<UnWallet>;
63
+ private get unWalletConfig();
36
64
  authorize(args: {
37
- responseMode?: string;
65
+ responseMode?: AuthorizationResponseMode;
38
66
  redirectURL: string;
39
67
  nonce?: string;
40
68
  isVirtual?: boolean;
@@ -51,10 +79,6 @@ declare class UnWallet {
51
79
  data?: string;
52
80
  ticketToken: string;
53
81
  }): Promise<SendTransactionResult>;
54
- private getConnectionID;
55
- private sendWSMessage;
56
- private handleWSMessage;
57
- private openWindow;
58
82
  }
59
83
 
60
- export { type Config, type SendTransactionResult, type SignResult, UnWallet };
84
+ export { type AuthorizationResponseMode, type Config, type Env, type SendTransactionResult, type SignResult, UWError, type UWErrorCode, UnWallet };
package/dist/index.js CHANGED
@@ -20,20 +20,32 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
20
20
  // src/index.ts
21
21
  var index_exports = {};
22
22
  __export(index_exports, {
23
+ UWError: () => UWError,
23
24
  UnWallet: () => UnWallet
24
25
  });
25
26
  module.exports = __toCommonJS(index_exports);
26
27
 
28
+ // src/error.ts
29
+ var _UWError = class _UWError extends Error {
30
+ constructor(code, message) {
31
+ super(message !== void 0 ? `${code}: ${message}` : code);
32
+ this.code = code;
33
+ }
34
+ };
35
+ _UWError.prototype.name = "UWError";
36
+ var UWError = _UWError;
37
+
27
38
  // src/unwallet.ts
28
39
  var import_ethers = require("ethers");
29
40
 
30
- // src/consts.ts
41
+ // src/config.ts
31
42
  var UNWALLET_CONFIG_PROD = {
32
43
  frontend: {
33
44
  baseURL: "https://id.unwallet.world"
34
45
  },
35
46
  xapi: {
36
- url: "wss://xapi.id.unwallet.world"
47
+ url: "wss://xapi.id.unwallet.world",
48
+ connectionTimeout: 1e4
37
49
  }
38
50
  };
39
51
  var UNWALLET_CONFIG_DEV = {
@@ -41,75 +53,196 @@ var UNWALLET_CONFIG_DEV = {
41
53
  baseURL: "http://localhost:4200"
42
54
  },
43
55
  xapi: {
44
- url: "wss://xapi.id.test.unwallet.dev"
56
+ url: "wss://xapi.id.test.unwallet.dev",
57
+ connectionTimeout: 1e4
45
58
  }
46
59
  };
60
+ function getUnWalletConfigByEnv(env) {
61
+ switch (env) {
62
+ case "prod":
63
+ return UNWALLET_CONFIG_PROD;
64
+ case "dev":
65
+ return UNWALLET_CONFIG_DEV;
66
+ }
67
+ }
47
68
 
48
- // src/unwallet.ts
49
- var UnWallet = class _UnWallet {
50
- constructor(config, unWalletConfig, ws) {
51
- this.resolve = null;
52
- this.reject = null;
53
- this.config = config;
54
- this.unWalletConfig = unWalletConfig;
55
- this.ws = ws;
56
- this.connectionID = "";
57
- this.initPromiseArgs();
58
- }
59
- initPromiseArgs() {
60
- this.resolve = () => {
61
- };
62
- this.reject = () => {
63
- };
69
+ // src/x.ts
70
+ var import_zod = require("zod");
71
+ var VALID_X_RESPONSE_TYPES = [
72
+ "connectionID",
73
+ "signature",
74
+ "transactionID",
75
+ "error"
76
+ ];
77
+ var XConnection = class _XConnection {
78
+ constructor(args) {
79
+ this.responseHandler = null;
80
+ this.socket = args.socket;
81
+ this.id = args.id;
82
+ this.initListeners();
64
83
  }
65
- static init(config) {
66
- return new Promise((resolve, reject) => {
67
- if (config.env === void 0) {
68
- config.env = "prod";
84
+ initListeners() {
85
+ this.socket.onopen = null;
86
+ this.socket.onmessage = (event) => {
87
+ if (this.responseHandler === null) {
88
+ return;
69
89
  }
70
- let unWalletConfig;
90
+ let resp;
71
91
  {
72
- switch (config.env) {
73
- case "prod":
74
- unWalletConfig = UNWALLET_CONFIG_PROD;
75
- break;
76
- case "dev":
77
- unWalletConfig = UNWALLET_CONFIG_DEV;
78
- break;
92
+ const result = safeParseMessageEventDataToXResponse(event.data);
93
+ if (!result.success) {
94
+ this.responseHandler.reject(result.error);
95
+ return;
79
96
  }
97
+ resp = result.data;
80
98
  }
81
- const ws = new WebSocket(unWalletConfig.xapi.url);
82
- {
83
- ws.onerror = () => {
84
- reject("websocket connection failed");
85
- };
86
- ws.onopen = () => {
87
- unWallet.getConnectionID();
88
- };
89
- ws.onmessage = (event) => {
90
- const msg = JSON.parse(event.data);
91
- if (msg.type === "connectionID") {
92
- unWallet.connectionID = msg.value;
93
- resolve(unWallet);
99
+ switch (resp.type) {
100
+ case "error":
101
+ switch (resp.value) {
102
+ case "rejected":
103
+ this.responseHandler.reject(new UWError("REQUEST_REJECTED"));
104
+ break;
105
+ default:
106
+ this.responseHandler.reject(
107
+ new UWError(
108
+ "INVALID_RESPONSE",
109
+ `unexpected error response value: ${resp.value}`
110
+ )
111
+ );
112
+ }
113
+ break;
114
+ default:
115
+ this.responseHandler.resolve(resp);
116
+ }
117
+ };
118
+ this.socket.onerror = null;
119
+ this.socket.onclose = (event) => {
120
+ if (this.responseHandler === null) {
121
+ return;
122
+ }
123
+ this.responseHandler.reject(
124
+ new UWError("CONNECTION_CLOSED", event.reason)
125
+ );
126
+ };
127
+ }
128
+ get readyState() {
129
+ return this.socket.readyState;
130
+ }
131
+ get hasResponseHandler() {
132
+ return this.responseHandler !== null;
133
+ }
134
+ setResponseHandler(handler) {
135
+ this.responseHandler = handler;
136
+ }
137
+ static async init(config) {
138
+ const socket = new WebSocket(config.url);
139
+ const id = await new Promise((resolve, reject) => {
140
+ setInterval(
141
+ () => reject(new UWError("CONNECTION_TIMEOUT")),
142
+ config.connectionTimeout
143
+ );
144
+ socket.onopen = () => socket.send(JSON.stringify({ action: "getConnectionID" }));
145
+ socket.onmessage = (event) => {
146
+ let resp;
147
+ {
148
+ const result = safeParseMessageEventDataToXResponse(event.data);
149
+ if (!result.success) {
150
+ reject(result.error);
94
151
  return;
95
152
  }
96
- unWallet.handleWSMessage(msg);
97
- };
153
+ resp = result.data;
154
+ }
155
+ if (resp.type !== "connectionID") {
156
+ reject(
157
+ new UWError(
158
+ "INVALID_RESPONSE",
159
+ `unexpected response type: ${resp.type}`
160
+ )
161
+ );
162
+ return;
163
+ }
164
+ resolve(resp.value);
165
+ };
166
+ socket.onerror = () => reject(new UWError("CONNECTION_FAILED"));
167
+ socket.onclose = (event) => reject(new UWError("CONNECTION_CLOSED", event.reason));
168
+ });
169
+ return new _XConnection({ socket, id });
170
+ }
171
+ };
172
+ function safeParseMessageEventDataToXResponse(data) {
173
+ let s;
174
+ {
175
+ const result = import_zod.z.string().refine((val) => {
176
+ try {
177
+ JSON.parse(val);
178
+ } catch (e) {
179
+ return false;
98
180
  }
99
- const unWallet = new _UnWallet(config, unWalletConfig, ws);
181
+ return true;
182
+ }).safeParse(data);
183
+ if (!result.success) {
184
+ return {
185
+ success: false,
186
+ error: new UWError(
187
+ "INVALID_RESPONSE",
188
+ "message event data must be a json string"
189
+ )
190
+ };
191
+ }
192
+ s = result.data;
193
+ }
194
+ let parsed;
195
+ {
196
+ const result = import_zod.z.object({
197
+ type: import_zod.z.enum(VALID_X_RESPONSE_TYPES),
198
+ value: import_zod.z.string()
199
+ }).safeParse(JSON.parse(s));
200
+ if (!result.success) {
201
+ return {
202
+ success: false,
203
+ error: new UWError(
204
+ "INVALID_RESPONSE",
205
+ `invalid message event data: ${result.error.message}`
206
+ )
207
+ };
208
+ }
209
+ parsed = result.data;
210
+ }
211
+ return {
212
+ success: true,
213
+ data: parsed
214
+ };
215
+ }
216
+
217
+ // src/unwallet.ts
218
+ var UnWallet = class _UnWallet {
219
+ constructor(args) {
220
+ this.env = args.env;
221
+ this.clientID = args.clientID;
222
+ this.xConnection = args.xConnection;
223
+ }
224
+ static async init(config) {
225
+ const env = config.env !== void 0 ? config.env : "prod";
226
+ return new _UnWallet({
227
+ env,
228
+ clientID: config.clientID,
229
+ xConnection: await XConnection.init(getUnWalletConfigByEnv(env).xapi)
100
230
  });
101
231
  }
232
+ get unWalletConfig() {
233
+ return getUnWalletConfigByEnv(this.env);
234
+ }
102
235
  authorize(args) {
103
- if (!args.responseMode) {
104
- args.responseMode = "fragment";
105
- }
106
- let url = new URL(
107
- `${this.unWalletConfig.frontend.baseURL}/${args.isVirtual === false ? "" : "v"}authorize`
236
+ const url = new URL(
237
+ `${this.unWalletConfig.frontend.baseURL}/${args.isVirtual !== void 0 ? args.isVirtual ? "v" : "" : "v"}authorize`
108
238
  );
109
239
  {
110
240
  url.searchParams.set("response_type", "id_token");
111
- url.searchParams.set("response_mode", args.responseMode);
112
- url.searchParams.set("client_id", this.config.clientID);
241
+ url.searchParams.set(
242
+ "response_mode",
243
+ args.responseMode !== void 0 ? args.responseMode : "fragment"
244
+ );
245
+ url.searchParams.set("client_id", this.clientID);
113
246
  url.searchParams.set("scope", "openid");
114
247
  url.searchParams.set("redirect_uri", args.redirectURL);
115
248
  if (args.nonce !== void 0) {
@@ -119,42 +252,91 @@ var UnWallet = class _UnWallet {
119
252
  url.searchParams.set("chain_id", args.chainID.toString());
120
253
  }
121
254
  }
122
- location.assign(url.toString());
255
+ location.assign(url);
123
256
  }
124
257
  sign(args) {
125
258
  return new Promise((resolve, reject) => {
126
- this.resolve = (sig) => {
127
- resolve({
128
- digest: import_ethers.ethers.sha256(import_ethers.ethers.toUtf8Bytes(args.message)),
129
- signature: sig
130
- });
131
- };
132
- this.reject = reject;
259
+ if (this.xConnection.readyState !== WebSocket.OPEN) {
260
+ reject(new UWError("CONNECTION_NOT_OPENED"));
261
+ return;
262
+ }
263
+ if (this.xConnection.hasResponseHandler) {
264
+ reject(new UWError("REQUEST_IN_PROGRESS"));
265
+ return;
266
+ }
267
+ this.xConnection.setResponseHandler({
268
+ resolve: (resp) => {
269
+ this.xConnection.setResponseHandler(null);
270
+ if (resp.type !== "signature") {
271
+ reject(
272
+ new UWError(
273
+ "INVALID_RESPONSE",
274
+ `unexpected response type: ${resp.type}`
275
+ )
276
+ );
277
+ return;
278
+ }
279
+ resolve({
280
+ digest: import_ethers.ethers.sha256(import_ethers.ethers.toUtf8Bytes(args.message)),
281
+ signature: resp.value
282
+ });
283
+ },
284
+ reject: (err) => {
285
+ this.xConnection.setResponseHandler(null);
286
+ reject(err);
287
+ }
288
+ });
133
289
  const url = new URL(`${this.unWalletConfig.frontend.baseURL}/x/sign`);
134
290
  {
135
- url.searchParams.set("connectionID", this.connectionID);
136
- url.searchParams.set("clientID", this.config.clientID);
291
+ url.searchParams.set("connectionID", this.xConnection.id);
292
+ url.searchParams.set("clientID", this.clientID);
137
293
  url.searchParams.set("message", args.message);
138
294
  url.searchParams.set("ticketToken", args.ticketToken);
139
295
  }
140
- this.openWindow(url);
296
+ openWindow(url);
141
297
  });
142
298
  }
143
299
  sendTransaction(args) {
144
300
  return new Promise((resolve, reject) => {
301
+ if (this.xConnection.readyState !== WebSocket.OPEN) {
302
+ reject(new UWError("CONNECTION_NOT_OPENED"));
303
+ return;
304
+ }
305
+ if (this.xConnection.hasResponseHandler) {
306
+ reject(new UWError("REQUEST_IN_PROGRESS"));
307
+ return;
308
+ }
145
309
  if (args.value === void 0 && args.data === void 0) {
146
- throw new Error("either value or data required");
310
+ reject(
311
+ new UWError("INVALID_REQUEST", "either value or data is required")
312
+ );
313
+ return;
147
314
  }
148
- this.resolve = (txID) => {
149
- resolve({ transactionID: txID });
150
- };
151
- this.reject = reject;
315
+ this.xConnection.setResponseHandler({
316
+ resolve: (resp) => {
317
+ this.xConnection.setResponseHandler(null);
318
+ if (resp.type !== "transactionID") {
319
+ reject(
320
+ new UWError(
321
+ "INVALID_RESPONSE",
322
+ `unexpected response type: ${resp.type}`
323
+ )
324
+ );
325
+ return;
326
+ }
327
+ resolve({ transactionID: resp.value });
328
+ },
329
+ reject: (err) => {
330
+ this.xConnection.setResponseHandler(null);
331
+ reject(err);
332
+ }
333
+ });
152
334
  const url = new URL(
153
335
  `${this.unWalletConfig.frontend.baseURL}/x/sendTransaction`
154
336
  );
155
337
  {
156
- url.searchParams.set("connectionID", this.connectionID);
157
- url.searchParams.set("clientID", this.config.clientID);
338
+ url.searchParams.set("connectionID", this.xConnection.id);
339
+ url.searchParams.set("clientID", this.clientID);
158
340
  url.searchParams.set("chainID", args.chainID.toString());
159
341
  url.searchParams.set("toAddress", args.toAddress);
160
342
  url.searchParams.set(
@@ -167,52 +349,23 @@ var UnWallet = class _UnWallet {
167
349
  );
168
350
  url.searchParams.set("ticketToken", args.ticketToken);
169
351
  }
170
- this.openWindow(url);
171
- });
172
- }
173
- getConnectionID() {
174
- this.sendWSMessage({
175
- action: "getConnectionID"
352
+ openWindow(url);
176
353
  });
177
354
  }
178
- sendWSMessage(msg) {
179
- this.ws.send(JSON.stringify(msg));
180
- }
181
- handleWSMessage(msg) {
182
- switch (msg.type) {
183
- case "signature":
184
- this.resolve(msg.value);
185
- break;
186
- case "transactionID":
187
- this.resolve(msg.value);
188
- break;
189
- case "error":
190
- switch (msg.value) {
191
- case "rejected":
192
- this.reject("canceled");
193
- break;
194
- default:
195
- throw new Error(msg.value);
196
- }
197
- break;
198
- default:
199
- throw new Error(`unexpected message type: ${msg.type}`);
200
- }
201
- this.initPromiseArgs();
202
- }
203
- openWindow(url) {
204
- const width = screen.width / 2;
205
- const height = screen.height;
206
- const left = screen.width / 4;
207
- const top = 0;
208
- window.open(
209
- url.toString(),
210
- "_blank",
211
- `width=${width},height=${height},left=${left},top=${top}`
212
- );
213
- }
214
355
  };
356
+ function openWindow(url) {
357
+ const width = screen.width / 2;
358
+ const height = screen.height;
359
+ const left = screen.width / 4;
360
+ const top = 0;
361
+ window.open(
362
+ url.toString(),
363
+ "_blank",
364
+ `width=${width},height=${height},left=${left},top=${top}`
365
+ );
366
+ }
215
367
  // Annotate the CommonJS export names for ESM import in node:
216
368
  0 && (module.exports = {
369
+ UWError,
217
370
  UnWallet
218
371
  });
package/dist/index.mjs CHANGED
@@ -1,13 +1,24 @@
1
+ // src/error.ts
2
+ var _UWError = class _UWError extends Error {
3
+ constructor(code, message) {
4
+ super(message !== void 0 ? `${code}: ${message}` : code);
5
+ this.code = code;
6
+ }
7
+ };
8
+ _UWError.prototype.name = "UWError";
9
+ var UWError = _UWError;
10
+
1
11
  // src/unwallet.ts
2
12
  import { ethers } from "ethers";
3
13
 
4
- // src/consts.ts
14
+ // src/config.ts
5
15
  var UNWALLET_CONFIG_PROD = {
6
16
  frontend: {
7
17
  baseURL: "https://id.unwallet.world"
8
18
  },
9
19
  xapi: {
10
- url: "wss://xapi.id.unwallet.world"
20
+ url: "wss://xapi.id.unwallet.world",
21
+ connectionTimeout: 1e4
11
22
  }
12
23
  };
13
24
  var UNWALLET_CONFIG_DEV = {
@@ -15,75 +26,196 @@ var UNWALLET_CONFIG_DEV = {
15
26
  baseURL: "http://localhost:4200"
16
27
  },
17
28
  xapi: {
18
- url: "wss://xapi.id.test.unwallet.dev"
29
+ url: "wss://xapi.id.test.unwallet.dev",
30
+ connectionTimeout: 1e4
19
31
  }
20
32
  };
33
+ function getUnWalletConfigByEnv(env) {
34
+ switch (env) {
35
+ case "prod":
36
+ return UNWALLET_CONFIG_PROD;
37
+ case "dev":
38
+ return UNWALLET_CONFIG_DEV;
39
+ }
40
+ }
21
41
 
22
- // src/unwallet.ts
23
- var UnWallet = class _UnWallet {
24
- constructor(config, unWalletConfig, ws) {
25
- this.resolve = null;
26
- this.reject = null;
27
- this.config = config;
28
- this.unWalletConfig = unWalletConfig;
29
- this.ws = ws;
30
- this.connectionID = "";
31
- this.initPromiseArgs();
32
- }
33
- initPromiseArgs() {
34
- this.resolve = () => {
35
- };
36
- this.reject = () => {
37
- };
42
+ // src/x.ts
43
+ import { z } from "zod";
44
+ var VALID_X_RESPONSE_TYPES = [
45
+ "connectionID",
46
+ "signature",
47
+ "transactionID",
48
+ "error"
49
+ ];
50
+ var XConnection = class _XConnection {
51
+ constructor(args) {
52
+ this.responseHandler = null;
53
+ this.socket = args.socket;
54
+ this.id = args.id;
55
+ this.initListeners();
38
56
  }
39
- static init(config) {
40
- return new Promise((resolve, reject) => {
41
- if (config.env === void 0) {
42
- config.env = "prod";
57
+ initListeners() {
58
+ this.socket.onopen = null;
59
+ this.socket.onmessage = (event) => {
60
+ if (this.responseHandler === null) {
61
+ return;
43
62
  }
44
- let unWalletConfig;
63
+ let resp;
45
64
  {
46
- switch (config.env) {
47
- case "prod":
48
- unWalletConfig = UNWALLET_CONFIG_PROD;
49
- break;
50
- case "dev":
51
- unWalletConfig = UNWALLET_CONFIG_DEV;
52
- break;
65
+ const result = safeParseMessageEventDataToXResponse(event.data);
66
+ if (!result.success) {
67
+ this.responseHandler.reject(result.error);
68
+ return;
53
69
  }
70
+ resp = result.data;
54
71
  }
55
- const ws = new WebSocket(unWalletConfig.xapi.url);
56
- {
57
- ws.onerror = () => {
58
- reject("websocket connection failed");
59
- };
60
- ws.onopen = () => {
61
- unWallet.getConnectionID();
62
- };
63
- ws.onmessage = (event) => {
64
- const msg = JSON.parse(event.data);
65
- if (msg.type === "connectionID") {
66
- unWallet.connectionID = msg.value;
67
- resolve(unWallet);
72
+ switch (resp.type) {
73
+ case "error":
74
+ switch (resp.value) {
75
+ case "rejected":
76
+ this.responseHandler.reject(new UWError("REQUEST_REJECTED"));
77
+ break;
78
+ default:
79
+ this.responseHandler.reject(
80
+ new UWError(
81
+ "INVALID_RESPONSE",
82
+ `unexpected error response value: ${resp.value}`
83
+ )
84
+ );
85
+ }
86
+ break;
87
+ default:
88
+ this.responseHandler.resolve(resp);
89
+ }
90
+ };
91
+ this.socket.onerror = null;
92
+ this.socket.onclose = (event) => {
93
+ if (this.responseHandler === null) {
94
+ return;
95
+ }
96
+ this.responseHandler.reject(
97
+ new UWError("CONNECTION_CLOSED", event.reason)
98
+ );
99
+ };
100
+ }
101
+ get readyState() {
102
+ return this.socket.readyState;
103
+ }
104
+ get hasResponseHandler() {
105
+ return this.responseHandler !== null;
106
+ }
107
+ setResponseHandler(handler) {
108
+ this.responseHandler = handler;
109
+ }
110
+ static async init(config) {
111
+ const socket = new WebSocket(config.url);
112
+ const id = await new Promise((resolve, reject) => {
113
+ setInterval(
114
+ () => reject(new UWError("CONNECTION_TIMEOUT")),
115
+ config.connectionTimeout
116
+ );
117
+ socket.onopen = () => socket.send(JSON.stringify({ action: "getConnectionID" }));
118
+ socket.onmessage = (event) => {
119
+ let resp;
120
+ {
121
+ const result = safeParseMessageEventDataToXResponse(event.data);
122
+ if (!result.success) {
123
+ reject(result.error);
68
124
  return;
69
125
  }
70
- unWallet.handleWSMessage(msg);
71
- };
126
+ resp = result.data;
127
+ }
128
+ if (resp.type !== "connectionID") {
129
+ reject(
130
+ new UWError(
131
+ "INVALID_RESPONSE",
132
+ `unexpected response type: ${resp.type}`
133
+ )
134
+ );
135
+ return;
136
+ }
137
+ resolve(resp.value);
138
+ };
139
+ socket.onerror = () => reject(new UWError("CONNECTION_FAILED"));
140
+ socket.onclose = (event) => reject(new UWError("CONNECTION_CLOSED", event.reason));
141
+ });
142
+ return new _XConnection({ socket, id });
143
+ }
144
+ };
145
+ function safeParseMessageEventDataToXResponse(data) {
146
+ let s;
147
+ {
148
+ const result = z.string().refine((val) => {
149
+ try {
150
+ JSON.parse(val);
151
+ } catch (e) {
152
+ return false;
72
153
  }
73
- const unWallet = new _UnWallet(config, unWalletConfig, ws);
154
+ return true;
155
+ }).safeParse(data);
156
+ if (!result.success) {
157
+ return {
158
+ success: false,
159
+ error: new UWError(
160
+ "INVALID_RESPONSE",
161
+ "message event data must be a json string"
162
+ )
163
+ };
164
+ }
165
+ s = result.data;
166
+ }
167
+ let parsed;
168
+ {
169
+ const result = z.object({
170
+ type: z.enum(VALID_X_RESPONSE_TYPES),
171
+ value: z.string()
172
+ }).safeParse(JSON.parse(s));
173
+ if (!result.success) {
174
+ return {
175
+ success: false,
176
+ error: new UWError(
177
+ "INVALID_RESPONSE",
178
+ `invalid message event data: ${result.error.message}`
179
+ )
180
+ };
181
+ }
182
+ parsed = result.data;
183
+ }
184
+ return {
185
+ success: true,
186
+ data: parsed
187
+ };
188
+ }
189
+
190
+ // src/unwallet.ts
191
+ var UnWallet = class _UnWallet {
192
+ constructor(args) {
193
+ this.env = args.env;
194
+ this.clientID = args.clientID;
195
+ this.xConnection = args.xConnection;
196
+ }
197
+ static async init(config) {
198
+ const env = config.env !== void 0 ? config.env : "prod";
199
+ return new _UnWallet({
200
+ env,
201
+ clientID: config.clientID,
202
+ xConnection: await XConnection.init(getUnWalletConfigByEnv(env).xapi)
74
203
  });
75
204
  }
205
+ get unWalletConfig() {
206
+ return getUnWalletConfigByEnv(this.env);
207
+ }
76
208
  authorize(args) {
77
- if (!args.responseMode) {
78
- args.responseMode = "fragment";
79
- }
80
- let url = new URL(
81
- `${this.unWalletConfig.frontend.baseURL}/${args.isVirtual === false ? "" : "v"}authorize`
209
+ const url = new URL(
210
+ `${this.unWalletConfig.frontend.baseURL}/${args.isVirtual !== void 0 ? args.isVirtual ? "v" : "" : "v"}authorize`
82
211
  );
83
212
  {
84
213
  url.searchParams.set("response_type", "id_token");
85
- url.searchParams.set("response_mode", args.responseMode);
86
- url.searchParams.set("client_id", this.config.clientID);
214
+ url.searchParams.set(
215
+ "response_mode",
216
+ args.responseMode !== void 0 ? args.responseMode : "fragment"
217
+ );
218
+ url.searchParams.set("client_id", this.clientID);
87
219
  url.searchParams.set("scope", "openid");
88
220
  url.searchParams.set("redirect_uri", args.redirectURL);
89
221
  if (args.nonce !== void 0) {
@@ -93,42 +225,91 @@ var UnWallet = class _UnWallet {
93
225
  url.searchParams.set("chain_id", args.chainID.toString());
94
226
  }
95
227
  }
96
- location.assign(url.toString());
228
+ location.assign(url);
97
229
  }
98
230
  sign(args) {
99
231
  return new Promise((resolve, reject) => {
100
- this.resolve = (sig) => {
101
- resolve({
102
- digest: ethers.sha256(ethers.toUtf8Bytes(args.message)),
103
- signature: sig
104
- });
105
- };
106
- this.reject = reject;
232
+ if (this.xConnection.readyState !== WebSocket.OPEN) {
233
+ reject(new UWError("CONNECTION_NOT_OPENED"));
234
+ return;
235
+ }
236
+ if (this.xConnection.hasResponseHandler) {
237
+ reject(new UWError("REQUEST_IN_PROGRESS"));
238
+ return;
239
+ }
240
+ this.xConnection.setResponseHandler({
241
+ resolve: (resp) => {
242
+ this.xConnection.setResponseHandler(null);
243
+ if (resp.type !== "signature") {
244
+ reject(
245
+ new UWError(
246
+ "INVALID_RESPONSE",
247
+ `unexpected response type: ${resp.type}`
248
+ )
249
+ );
250
+ return;
251
+ }
252
+ resolve({
253
+ digest: ethers.sha256(ethers.toUtf8Bytes(args.message)),
254
+ signature: resp.value
255
+ });
256
+ },
257
+ reject: (err) => {
258
+ this.xConnection.setResponseHandler(null);
259
+ reject(err);
260
+ }
261
+ });
107
262
  const url = new URL(`${this.unWalletConfig.frontend.baseURL}/x/sign`);
108
263
  {
109
- url.searchParams.set("connectionID", this.connectionID);
110
- url.searchParams.set("clientID", this.config.clientID);
264
+ url.searchParams.set("connectionID", this.xConnection.id);
265
+ url.searchParams.set("clientID", this.clientID);
111
266
  url.searchParams.set("message", args.message);
112
267
  url.searchParams.set("ticketToken", args.ticketToken);
113
268
  }
114
- this.openWindow(url);
269
+ openWindow(url);
115
270
  });
116
271
  }
117
272
  sendTransaction(args) {
118
273
  return new Promise((resolve, reject) => {
274
+ if (this.xConnection.readyState !== WebSocket.OPEN) {
275
+ reject(new UWError("CONNECTION_NOT_OPENED"));
276
+ return;
277
+ }
278
+ if (this.xConnection.hasResponseHandler) {
279
+ reject(new UWError("REQUEST_IN_PROGRESS"));
280
+ return;
281
+ }
119
282
  if (args.value === void 0 && args.data === void 0) {
120
- throw new Error("either value or data required");
283
+ reject(
284
+ new UWError("INVALID_REQUEST", "either value or data is required")
285
+ );
286
+ return;
121
287
  }
122
- this.resolve = (txID) => {
123
- resolve({ transactionID: txID });
124
- };
125
- this.reject = reject;
288
+ this.xConnection.setResponseHandler({
289
+ resolve: (resp) => {
290
+ this.xConnection.setResponseHandler(null);
291
+ if (resp.type !== "transactionID") {
292
+ reject(
293
+ new UWError(
294
+ "INVALID_RESPONSE",
295
+ `unexpected response type: ${resp.type}`
296
+ )
297
+ );
298
+ return;
299
+ }
300
+ resolve({ transactionID: resp.value });
301
+ },
302
+ reject: (err) => {
303
+ this.xConnection.setResponseHandler(null);
304
+ reject(err);
305
+ }
306
+ });
126
307
  const url = new URL(
127
308
  `${this.unWalletConfig.frontend.baseURL}/x/sendTransaction`
128
309
  );
129
310
  {
130
- url.searchParams.set("connectionID", this.connectionID);
131
- url.searchParams.set("clientID", this.config.clientID);
311
+ url.searchParams.set("connectionID", this.xConnection.id);
312
+ url.searchParams.set("clientID", this.clientID);
132
313
  url.searchParams.set("chainID", args.chainID.toString());
133
314
  url.searchParams.set("toAddress", args.toAddress);
134
315
  url.searchParams.set(
@@ -141,51 +322,22 @@ var UnWallet = class _UnWallet {
141
322
  );
142
323
  url.searchParams.set("ticketToken", args.ticketToken);
143
324
  }
144
- this.openWindow(url);
145
- });
146
- }
147
- getConnectionID() {
148
- this.sendWSMessage({
149
- action: "getConnectionID"
325
+ openWindow(url);
150
326
  });
151
327
  }
152
- sendWSMessage(msg) {
153
- this.ws.send(JSON.stringify(msg));
154
- }
155
- handleWSMessage(msg) {
156
- switch (msg.type) {
157
- case "signature":
158
- this.resolve(msg.value);
159
- break;
160
- case "transactionID":
161
- this.resolve(msg.value);
162
- break;
163
- case "error":
164
- switch (msg.value) {
165
- case "rejected":
166
- this.reject("canceled");
167
- break;
168
- default:
169
- throw new Error(msg.value);
170
- }
171
- break;
172
- default:
173
- throw new Error(`unexpected message type: ${msg.type}`);
174
- }
175
- this.initPromiseArgs();
176
- }
177
- openWindow(url) {
178
- const width = screen.width / 2;
179
- const height = screen.height;
180
- const left = screen.width / 4;
181
- const top = 0;
182
- window.open(
183
- url.toString(),
184
- "_blank",
185
- `width=${width},height=${height},left=${left},top=${top}`
186
- );
187
- }
188
328
  };
329
+ function openWindow(url) {
330
+ const width = screen.width / 2;
331
+ const height = screen.height;
332
+ const left = screen.width / 4;
333
+ const top = 0;
334
+ window.open(
335
+ url.toString(),
336
+ "_blank",
337
+ `width=${width},height=${height},left=${left},top=${top}`
338
+ );
339
+ }
189
340
  export {
341
+ UWError,
190
342
  UnWallet
191
343
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "unwallet-client-sdk",
3
- "version": "0.15.0",
3
+ "version": "0.16.0",
4
4
  "description": "unWallet client-side SDK",
5
5
  "license": "MIT",
6
6
  "author": "SIVIRA",
@@ -29,7 +29,8 @@
29
29
  }
30
30
  },
31
31
  "dependencies": {
32
- "ethers": "6.14.0"
32
+ "ethers": "6.14.0",
33
+ "zod": "3.24.4"
33
34
  },
34
35
  "devDependencies": {
35
36
  "@commitlint/cli": "19.8.1",