favalib 0.0.2 → 0.0.4

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.
@@ -72,13 +72,9 @@ declare class TwoFaLib extends TypedEventTarget<TwoFaLibEventMapEvents> {
72
72
  /**
73
73
  * Sets a server url, this will allow syncing with the server.
74
74
  * @param serverUrl - The server url.
75
+ * @param force - Force setting the sync server url, even if no connection can be made
75
76
  */
76
- setSyncServerUrl(serverUrl: string): Promise<void>;
77
- /**
78
- * Sets the sync state, this will initiate the sync manager instance.
79
- * @param syncState - The state of the sync.
80
- */
81
- private setSyncState;
77
+ setSyncServerUrl(serverUrl: string, force?: boolean): Promise<void>;
82
78
  /**
83
79
  * Dispatches a library event.
84
80
  * @param eventType - The type of the event to dispatch, uses the TwoFaLibEvent enum.
@@ -1,8 +1,8 @@
1
1
  import { TypedEventTarget } from 'typescript-event-target';
2
2
  import TwoFaLibMediator from './TwoFaLibMediator.mjs';
3
3
  import { TwoFaLibEvent } from './TwoFaLibEvent.mjs';
4
- import { InitializationError } from './TwoFALibError.mjs';
5
- import SyncManager from './subclasses/SyncManager.mjs';
4
+ import { InitializationError, SyncError } from './TwoFALibError.mjs';
5
+ import SyncManager, { ConnectionStatus } from './subclasses/SyncManager.mjs';
6
6
  import LibraryLoader from './subclasses/LibraryLoader.mjs';
7
7
  import ExportImportManager from './subclasses/ExportImportManager.mjs';
8
8
  import PersistentStorageManager from './subclasses/PersistentStorageManager.mjs';
@@ -72,7 +72,8 @@ class TwoFaLib extends TypedEventTarget {
72
72
  this.mediator.getComponent('vaultDataManager').replaceVault(vault);
73
73
  }
74
74
  if (syncState?.serverUrl) {
75
- this.setSyncState(syncState);
75
+ // Initiate the syncManager
76
+ this.mediator.registerComponent('syncManager', new SyncManager(this.mediator, this.deviceType, this.publicKey, this.privateKey, syncState, this.deviceId));
76
77
  }
77
78
  else {
78
79
  // If no syncmanager we're ready now, otherwise the syncmanager is responsible for emitting the ready event
@@ -139,27 +140,47 @@ class TwoFaLib extends TypedEventTarget {
139
140
  /**
140
141
  * Sets a server url, this will allow syncing with the server.
141
142
  * @param serverUrl - The server url.
143
+ * @param force - Force setting the sync server url, even if no connection can be made
142
144
  */
143
- async setSyncServerUrl(serverUrl) {
145
+ async setSyncServerUrl(serverUrl, force = false) {
144
146
  if (this.sync) {
147
+ // close connection so no data is send to the old syncServer
145
148
  this.sync.closeServerConnection();
146
- this.mediator.unRegisterComponent('syncManager');
147
149
  }
148
150
  const newSyncState = {
149
151
  serverUrl,
150
152
  devices: [],
151
153
  commandSendQueue: [],
152
154
  };
153
- this.setSyncState(newSyncState);
155
+ const newSyncManager = new SyncManager(this.mediator, this.deviceType, this.publicKey, this.privateKey, newSyncState, this.deviceId);
156
+ const success = await new Promise((resolve) => {
157
+ this.addEventListener(TwoFaLibEvent.ConnectionToSyncServerStatusChanged, (event) => {
158
+ if (event.detail.newStatus === ConnectionStatus.CONNECTED) {
159
+ resolve(true);
160
+ }
161
+ if (event.detail.newStatus === ConnectionStatus.FAILED) {
162
+ resolve(false);
163
+ }
164
+ });
165
+ });
166
+ if (!success) {
167
+ if (force) {
168
+ this.log('warning', `Failed to connect to server at ${serverUrl}, force setting`);
169
+ }
170
+ else {
171
+ if (this.sync) {
172
+ // re-establish old connection
173
+ this.sync.initServerConnection();
174
+ }
175
+ newSyncManager.closeServerConnection();
176
+ throw new SyncError(`Failed to connect to server at ${serverUrl}, not setting`);
177
+ }
178
+ }
179
+ // connection succeeded (or force=true), switch to the new syncManager
180
+ this.mediator.unRegisterComponent('syncManager');
181
+ this.mediator.registerComponent('syncManager', newSyncManager);
154
182
  await this.forceSave();
155
183
  }
156
- /**
157
- * Sets the sync state, this will initiate the sync manager instance.
158
- * @param syncState - The state of the sync.
159
- */
160
- setSyncState(syncState) {
161
- this.mediator.registerComponent('syncManager', new SyncManager(this.mediator, this.deviceType, this.publicKey, this.privateKey, syncState, this.deviceId));
162
- }
163
184
  /**
164
185
  * Dispatches a library event.
165
186
  * @param eventType - The type of the event to dispatch, uses the TwoFaLibEvent enum.
@@ -1,6 +1,7 @@
1
1
  import type { EmptyObject } from 'type-fest';
2
2
  import type { TwoFaLibEvent } from '../TwoFaLibEvent.mjs';
3
3
  import type { LockedRepresentationString } from './Vault.mjs';
4
+ import type { ConnectionStatus } from '../subclasses/SyncManager.mjs';
4
5
  export interface ChangedEvent {
5
6
  newLockedRepresentationString: LockedRepresentationString;
6
7
  }
@@ -9,7 +10,7 @@ export interface TwoFaLibEventMap {
9
10
  [TwoFaLibEvent.LoadedFromLockedRepresentation]: EmptyObject;
10
11
  [TwoFaLibEvent.ConnectToExistingVaultFinished]: EmptyObject;
11
12
  [TwoFaLibEvent.ConnectionToSyncServerStatusChanged]: {
12
- connected: boolean;
13
+ newStatus: ConnectionStatus;
13
14
  };
14
15
  [TwoFaLibEvent.Log]: {
15
16
  severity: 'info' | 'warning';
@@ -6,6 +6,12 @@ import { VaultSyncStateWithServerUrl } from '../interfaces/Vault.mjs';
6
6
  import { SyncCommandFromServer } from 'favaserver/ServerMessage';
7
7
  import { SyncCommandFromClient } from 'favaserver/ClientMessage';
8
8
  import type { AddSyncDeviceData } from '../Command/commands/AddSyncDeviceCommand.mjs';
9
+ export declare enum ConnectionStatus {
10
+ CONNECTING = 0,
11
+ CONNECTED = 1,
12
+ NOT_CONNECTED = 2,
13
+ FAILED = 3
14
+ }
9
15
  /**
10
16
  * Manages synchronization of 2FA devices and communication with the server.
11
17
  */
@@ -24,6 +30,7 @@ declare class SyncManager {
24
30
  private commandSendQueue;
25
31
  private reconnectTimeout?;
26
32
  private terminateTimeout?;
33
+ private connectionFailedTimeout?;
27
34
  private shouldReconnect;
28
35
  /**
29
36
  * Public getter for the command send queue.
@@ -7,6 +7,13 @@ import { InitializationError, SyncAddDeviceFlowConflictError, SyncError, SyncInW
7
7
  import AddSyncDeviceCommand from '../Command/commands/AddSyncDeviceCommand.mjs';
8
8
  const IN_TESTING = process.env.NODE_ENV === 'test';
9
9
  const IN_DEV = process.env.NODE_ENV === 'development';
10
+ export var ConnectionStatus;
11
+ (function (ConnectionStatus) {
12
+ ConnectionStatus[ConnectionStatus["CONNECTING"] = 0] = "CONNECTING";
13
+ ConnectionStatus[ConnectionStatus["CONNECTED"] = 1] = "CONNECTED";
14
+ ConnectionStatus[ConnectionStatus["NOT_CONNECTED"] = 2] = "NOT_CONNECTED";
15
+ ConnectionStatus[ConnectionStatus["FAILED"] = 3] = "FAILED";
16
+ })(ConnectionStatus || (ConnectionStatus = {}));
10
17
  const generateNonCryptographicRandomString = () => {
11
18
  const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
12
19
  const length = Math.floor(Math.random() * 64) + 1;
@@ -53,6 +60,16 @@ class SyncManager {
53
60
  this.commandSendQueue = commandSendQueue;
54
61
  this.serverUrl = serverUrl;
55
62
  this.initServerConnection();
63
+ // if not yet connected after 2 tries, emit ready event so we can continue
64
+ this.connectionFailedTimeout = setTimeout(() => {
65
+ if (!this.readyEventEmitted && !this.webSocketConnected) {
66
+ this.log('warning', 'Failed to connect to sync backend');
67
+ this.dispatchLibEvent(TwoFaLibEvent.Ready);
68
+ this.dispatchLibEvent(TwoFaLibEvent.ConnectionToSyncServerStatusChanged, {
69
+ newStatus: ConnectionStatus.FAILED,
70
+ });
71
+ }
72
+ }, this.reconnectInterval + 1000);
56
73
  }
57
74
  get libraryLoader() {
58
75
  return this.mediator.getComponent('libraryLoader');
@@ -123,8 +140,10 @@ class SyncManager {
123
140
  this.log('info', 'Connected to server.');
124
141
  this.sendToServer('connect', { deviceId: syncManager.deviceId });
125
142
  this.dispatchLibEvent(TwoFaLibEvent.ConnectionToSyncServerStatusChanged, {
126
- connected: true,
143
+ newStatus: ConnectionStatus.CONNECTED,
127
144
  });
145
+ clearTimeout(this.connectionFailedTimeout);
146
+ this.connectionFailedTimeout = undefined;
128
147
  // send any commands that were done while offline
129
148
  void this.processCommandSendQueue();
130
149
  });
@@ -132,15 +151,18 @@ class SyncManager {
132
151
  this.ws = ws;
133
152
  }
134
153
  handleWebSocketClose(event) {
135
- this.dispatchLibEvent(TwoFaLibEvent.ConnectionToSyncServerStatusChanged, {
136
- connected: false,
137
- });
138
154
  if (this.shouldReconnect) {
155
+ this.dispatchLibEvent(TwoFaLibEvent.ConnectionToSyncServerStatusChanged, {
156
+ newStatus: ConnectionStatus.CONNECTING,
157
+ });
139
158
  // if we shouldn't reconnect, this closing is expected
140
159
  this.log('warning', `WebSocket closed: ${event.code} ${event.reason}`);
141
160
  this.attemptReconnect();
142
161
  }
143
162
  else {
163
+ this.dispatchLibEvent(TwoFaLibEvent.ConnectionToSyncServerStatusChanged, {
164
+ newStatus: ConnectionStatus.NOT_CONNECTED,
165
+ });
144
166
  // Connection closed, no need to force terminate
145
167
  if (this.terminateTimeout) {
146
168
  clearTimeout(this.terminateTimeout);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "favalib",
3
- "version": "0.0.2",
3
+ "version": "0.0.4",
4
4
  "description": "",
5
5
  "type": "module",
6
6
  "exports": {