@fivenorth/loop-sdk 0.7.2 → 0.7.3

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.
Files changed (2) hide show
  1. package/dist/index.js +272 -145
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -2067,6 +2067,43 @@ var MessageType;
2067
2067
  MessageType2["REJECT_REQUEST"] = "reject_request";
2068
2068
  })(MessageType ||= {});
2069
2069
 
2070
+ // src/errors.ts
2071
+ class RequestTimeoutError extends Error {
2072
+ constructor(timeout) {
2073
+ super(`Request timed out after ${timeout}ms.`);
2074
+ }
2075
+ }
2076
+
2077
+ class RejectRequestError extends Error {
2078
+ constructor() {
2079
+ super("Request was rejected by the wallet.");
2080
+ }
2081
+ }
2082
+
2083
+ class UnauthorizedError extends Error {
2084
+ code;
2085
+ constructor(code) {
2086
+ super(code || "Unauthorized");
2087
+ this.code = code;
2088
+ }
2089
+ }
2090
+ var UNAUTH_CODES = new Set(["UNAUTHENTICATED", "UNAUTHORIZED", "SESSION_EXPIRED", "LOGGED_OUT"]);
2091
+ function extractErrorCode(message) {
2092
+ if (typeof message?.error?.code === "string" && message.error.code.length > 0) {
2093
+ return message.error.code;
2094
+ }
2095
+ if (message?.type === "unauthorized" && typeof message?.code === "string") {
2096
+ return message.code;
2097
+ }
2098
+ return null;
2099
+ }
2100
+ function isUnauthCode(code) {
2101
+ if (!code) {
2102
+ return false;
2103
+ }
2104
+ return UNAUTH_CODES.has(code);
2105
+ }
2106
+
2070
2107
  // src/connection.ts
2071
2108
  class Connection {
2072
2109
  walletUrl = "https://cantonloop.com";
@@ -2076,6 +2113,7 @@ class Connection {
2076
2113
  ticketId = null;
2077
2114
  onMessageHandler = null;
2078
2115
  reconnectPromise = null;
2116
+ status = "disconnected";
2079
2117
  constructor({ network, walletUrl, apiUrl }) {
2080
2118
  this.network = network || "main";
2081
2119
  switch (this.network) {
@@ -2106,6 +2144,9 @@ class Connection {
2106
2144
  this.apiUrl = apiUrl;
2107
2145
  }
2108
2146
  }
2147
+ connectInProgress() {
2148
+ return this.status === "connecting" || this.status === "connected";
2149
+ }
2109
2150
  async getTicket(appName, sessionId, version) {
2110
2151
  const response = await fetch(`${this.apiUrl}/api/v1/.connect/pair/tickets`, {
2111
2152
  method: "POST",
@@ -2198,7 +2239,10 @@ class Connection {
2198
2239
  }
2199
2240
  });
2200
2241
  if (!response.ok) {
2201
- throw new Error("Session verification failed.");
2242
+ if (response.status === 401 || response.status === 403) {
2243
+ throw new UnauthorizedError;
2244
+ }
2245
+ throw new Error(`Session verification failed with status ${response.status}.`);
2202
2246
  }
2203
2247
  const data = await response.json();
2204
2248
  const email = data?.email;
@@ -2216,45 +2260,24 @@ class Connection {
2216
2260
  };
2217
2261
  return account;
2218
2262
  }
2219
- websocketUrl(ticketId) {
2220
- return `${this.network === "local" ? "ws" : "wss"}://${this.apiUrl.replace("https://", "").replace("http://", "")}/api/v1/.connect/pair/ws/${ticketId}`;
2221
- }
2222
- attachWebSocket(ticketId, onMessage, onOpen, onError, onClose) {
2223
- const wsUrl = this.websocketUrl(ticketId);
2224
- const ws = new WebSocket(wsUrl);
2225
- ws.onmessage = onMessage;
2226
- ws.onopen = () => {
2227
- console.log("Connected to ticket server.");
2228
- onOpen?.();
2229
- };
2230
- ws.onclose = (event) => {
2231
- if (this.ws === ws) {
2232
- this.ws = null;
2233
- }
2234
- console.log("Disconnected from ticket server.");
2235
- onClose?.(event);
2236
- };
2237
- ws.onerror = (event) => {
2238
- if (this.ws === ws) {
2239
- this.ws = null;
2240
- }
2241
- onError?.(event);
2242
- };
2243
- this.ws = ws;
2244
- }
2245
2263
  connectWebSocket(ticketId, onMessage) {
2246
- this.ticketId = ticketId;
2264
+ if (this.ws && (this.ws.readyState === WebSocket.OPEN || this.ws.readyState === WebSocket.CONNECTING) && this.ticketId !== ticketId) {
2265
+ this.ws.close();
2266
+ this.ws = null;
2267
+ }
2268
+ if (this.status === "connecting" || this.status === "connected") {
2269
+ return;
2270
+ }
2247
2271
  this.onMessageHandler = onMessage;
2272
+ this.ticketId = ticketId;
2273
+ this.status = "connecting";
2248
2274
  this.attachWebSocket(ticketId, onMessage);
2249
2275
  }
2250
2276
  reconnect() {
2251
2277
  if (!this.ticketId || !this.onMessageHandler) {
2252
2278
  return Promise.reject(new Error("Cannot reconnect without a known ticket."));
2253
2279
  }
2254
- if (this.reconnectPromise) {
2255
- return this.reconnectPromise;
2256
- }
2257
- this.reconnectPromise = new Promise((resolve, reject) => {
2280
+ return new Promise((resolve, reject) => {
2258
2281
  let opened = false;
2259
2282
  this.attachWebSocket(this.ticketId, this.onMessageHandler, () => {
2260
2283
  opened = true;
@@ -2270,29 +2293,37 @@ class Connection {
2270
2293
  }
2271
2294
  reject(new Error("Failed to reconnect to ticket server."));
2272
2295
  });
2273
- }).finally(() => {
2274
- this.reconnectPromise = null;
2275
2296
  });
2276
- return this.reconnectPromise;
2277
2297
  }
2278
- async reconnectWebSocket() {
2279
- if (this.ws && this.ws.readyState === WebSocket.OPEN) {
2280
- return;
2281
- }
2282
- return this.reconnect();
2283
- }
2284
- }
2285
-
2286
- // src/errors.ts
2287
- class RequestTimeoutError extends Error {
2288
- constructor(timeout) {
2289
- super(`Request timed out after ${timeout}ms.`);
2298
+ websocketUrl(ticketId) {
2299
+ return `${this.network === "local" ? "ws" : "wss"}://${this.apiUrl.replace("https://", "").replace("http://", "")}/api/v1/.connect/pair/ws/${encodeURIComponent(ticketId)}`;
2290
2300
  }
2291
- }
2292
-
2293
- class RejectRequestError extends Error {
2294
- constructor() {
2295
- super("Request was rejected by the wallet.");
2301
+ attachWebSocket(ticketId, onMessage, onOpen, onError, onClose) {
2302
+ const wsUrl = this.websocketUrl(ticketId);
2303
+ const ws = new WebSocket(wsUrl);
2304
+ ws.onmessage = onMessage;
2305
+ ws.onopen = () => {
2306
+ this.status = "connected";
2307
+ console.log("[LoopSDK] Connected to ticket server.");
2308
+ onOpen?.();
2309
+ };
2310
+ ws.onclose = (event) => {
2311
+ this.status = "disconnected";
2312
+ if (this.ws === ws) {
2313
+ this.ws = null;
2314
+ }
2315
+ console.log("[LoopSDK] Disconnected from ticket server.");
2316
+ onClose?.(event);
2317
+ };
2318
+ ws.onerror = (event) => {
2319
+ this.status = "disconnected";
2320
+ ws.close();
2321
+ if (this.ws === ws) {
2322
+ this.ws = null;
2323
+ }
2324
+ onError?.(event);
2325
+ };
2326
+ this.ws = ws;
2296
2327
  }
2297
2328
  }
2298
2329
 
@@ -2403,24 +2434,24 @@ class Provider {
2403
2434
  }
2404
2435
  async ensureConnected() {
2405
2436
  if (this.connection.ws && this.connection.ws.readyState === WebSocket.OPEN) {
2406
- return;
2437
+ return Promise.resolve();
2407
2438
  }
2408
- if (typeof this.connection.reconnectWebSocket === "function") {
2409
- await this.connection.reconnectWebSocket();
2410
- if (this.connection.ws && this.connection.ws.readyState === WebSocket.OPEN) {
2411
- return;
2412
- }
2439
+ await this.connection.reconnect();
2440
+ if (this.connection.ws && this.connection.ws.readyState === WebSocket.OPEN) {
2441
+ return;
2413
2442
  }
2414
2443
  throw new Error("Not connected.");
2415
2444
  }
2416
2445
  sendRequest(messageType, params = {}, options) {
2417
2446
  return new Promise((resolve, reject) => {
2418
2447
  const requestId = generateRequestId();
2419
- const requestContext = this.hooks?.onRequestStart?.(messageType, options?.requestLabel);
2448
+ let requestContext;
2420
2449
  const ensure = async () => {
2421
2450
  try {
2422
2451
  await this.ensureConnected();
2452
+ requestContext = await this.hooks?.onRequestStart?.(messageType, options?.requestLabel);
2423
2453
  } catch (error) {
2454
+ console.error("[LoopSDK] error when checking connection status", error);
2424
2455
  this.hooks?.onRequestFinish?.({
2425
2456
  status: "error",
2426
2457
  messageType,
@@ -2444,7 +2475,13 @@ class Provider {
2444
2475
  };
2445
2476
  }
2446
2477
  }
2447
- this.connection.ws.send(JSON.stringify(requestBody));
2478
+ try {
2479
+ this.connection.ws.send(JSON.stringify(requestBody));
2480
+ } catch (error) {
2481
+ console.error("[LoopSDK] error when sending request", error);
2482
+ reject(error);
2483
+ return;
2484
+ }
2448
2485
  const intervalTime = 300;
2449
2486
  let elapsedTime = 0;
2450
2487
  const timeoutMs = options?.requestTimeout ?? this.requestTimeout;
@@ -2453,6 +2490,18 @@ class Provider {
2453
2490
  if (response) {
2454
2491
  clearInterval(intervalId);
2455
2492
  this.requests.delete(requestId);
2493
+ const code = extractErrorCode(response);
2494
+ if (isUnauthCode(code)) {
2495
+ this.hooks?.onRequestFinish?.({
2496
+ status: "error",
2497
+ messageType,
2498
+ requestLabel: options?.requestLabel,
2499
+ requestContext,
2500
+ errorCode: code
2501
+ });
2502
+ reject(new UnauthorizedError(code));
2503
+ return;
2504
+ }
2456
2505
  if (response.type === "reject_request" /* REJECT_REQUEST */) {
2457
2506
  this.hooks?.onRequestFinish?.({
2458
2507
  status: "rejected",
@@ -2568,11 +2617,87 @@ class LoopWallet {
2568
2617
  }
2569
2618
  }
2570
2619
 
2620
+ // src/session.ts
2621
+ var STORAGE_KEY_LOOP_CONNECT = "loop_connect";
2622
+
2623
+ class SessionInfo {
2624
+ sessionId;
2625
+ ticketId;
2626
+ authToken;
2627
+ partyId;
2628
+ publicKey;
2629
+ email;
2630
+ _isAuthorized = false;
2631
+ constructor({ sessionId, ticketId, authToken, partyId, publicKey, email }) {
2632
+ this.sessionId = sessionId;
2633
+ this.ticketId = ticketId;
2634
+ this.authToken = authToken;
2635
+ this.partyId = partyId;
2636
+ this.publicKey = publicKey;
2637
+ this.email = email;
2638
+ }
2639
+ setTicketId(ticketId) {
2640
+ this.ticketId = ticketId;
2641
+ this.save();
2642
+ }
2643
+ authorized() {
2644
+ if (this.ticketId === undefined || this.sessionId === undefined || this.authToken === undefined || this.partyId === undefined || this.publicKey === undefined) {
2645
+ throw new Error("Session cannot be authorized without all required fields.");
2646
+ }
2647
+ this._isAuthorized = true;
2648
+ }
2649
+ isPreAuthorized() {
2650
+ return !this._isAuthorized && this.ticketId !== undefined && this.sessionId !== undefined && this.authToken !== undefined && this.partyId !== undefined && this.publicKey !== undefined;
2651
+ }
2652
+ isAuthorized() {
2653
+ return this._isAuthorized;
2654
+ }
2655
+ save() {
2656
+ localStorage.setItem("loop_connect", this.toJson());
2657
+ }
2658
+ reset() {
2659
+ localStorage.removeItem(STORAGE_KEY_LOOP_CONNECT);
2660
+ this.sessionId = generateRequestId();
2661
+ this._isAuthorized = false;
2662
+ this.ticketId = undefined;
2663
+ this.authToken = undefined;
2664
+ this.partyId = undefined;
2665
+ this.publicKey = undefined;
2666
+ this.email = undefined;
2667
+ }
2668
+ static fromStorage() {
2669
+ const existingConnectionRaw = localStorage.getItem(STORAGE_KEY_LOOP_CONNECT);
2670
+ if (!existingConnectionRaw) {
2671
+ return new SessionInfo({ sessionId: generateRequestId() });
2672
+ }
2673
+ let session = null;
2674
+ try {
2675
+ session = new SessionInfo(JSON.parse(existingConnectionRaw));
2676
+ } catch (error) {
2677
+ console.error("Failed to parse existing connection info, local storage is corrupted.", error);
2678
+ localStorage.removeItem(STORAGE_KEY_LOOP_CONNECT);
2679
+ session = new SessionInfo({ sessionId: generateRequestId() });
2680
+ }
2681
+ return session;
2682
+ }
2683
+ toJson() {
2684
+ return JSON.stringify({
2685
+ sessionId: this.sessionId,
2686
+ ticketId: this.ticketId,
2687
+ authToken: this.authToken,
2688
+ partyId: this.partyId,
2689
+ publicKey: this.publicKey,
2690
+ email: this.email
2691
+ });
2692
+ }
2693
+ }
2694
+
2571
2695
  // src/index.ts
2572
2696
  class LoopSDK {
2573
- version = "0.0.1";
2697
+ version = "0.7.3";
2574
2698
  appName = "Unknown";
2575
2699
  connection = null;
2700
+ session = null;
2576
2701
  provider = null;
2577
2702
  openMode = "popup";
2578
2703
  requestSigningMode = "popup";
@@ -2581,7 +2706,6 @@ class LoopSDK {
2581
2706
  onAccept = null;
2582
2707
  onReject = null;
2583
2708
  overlay = null;
2584
- ticketId = null;
2585
2709
  wallet;
2586
2710
  constructor() {
2587
2711
  this.wallet = new LoopWallet(() => this.provider);
@@ -2595,6 +2719,9 @@ class LoopSDK {
2595
2719
  onReject,
2596
2720
  options
2597
2721
  }) {
2722
+ if (typeof window === "undefined" || typeof document === "undefined" || typeof localStorage === "undefined") {
2723
+ throw new Error("LoopSDK can only be initialized in a browser environment with localStorage support.");
2724
+ }
2598
2725
  this.appName = appName;
2599
2726
  this.onAccept = onAccept || null;
2600
2727
  this.onReject = onReject || null;
@@ -2609,70 +2736,68 @@ class LoopSDK {
2609
2736
  this.redirectUrl = resolvedOptions.redirectUrl;
2610
2737
  this.connection = new Connection({ network, walletUrl, apiUrl });
2611
2738
  }
2612
- async connect() {
2613
- if (typeof window === "undefined") {
2614
- console.warn("LoopSDK.connect() can only be called in a browser environment.");
2739
+ async loadSessionInfo() {
2740
+ if (this.session) {
2741
+ return;
2742
+ }
2743
+ this.session = SessionInfo.fromStorage();
2744
+ if (!this.session.isPreAuthorized()) {
2615
2745
  return;
2616
2746
  }
2747
+ try {
2748
+ const verifiedAccount = await this.connection?.verifySession(this.session.authToken);
2749
+ if (!verifiedAccount || verifiedAccount?.party_id !== this.session.partyId) {
2750
+ console.warn("[LoopSDK] Stored partyId does not match verified account. Clearing cached session.");
2751
+ this.logout();
2752
+ return;
2753
+ }
2754
+ this.session.authorized();
2755
+ } catch (err) {
2756
+ if (err instanceof UnauthorizedError) {
2757
+ console.error("Unauthorized error when verifying session.", err);
2758
+ this.session.reset();
2759
+ return;
2760
+ }
2761
+ console.error("[LoopSDK] Failed to verify session.", err);
2762
+ throw err;
2763
+ }
2764
+ }
2765
+ async autoConnect() {
2617
2766
  if (!this.connection) {
2618
2767
  throw new Error("SDK not initialized. Call init() first.");
2619
2768
  }
2620
- const existingConnectionRaw = localStorage.getItem("loop_connect");
2621
- if (existingConnectionRaw) {
2622
- try {
2623
- let canReuseTicket = true;
2624
- const { ticketId, authToken, partyId, publicKey, email } = JSON.parse(existingConnectionRaw);
2625
- if (authToken && partyId && publicKey) {
2626
- try {
2627
- const verifiedAccount = await this.connection.verifySession(authToken);
2628
- if (verifiedAccount.party_id === partyId) {
2629
- this.provider = new Provider({
2630
- connection: this.connection,
2631
- party_id: partyId,
2632
- auth_token: authToken,
2633
- public_key: publicKey,
2634
- email,
2635
- hooks: this.createProviderHooks()
2636
- });
2637
- this.ticketId = ticketId || null;
2638
- this.onAccept?.(this.provider);
2639
- if (ticketId) {
2640
- this.connection.connectWebSocket(ticketId, this.handleWebSocketMessage.bind(this));
2641
- }
2642
- return;
2643
- } else {
2644
- console.warn("[LoopSDK] Sttored partyId does not march verified account. Clearing cached session.");
2645
- canReuseTicket = false;
2646
- localStorage.removeItem("loop_connect");
2647
- }
2648
- } catch (err) {
2649
- console.error("Auto-login failed, token is invalid. Starting new connection.", err);
2650
- canReuseTicket = false;
2651
- localStorage.removeItem("loop_connect");
2652
- }
2653
- }
2654
- if (ticketId && canReuseTicket) {
2655
- this.ticketId = ticketId;
2656
- const url = new URL("/.connect/", this.connection.walletUrl);
2657
- url.searchParams.set("ticketId", ticketId);
2658
- if (this.redirectUrl) {
2659
- url.searchParams.set("redirectUrl", this.redirectUrl);
2660
- }
2661
- const connectUrl = url.toString();
2662
- this.showQrCode(connectUrl);
2663
- this.connection.connectWebSocket(ticketId, this.handleWebSocketMessage.bind(this));
2664
- return;
2665
- }
2666
- } catch (error) {
2667
- console.error("Failed to parse existing connection info, creating a new one.", error);
2668
- }
2669
- localStorage.removeItem("loop_connect");
2769
+ await this.loadSessionInfo();
2770
+ if (!this.session) {
2771
+ throw new Error("No valid session found. The network connection maynot available or the backend is not reachable.");
2772
+ }
2773
+ if (this.session.isAuthorized()) {
2774
+ this.provider = new Provider({
2775
+ connection: this.connection,
2776
+ party_id: this.session.partyId,
2777
+ auth_token: this.session.authToken,
2778
+ public_key: this.session.publicKey,
2779
+ email: this.session.email,
2780
+ hooks: this.createProviderHooks()
2781
+ });
2782
+ this.onAccept?.(this.provider);
2783
+ this.connection.connectWebSocket(this.session.ticketId, this.handleWebSocketMessage.bind(this));
2784
+ return Promise.resolve();
2785
+ }
2786
+ }
2787
+ async connect() {
2788
+ if (!this.connection) {
2789
+ throw new Error("SDK not initialized. Call init() first.");
2790
+ }
2791
+ await this.autoConnect();
2792
+ if (this.connection?.connectInProgress() === true) {
2793
+ return;
2794
+ }
2795
+ if (this.session && this.session.isAuthorized()) {
2796
+ return;
2670
2797
  }
2671
- const sessionId = generateRequestId();
2672
2798
  try {
2673
- const { ticket_id: ticketId } = await this.connection.getTicket(this.appName, sessionId, this.version);
2674
- this.ticketId = ticketId;
2675
- localStorage.setItem("loop_connect", JSON.stringify({ sessionId, ticketId }));
2799
+ const { ticket_id: ticketId } = await this.connection.getTicket(this.appName, this.session.sessionId, this.version);
2800
+ this.session.setTicketId(ticketId);
2676
2801
  const connectUrl = this.buildConnectUrl(ticketId);
2677
2802
  this.showQrCode(connectUrl);
2678
2803
  this.connection.connectWebSocket(ticketId, this.handleWebSocketMessage.bind(this));
@@ -2683,6 +2808,12 @@ class LoopSDK {
2683
2808
  }
2684
2809
  handleWebSocketMessage(event) {
2685
2810
  const message = JSON.parse(event.data);
2811
+ const errCode = extractErrorCode(message);
2812
+ if (isUnauthCode(errCode)) {
2813
+ console.warn("[LoopSDK] Detected session invalidation:", errCode, { message });
2814
+ this.logout();
2815
+ return;
2816
+ }
2686
2817
  console.log("[LoopSDK] WS message received:", message);
2687
2818
  if (message.type === "handshake_accept" /* HANDSHAKE_ACCEPT */) {
2688
2819
  console.log("[LoopSDK] Entering HANDSHAKE_ACCEPT flow");
@@ -2696,32 +2827,27 @@ class LoopSDK {
2696
2827
  email,
2697
2828
  hooks: this.createProviderHooks()
2698
2829
  });
2699
- const connectionInfoRaw = localStorage.getItem("loop_connect");
2700
- if (connectionInfoRaw) {
2701
- try {
2702
- const connectionInfo = JSON.parse(connectionInfoRaw);
2703
- this.ticketId = connectionInfo.ticketId || this.ticketId;
2704
- connectionInfo.authToken = authToken;
2705
- connectionInfo.partyId = partyId;
2706
- connectionInfo.publicKey = publicKey;
2707
- connectionInfo.email = email;
2708
- localStorage.setItem("loop_connect", JSON.stringify(connectionInfo));
2709
- this.onAccept?.(this.provider);
2710
- this.hideQrCode();
2711
- this.connection?.connectWebSocket(connectionInfo.ticketId, this.handleWebSocketMessage.bind(this));
2712
- console.log("[LoopSDK] HANDSHAKE_ACCEPT: closing popup (if exists)");
2713
- this.popupWindow = null;
2714
- } catch (error) {
2715
- console.error("Failed to update local storage with auth token.", error);
2716
- }
2830
+ try {
2831
+ this.session.authToken = authToken;
2832
+ this.session.partyId = partyId;
2833
+ this.session.publicKey = publicKey;
2834
+ this.session.email = email;
2835
+ this.session.authorized();
2836
+ this.session.save();
2837
+ this.onAccept?.(this.provider);
2838
+ this.hideQrCode();
2839
+ console.log("[LoopSDK] HANDSHAKE_ACCEPT: closing popup (if exists)");
2840
+ this.popupWindow = null;
2841
+ } catch (error) {
2842
+ console.error("Failed to update local storage with auth token.", error);
2717
2843
  }
2718
2844
  }
2719
2845
  } else if (message.type === "handshake_reject" /* HANDSHAKE_REJECT */) {
2720
2846
  console.log("[LoopSDK] Entering HANDSHAKE_REJECT flow");
2721
- localStorage.removeItem("loop_connect");
2722
2847
  this.connection?.ws?.close();
2723
2848
  this.onReject?.();
2724
2849
  this.hideQrCode();
2850
+ this.session?.reset();
2725
2851
  console.log("[LoopSDK] HANDSHAKE_REJECT: closing popup (if exists)");
2726
2852
  this.popupWindow = null;
2727
2853
  } else if (this.provider) {
@@ -2746,7 +2872,7 @@ class LoopSDK {
2746
2872
  if (typeof window === "undefined") {
2747
2873
  return null;
2748
2874
  }
2749
- if (!this.ticketId) {
2875
+ if (!this.session?.ticketId) {
2750
2876
  console.warn("[LoopSDK] Cannot open wallet UI for request: no active ticket.");
2751
2877
  return null;
2752
2878
  }
@@ -2791,9 +2917,6 @@ class LoopSDK {
2791
2917
  return window.open(url, "_blank", "noopener,noreferrer");
2792
2918
  }
2793
2919
  showQrCode(url) {
2794
- if (typeof window === "undefined" || typeof document === "undefined") {
2795
- return;
2796
- }
2797
2920
  import_qrcode.default.toDataURL(url, (err, dataUrl) => {
2798
2921
  if (err) {
2799
2922
  console.error("Failed to generate QR code", err);
@@ -2847,6 +2970,12 @@ class LoopSDK {
2847
2970
  this.overlay = null;
2848
2971
  }
2849
2972
  }
2973
+ logout() {
2974
+ this.session?.reset();
2975
+ this.provider = null;
2976
+ this.connection?.ws?.close();
2977
+ this.hideQrCode();
2978
+ }
2850
2979
  requireProvider() {
2851
2980
  if (!this.provider) {
2852
2981
  throw new Error("SDK not connected. Call connect() and wait for acceptance first.");
@@ -2855,9 +2984,7 @@ class LoopSDK {
2855
2984
  }
2856
2985
  createProviderHooks() {
2857
2986
  return {
2858
- onRequestStart: () => {
2859
- return this.openRequestUi();
2860
- },
2987
+ onRequestStart: () => this.openRequestUi(),
2861
2988
  onRequestFinish: ({ requestContext }) => {
2862
2989
  const win = requestContext;
2863
2990
  if (win) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fivenorth/loop-sdk",
3
- "version": "0.7.2",
3
+ "version": "0.7.3",
4
4
  "author": "hello@fivenorth.io",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.js",