@keyringnetwork/keyring-connect-sdk 3.2.0 → 4.1.0-alpha.2
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/core/VerificationSession.d.ts +62 -0
- package/dist/core/VerificationSession.js +404 -0
- package/dist/core/errors.d.ts +49 -0
- package/dist/core/errors.js +81 -0
- package/dist/core/htppClient.d.ts +17 -0
- package/dist/core/htppClient.js +160 -0
- package/dist/{main.d.ts → core/keyringConnectExtension.d.ts} +6 -4
- package/dist/{main.js → core/keyringConnectExtension.js} +33 -16
- package/dist/core/websocketClient.d.ts +23 -0
- package/dist/core/websocketClient.js +220 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.js +3 -3
- package/dist/types/api.d.ts +24 -0
- package/dist/types/api.js +2 -0
- package/dist/types/core.d.ts +78 -0
- package/dist/types/core.js +2 -0
- package/dist/{types.d.ts → types/extension.d.ts} +2 -49
- package/dist/types/index.d.ts +4 -0
- package/dist/types/index.js +20 -0
- package/dist/types/websocket.d.ts +68 -0
- package/dist/types/websocket.js +3 -0
- package/dist/ui/UIManager.d.ts +52 -0
- package/dist/ui/UIManager.js +257 -0
- package/dist/ui/components/keyring-button.d.ts +12 -0
- package/dist/ui/components/keyring-button.js +140 -0
- package/dist/ui/components/keyring-text.d.ts +12 -0
- package/dist/ui/components/keyring-text.js +169 -0
- package/dist/ui/composites/keyring-complete-modal.d.ts +19 -0
- package/dist/ui/composites/keyring-complete-modal.js +200 -0
- package/dist/ui/composites/keyring-mobile-modal.d.ts +25 -0
- package/dist/ui/composites/keyring-mobile-modal.js +308 -0
- package/dist/ui/composites/keyring-qr-modal.d.ts +32 -0
- package/dist/ui/composites/keyring-qr-modal.js +464 -0
- package/dist/ui/composites/keyring-selection-modal.d.ts +25 -0
- package/dist/ui/composites/keyring-selection-modal.js +342 -0
- package/dist/ui/composites/keyring-terminated-modal.d.ts +14 -0
- package/dist/ui/composites/keyring-terminated-modal.js +121 -0
- package/dist/ui/icons/apple-icon.d.ts +4 -0
- package/dist/ui/icons/apple-icon.js +47 -0
- package/dist/ui/icons/check-circle.d.ts +4 -0
- package/dist/ui/icons/check-circle.js +35 -0
- package/dist/ui/icons/checkmark.d.ts +4 -0
- package/dist/ui/icons/checkmark.js +37 -0
- package/dist/ui/icons/close-circle.d.ts +4 -0
- package/dist/ui/icons/close-circle.js +35 -0
- package/dist/ui/icons/error-circle.d.ts +4 -0
- package/dist/ui/icons/error-circle.js +48 -0
- package/dist/ui/icons/extension-grid.d.ts +4 -0
- package/dist/ui/icons/extension-grid.js +79 -0
- package/dist/ui/icons/google-icon.d.ts +4 -0
- package/dist/ui/icons/google-icon.js +55 -0
- package/dist/ui/icons/gradient-donut.d.ts +8 -0
- package/dist/ui/icons/gradient-donut.js +85 -0
- package/dist/ui/icons/keyring.d.ts +4 -0
- package/dist/ui/icons/keyring.js +71 -0
- package/dist/ui/icons/mobile-grid.d.ts +4 -0
- package/dist/ui/icons/mobile-grid.js +68 -0
- package/dist/ui/icons/success.d.ts +4 -0
- package/dist/ui/icons/success.js +54 -0
- package/dist/utils/environment.d.ts +34 -0
- package/dist/utils/environment.js +67 -0
- package/dist/utils/logger.d.ts +7 -0
- package/dist/utils/logger.js +40 -0
- package/dist/utils/platformUtils.d.ts +24 -0
- package/dist/utils/platformUtils.js +64 -0
- package/package.json +29 -14
- package/readme.md +57 -105
- /package/dist/{types.js → types/extension.js} +0 -0
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { CredentialData, SDKEventData, SDKEventTypes, SessionConfig } from "../types";
|
|
2
|
+
/**
|
|
3
|
+
* Represents an active verification session with proper cleanup
|
|
4
|
+
*/
|
|
5
|
+
export declare class VerificationSession {
|
|
6
|
+
private readonly sessionConfig;
|
|
7
|
+
private static activeSession;
|
|
8
|
+
private static staticLogger;
|
|
9
|
+
private logger;
|
|
10
|
+
private httpClient?;
|
|
11
|
+
private wsClient?;
|
|
12
|
+
private sessionResponse?;
|
|
13
|
+
private stopPolling?;
|
|
14
|
+
private uiManager?;
|
|
15
|
+
private eventListeners;
|
|
16
|
+
private isWebsocketActive;
|
|
17
|
+
private resultPromise?;
|
|
18
|
+
private resolveResult?;
|
|
19
|
+
private rejectResult?;
|
|
20
|
+
private constructor();
|
|
21
|
+
/**
|
|
22
|
+
* Launch verification flow with automatic platform detection and fallback
|
|
23
|
+
*/
|
|
24
|
+
static launch(sessionConfig: SessionConfig): Promise<VerificationSession>;
|
|
25
|
+
/**
|
|
26
|
+
* Start the verification session
|
|
27
|
+
*/
|
|
28
|
+
start(): Promise<CredentialData | null>;
|
|
29
|
+
get sessionId(): string | undefined;
|
|
30
|
+
get sessionToken(): string | undefined;
|
|
31
|
+
/**
|
|
32
|
+
* Add event listener for verification events
|
|
33
|
+
*/
|
|
34
|
+
addEventListener<T extends SDKEventTypes>(eventType: T, listener: (data: SDKEventData[T]) => void): void;
|
|
35
|
+
/**
|
|
36
|
+
* Remove event listener
|
|
37
|
+
*/
|
|
38
|
+
removeEventListener<T extends SDKEventTypes>(eventType: T, listener: (data: SDKEventData[T]) => void): void;
|
|
39
|
+
/**
|
|
40
|
+
* Clean up all resources
|
|
41
|
+
*/
|
|
42
|
+
close(): Promise<void>;
|
|
43
|
+
private complete;
|
|
44
|
+
private fail;
|
|
45
|
+
/**
|
|
46
|
+
* Initialize mobile-specific resources (HTTP client, session, WebSocket)
|
|
47
|
+
*/
|
|
48
|
+
private initializeMobileResources;
|
|
49
|
+
private setupWebSocket;
|
|
50
|
+
private setupUI;
|
|
51
|
+
private showQR;
|
|
52
|
+
private showMobileModal;
|
|
53
|
+
/**
|
|
54
|
+
* Start HTTP polling as fallback when WebSocket is disconnected
|
|
55
|
+
*/
|
|
56
|
+
private startPollingFallback;
|
|
57
|
+
/**
|
|
58
|
+
* Handle session updates from HTTP polling
|
|
59
|
+
*/
|
|
60
|
+
private handlePollingUpdate;
|
|
61
|
+
private emit;
|
|
62
|
+
}
|
|
@@ -0,0 +1,404 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
3
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
4
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
5
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
6
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
7
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
8
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
9
|
+
});
|
|
10
|
+
};
|
|
11
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
+
exports.VerificationSession = void 0;
|
|
13
|
+
const UIManager_1 = require("../ui/UIManager");
|
|
14
|
+
const logger_1 = require("../utils/logger");
|
|
15
|
+
const platformUtils_1 = require("../utils/platformUtils");
|
|
16
|
+
const errors_1 = require("./errors");
|
|
17
|
+
const htppClient_1 = require("./htppClient");
|
|
18
|
+
const keyringConnectExtension_1 = require("./keyringConnectExtension");
|
|
19
|
+
const websocketClient_1 = require("./websocketClient");
|
|
20
|
+
/**
|
|
21
|
+
* Represents an active verification session with proper cleanup
|
|
22
|
+
*/
|
|
23
|
+
class VerificationSession {
|
|
24
|
+
constructor(sessionConfig) {
|
|
25
|
+
this.sessionConfig = sessionConfig;
|
|
26
|
+
this.eventListeners = new Map();
|
|
27
|
+
this.isWebsocketActive = false;
|
|
28
|
+
// Initialize with basic logger, will be updated with sessionId after session is created
|
|
29
|
+
this.logger = logger_1.KeyringLogger.init("VerificationSession").child({
|
|
30
|
+
policyId: sessionConfig.policy_id,
|
|
31
|
+
name: sessionConfig.name,
|
|
32
|
+
});
|
|
33
|
+
this.logger.debug("Creating verification session");
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Launch verification flow with automatic platform detection and fallback
|
|
37
|
+
*/
|
|
38
|
+
static launch(sessionConfig) {
|
|
39
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
40
|
+
this.staticLogger.info({ sessionConfig }, "Launching verification flow");
|
|
41
|
+
try {
|
|
42
|
+
// Clean up any existing session
|
|
43
|
+
if (this.activeSession) {
|
|
44
|
+
this.staticLogger.debug("Cleaning up existing session");
|
|
45
|
+
this.activeSession.close();
|
|
46
|
+
this.activeSession = null;
|
|
47
|
+
}
|
|
48
|
+
// Ensure online connectivity
|
|
49
|
+
if (!(yield platformUtils_1.PlatformUtils.waitForOnline(5000))) {
|
|
50
|
+
throw new errors_1.KeyringError(errors_1.KeyringErrorCode.NETWORK_ERROR, "No internet connection available");
|
|
51
|
+
}
|
|
52
|
+
// Create and start verification session
|
|
53
|
+
this.activeSession = new VerificationSession(sessionConfig);
|
|
54
|
+
return this.activeSession;
|
|
55
|
+
}
|
|
56
|
+
catch (error) {
|
|
57
|
+
this.staticLogger.error(error, "Launch failed");
|
|
58
|
+
const keyringError = errors_1.KeyringError.fromUnknown(error);
|
|
59
|
+
throw keyringError;
|
|
60
|
+
}
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Start the verification session
|
|
65
|
+
*/
|
|
66
|
+
start() {
|
|
67
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
68
|
+
this.logger.debug("Starting verification session");
|
|
69
|
+
// Create result promise that will be resolved when verification completes
|
|
70
|
+
this.resultPromise = new Promise((resolve, reject) => {
|
|
71
|
+
this.resolveResult = resolve;
|
|
72
|
+
this.rejectResult = reject;
|
|
73
|
+
});
|
|
74
|
+
try {
|
|
75
|
+
// Setup UI - this will handle mobile/extension branching
|
|
76
|
+
yield this.setupUI();
|
|
77
|
+
// Return promise that resolves when verification completes
|
|
78
|
+
return this.resultPromise;
|
|
79
|
+
}
|
|
80
|
+
catch (error) {
|
|
81
|
+
this.close();
|
|
82
|
+
throw errors_1.KeyringError.fromUnknown(error, { sessionId: this.sessionId });
|
|
83
|
+
}
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
get sessionId() {
|
|
87
|
+
var _a;
|
|
88
|
+
return (_a = this.sessionResponse) === null || _a === void 0 ? void 0 : _a.session_id;
|
|
89
|
+
}
|
|
90
|
+
get sessionToken() {
|
|
91
|
+
var _a;
|
|
92
|
+
return (_a = this.sessionResponse) === null || _a === void 0 ? void 0 : _a.session_token;
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* Add event listener for verification events
|
|
96
|
+
*/
|
|
97
|
+
addEventListener(eventType, listener) {
|
|
98
|
+
if (!this.eventListeners.has(eventType)) {
|
|
99
|
+
this.eventListeners.set(eventType, []);
|
|
100
|
+
}
|
|
101
|
+
this.eventListeners.get(eventType).push(listener);
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* Remove event listener
|
|
105
|
+
*/
|
|
106
|
+
removeEventListener(eventType, listener) {
|
|
107
|
+
const listeners = this.eventListeners.get(eventType);
|
|
108
|
+
if (listeners) {
|
|
109
|
+
const index = listeners.indexOf(listener);
|
|
110
|
+
if (index > -1) {
|
|
111
|
+
listeners.splice(index, 1);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* Clean up all resources
|
|
117
|
+
*/
|
|
118
|
+
close() {
|
|
119
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
120
|
+
this.logger.debug("Cleaning up verification session");
|
|
121
|
+
// Stop polling if active
|
|
122
|
+
if (this.stopPolling) {
|
|
123
|
+
this.stopPolling();
|
|
124
|
+
this.stopPolling = undefined;
|
|
125
|
+
}
|
|
126
|
+
// Clean up WebSocket (mobile only)
|
|
127
|
+
if (this.wsClient) {
|
|
128
|
+
this.isWebsocketActive = false;
|
|
129
|
+
this.wsClient.disconnect();
|
|
130
|
+
this.wsClient = undefined;
|
|
131
|
+
}
|
|
132
|
+
// Clean up HTTP session (mobile only)
|
|
133
|
+
if (this.httpClient && this.sessionId && this.sessionToken) {
|
|
134
|
+
this.logger.debug({ sessionId: this.sessionId }, "Deleting session from backend");
|
|
135
|
+
yield this.httpClient.closeSession(this.sessionId, this.sessionToken);
|
|
136
|
+
}
|
|
137
|
+
// Clear event listeners
|
|
138
|
+
this.eventListeners.clear();
|
|
139
|
+
// Reject result promise if still pending
|
|
140
|
+
if (this.rejectResult) {
|
|
141
|
+
const error = new errors_1.KeyringError(errors_1.KeyringErrorCode.USER_CANCELLED, "Session was cleaned up");
|
|
142
|
+
this.rejectResult(error);
|
|
143
|
+
this.resolveResult = undefined;
|
|
144
|
+
this.rejectResult = undefined;
|
|
145
|
+
}
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
complete(result) {
|
|
149
|
+
var _a;
|
|
150
|
+
this.logger.debug("Session completed successfully");
|
|
151
|
+
(_a = this.resolveResult) === null || _a === void 0 ? void 0 : _a.call(this, result);
|
|
152
|
+
this.close();
|
|
153
|
+
}
|
|
154
|
+
fail(error) {
|
|
155
|
+
var _a;
|
|
156
|
+
this.logger.error(error, "Session failed");
|
|
157
|
+
(_a = this.rejectResult) === null || _a === void 0 ? void 0 : _a.call(this, error);
|
|
158
|
+
this.close();
|
|
159
|
+
}
|
|
160
|
+
/**
|
|
161
|
+
* Initialize mobile-specific resources (HTTP client, session, WebSocket)
|
|
162
|
+
*/
|
|
163
|
+
initializeMobileResources() {
|
|
164
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
165
|
+
// Initialize HTTP client
|
|
166
|
+
this.httpClient = htppClient_1.HttpClient.initializeHttpClient(this.sessionConfig.api_key);
|
|
167
|
+
// Create backend session
|
|
168
|
+
this.sessionResponse = yield this.httpClient.createSession(this.sessionConfig);
|
|
169
|
+
// Update logger with sessionId now that we have it
|
|
170
|
+
this.logger = this.logger.child({
|
|
171
|
+
sessionId: this.sessionResponse.session_id,
|
|
172
|
+
});
|
|
173
|
+
this.logger.debug({
|
|
174
|
+
sessionId: this.sessionResponse.session_id,
|
|
175
|
+
status: this.sessionResponse.status,
|
|
176
|
+
}, "Mobile session created");
|
|
177
|
+
});
|
|
178
|
+
}
|
|
179
|
+
setupWebSocket() {
|
|
180
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
181
|
+
var _a;
|
|
182
|
+
if (!((_a = this.sessionResponse) === null || _a === void 0 ? void 0 : _a.session_id)) {
|
|
183
|
+
throw new errors_1.KeyringError(errors_1.KeyringErrorCode.SESSION_NOT_FOUND, "Session not initialized for mobile flow");
|
|
184
|
+
}
|
|
185
|
+
const wsCallbacks = {
|
|
186
|
+
onConnectionStatusChanged: (connected) => {
|
|
187
|
+
var _a;
|
|
188
|
+
this.emit("connectionStatusChanged", connected);
|
|
189
|
+
if (connected) {
|
|
190
|
+
(_a = this.uiManager) === null || _a === void 0 ? void 0 : _a.updateStatus("Connected to Keyring app", "connected");
|
|
191
|
+
// Stop polling if WebSocket reconnects
|
|
192
|
+
if (this.stopPolling) {
|
|
193
|
+
this.logger.debug("WebSocket reconnected, stopping polling");
|
|
194
|
+
this.stopPolling();
|
|
195
|
+
this.stopPolling = undefined;
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
else {
|
|
199
|
+
// Only start polling if session is still active (not intentionally closed)
|
|
200
|
+
if (this.isWebsocketActive) {
|
|
201
|
+
// WebSocket disconnected, start polling as fallback
|
|
202
|
+
this.startPollingFallback();
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
},
|
|
206
|
+
onMobileConnected: (data) => {
|
|
207
|
+
var _a;
|
|
208
|
+
this.emit("mobileConnected", data);
|
|
209
|
+
(_a = this.uiManager) === null || _a === void 0 ? void 0 : _a.updateStatus("Connected to mobile app", "connected");
|
|
210
|
+
},
|
|
211
|
+
onProcessingStarted: (data) => {
|
|
212
|
+
var _a;
|
|
213
|
+
this.logger.debug(data, "Processing started");
|
|
214
|
+
(_a = this.uiManager) === null || _a === void 0 ? void 0 : _a.updateStatus("Processing verification...", "processing");
|
|
215
|
+
this.emit("processingStarted", data);
|
|
216
|
+
},
|
|
217
|
+
onProcessingCompleted: (data) => {
|
|
218
|
+
var _a, _b;
|
|
219
|
+
this.logger.debug(data, "Processing completed");
|
|
220
|
+
if (!data.result.credential_data) {
|
|
221
|
+
(_a = this.uiManager) === null || _a === void 0 ? void 0 : _a.updateStatus("Verification failed", "failed");
|
|
222
|
+
this.fail(new errors_1.KeyringError(errors_1.KeyringErrorCode.PROCESSING_FAILED, "Processing failed"));
|
|
223
|
+
return;
|
|
224
|
+
}
|
|
225
|
+
else {
|
|
226
|
+
(_b = this.uiManager) === null || _b === void 0 ? void 0 : _b.showCompleteModal(data.result.entity_type, data.result.datasourceObject);
|
|
227
|
+
this.emit("processingCompleted", data);
|
|
228
|
+
this.complete(data.result.credential_data);
|
|
229
|
+
}
|
|
230
|
+
},
|
|
231
|
+
onSessionExpired: (data) => {
|
|
232
|
+
var _a, _b;
|
|
233
|
+
(_a = this.uiManager) === null || _a === void 0 ? void 0 : _a.updateStatus("Session expired", "expired");
|
|
234
|
+
this.emit("sessionExpired", data);
|
|
235
|
+
(_b = this.uiManager) === null || _b === void 0 ? void 0 : _b.showTerminatedModal();
|
|
236
|
+
this.fail(new errors_1.KeyringError(errors_1.KeyringErrorCode.SESSION_EXPIRED, "Verification session has expired"));
|
|
237
|
+
},
|
|
238
|
+
onError: (data) => {
|
|
239
|
+
var _a;
|
|
240
|
+
this.logger.error(data, "WebSocket error");
|
|
241
|
+
this.emit("error", data);
|
|
242
|
+
const errorMessage = typeof data === "object" && "message" in data ? data.message : data;
|
|
243
|
+
(_a = this.uiManager) === null || _a === void 0 ? void 0 : _a.updateStatus("Verification failed", "failed");
|
|
244
|
+
this.fail(errors_1.KeyringError.fromUnknown(errorMessage, { sessionId: this.sessionId }));
|
|
245
|
+
},
|
|
246
|
+
};
|
|
247
|
+
this.wsClient = new websocketClient_1.KeyringWebSocketClient(this.sessionResponse.session_id, this.sessionResponse.session_token, wsCallbacks);
|
|
248
|
+
this.isWebsocketActive = true;
|
|
249
|
+
yield this.wsClient.connect();
|
|
250
|
+
});
|
|
251
|
+
}
|
|
252
|
+
setupUI() {
|
|
253
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
254
|
+
this.uiManager = new UIManager_1.UIManager();
|
|
255
|
+
this.uiManager.setCallbacks(
|
|
256
|
+
// onCancel
|
|
257
|
+
() => {
|
|
258
|
+
this.fail(new errors_1.KeyringError(errors_1.KeyringErrorCode.USER_CANCELLED, "User cancelled verification"));
|
|
259
|
+
},
|
|
260
|
+
// onExtensionSelected
|
|
261
|
+
() => __awaiter(this, void 0, void 0, function* () {
|
|
262
|
+
var _a;
|
|
263
|
+
try {
|
|
264
|
+
this.logger.debug("Extension selected");
|
|
265
|
+
const result = yield keyringConnectExtension_1.KeyringConnectExtension.launchExtension(this.sessionConfig);
|
|
266
|
+
this.complete(result);
|
|
267
|
+
}
|
|
268
|
+
catch (error) {
|
|
269
|
+
this.fail(new errors_1.KeyringError(errors_1.KeyringErrorCode.PROCESSING_FAILED, (_a = error.message) !== null && _a !== void 0 ? _a : "Extension launch failed"));
|
|
270
|
+
}
|
|
271
|
+
}),
|
|
272
|
+
// onMobileSelected
|
|
273
|
+
() => __awaiter(this, void 0, void 0, function* () {
|
|
274
|
+
var _a;
|
|
275
|
+
try {
|
|
276
|
+
this.logger.debug("Mobile selected");
|
|
277
|
+
// Initialize mobile resources when mobile is selected
|
|
278
|
+
yield this.initializeMobileResources();
|
|
279
|
+
// Setup WebSocket connection
|
|
280
|
+
yield this.setupWebSocket();
|
|
281
|
+
// Show QR code
|
|
282
|
+
this.showQR();
|
|
283
|
+
}
|
|
284
|
+
catch (error) {
|
|
285
|
+
this.logger.error({ error }, "Failed to initialize mobile flow");
|
|
286
|
+
this.fail(new errors_1.KeyringError(errors_1.KeyringErrorCode.SESSION_CREATION_FAILED, (_a = error.message) !== null && _a !== void 0 ? _a : "Failed to initialize mobile flow"));
|
|
287
|
+
}
|
|
288
|
+
}));
|
|
289
|
+
// Show appropriate UI based on platform
|
|
290
|
+
const platform = platformUtils_1.PlatformUtils.detectPlatform();
|
|
291
|
+
if (platform.isDesktop) {
|
|
292
|
+
this.uiManager.showSelectionModal();
|
|
293
|
+
}
|
|
294
|
+
else {
|
|
295
|
+
// For mobile platforms, initialize mobile resources immediately
|
|
296
|
+
yield this.initializeMobileResources();
|
|
297
|
+
yield this.setupWebSocket();
|
|
298
|
+
this.showMobileModal();
|
|
299
|
+
}
|
|
300
|
+
});
|
|
301
|
+
}
|
|
302
|
+
showQR() {
|
|
303
|
+
if (!this.uiManager || !this.sessionResponse)
|
|
304
|
+
return;
|
|
305
|
+
this.uiManager.showQRModal(this.sessionResponse.qr_code_data, this.sessionResponse.session_id, this.sessionResponse.expires_at);
|
|
306
|
+
}
|
|
307
|
+
showMobileModal() {
|
|
308
|
+
if (!this.uiManager || !this.sessionResponse)
|
|
309
|
+
return;
|
|
310
|
+
this.uiManager.showMobileModal(this.sessionResponse.qr_code_data, this.sessionResponse.expires_at);
|
|
311
|
+
}
|
|
312
|
+
/**
|
|
313
|
+
* Start HTTP polling as fallback when WebSocket is disconnected
|
|
314
|
+
*/
|
|
315
|
+
startPollingFallback() {
|
|
316
|
+
var _a, _b;
|
|
317
|
+
if (!this.httpClient || !((_a = this.sessionResponse) === null || _a === void 0 ? void 0 : _a.session_id)) {
|
|
318
|
+
this.logger.warn("Cannot start polling: httpClient or sessionId not available");
|
|
319
|
+
return;
|
|
320
|
+
}
|
|
321
|
+
// Don't start polling if already active
|
|
322
|
+
if (this.stopPolling) {
|
|
323
|
+
return;
|
|
324
|
+
}
|
|
325
|
+
this.logger.info("Starting HTTP polling fallback");
|
|
326
|
+
(_b = this.uiManager) === null || _b === void 0 ? void 0 : _b.updateStatus("Connection lost, checking status...", "processing");
|
|
327
|
+
this.stopPolling = this.httpClient.startPolling(this.sessionResponse.session_id, this.sessionResponse.session_token, (session) => {
|
|
328
|
+
this.handlePollingUpdate(session);
|
|
329
|
+
}, (error) => {
|
|
330
|
+
this.logger.error({ error }, "Polling error");
|
|
331
|
+
this.fail(new errors_1.KeyringError(errors_1.KeyringErrorCode.NETWORK_ERROR, "Polling failed"));
|
|
332
|
+
}, 2000 // Poll every 2 seconds
|
|
333
|
+
);
|
|
334
|
+
}
|
|
335
|
+
/**
|
|
336
|
+
* Handle session updates from HTTP polling
|
|
337
|
+
*/
|
|
338
|
+
handlePollingUpdate(session) {
|
|
339
|
+
var _a, _b, _c, _d, _e, _f, _g;
|
|
340
|
+
if (!this.isWebsocketActive) {
|
|
341
|
+
return;
|
|
342
|
+
}
|
|
343
|
+
this.logger.debug({ status: session.status, hasResult: !!session.result }, "Received polling update");
|
|
344
|
+
// Handle different session statuses
|
|
345
|
+
switch (session.status) {
|
|
346
|
+
case "processing_completed":
|
|
347
|
+
this.logger.debug("Session completed via polling");
|
|
348
|
+
const result = session.result;
|
|
349
|
+
const hasRequiredData = (result === null || result === void 0 ? void 0 : result.credential_data) && (result === null || result === void 0 ? void 0 : result.entity_type) && (result === null || result === void 0 ? void 0 : result.datasourceObject);
|
|
350
|
+
if (hasRequiredData) {
|
|
351
|
+
(_a = this.uiManager) === null || _a === void 0 ? void 0 : _a.showCompleteModal(result.entity_type, result.datasourceObject);
|
|
352
|
+
this.emit("processingCompleted", {
|
|
353
|
+
result: {
|
|
354
|
+
credential_data: result.credential_data,
|
|
355
|
+
entity_type: result.entity_type,
|
|
356
|
+
datasourceObject: result.datasourceObject,
|
|
357
|
+
},
|
|
358
|
+
status: "success",
|
|
359
|
+
});
|
|
360
|
+
this.complete(result.credential_data);
|
|
361
|
+
}
|
|
362
|
+
else {
|
|
363
|
+
(_b = this.uiManager) === null || _b === void 0 ? void 0 : _b.updateStatus("Verification failed", "failed");
|
|
364
|
+
this.fail(new errors_1.KeyringError(errors_1.KeyringErrorCode.PROCESSING_FAILED, "No credential data in result"));
|
|
365
|
+
}
|
|
366
|
+
break;
|
|
367
|
+
case "processing_failed":
|
|
368
|
+
this.logger.debug("Session failed via polling");
|
|
369
|
+
(_c = this.uiManager) === null || _c === void 0 ? void 0 : _c.updateStatus("Verification failed", "failed");
|
|
370
|
+
this.fail(new errors_1.KeyringError(errors_1.KeyringErrorCode.PROCESSING_FAILED, session.error || "Verification failed"));
|
|
371
|
+
break;
|
|
372
|
+
case "session_expired":
|
|
373
|
+
this.logger.debug("Session expired via polling");
|
|
374
|
+
(_d = this.uiManager) === null || _d === void 0 ? void 0 : _d.showTerminatedModal();
|
|
375
|
+
this.fail(new errors_1.KeyringError(errors_1.KeyringErrorCode.SESSION_EXPIRED, "Verification session has expired"));
|
|
376
|
+
break;
|
|
377
|
+
case "processing_started":
|
|
378
|
+
(_e = this.uiManager) === null || _e === void 0 ? void 0 : _e.updateStatus("Processing verification...", "processing");
|
|
379
|
+
break;
|
|
380
|
+
case "mobile_connected":
|
|
381
|
+
(_f = this.uiManager) === null || _f === void 0 ? void 0 : _f.updateStatus("Mobile app connected...", "connected");
|
|
382
|
+
break;
|
|
383
|
+
case "session_created":
|
|
384
|
+
(_g = this.uiManager) === null || _g === void 0 ? void 0 : _g.updateStatus("Waiting for mobile app...", "connected");
|
|
385
|
+
break;
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
emit(eventType, data) {
|
|
389
|
+
const listeners = this.eventListeners.get(eventType);
|
|
390
|
+
if (listeners) {
|
|
391
|
+
listeners.forEach((listener) => {
|
|
392
|
+
try {
|
|
393
|
+
listener(data);
|
|
394
|
+
}
|
|
395
|
+
catch (error) {
|
|
396
|
+
this.logger.error(error, `Error in event listener for ${eventType}`);
|
|
397
|
+
}
|
|
398
|
+
});
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
exports.VerificationSession = VerificationSession;
|
|
403
|
+
VerificationSession.activeSession = null;
|
|
404
|
+
VerificationSession.staticLogger = logger_1.KeyringLogger.init("VerificationSession");
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Keyring SDK Error Codes
|
|
3
|
+
*/
|
|
4
|
+
export declare enum KeyringErrorCode {
|
|
5
|
+
NETWORK_ERROR = "NETWORK_ERROR",
|
|
6
|
+
SESSION_CREATION_FAILED = "SESSION_CREATION_FAILED",
|
|
7
|
+
WEBSOCKET_CONNECTION_FAILED = "WEBSOCKET_CONNECTION_FAILED",
|
|
8
|
+
EXTENSION_NOT_FOUND = "EXTENSION_NOT_FOUND",
|
|
9
|
+
EXTENSION_TIMEOUT = "EXTENSION_TIMEOUT",
|
|
10
|
+
CHROME_RUNTIME_ERROR = "CHROME_RUNTIME_ERROR",
|
|
11
|
+
EXTENSION_LAUNCH_FAILED = "EXTENSION_LAUNCH_FAILED",
|
|
12
|
+
SESSION_EXPIRED = "SESSION_EXPIRED",
|
|
13
|
+
SESSION_NOT_FOUND = "SESSION_NOT_FOUND",
|
|
14
|
+
INVALID_SESSION_CONFIG = "INVALID_SESSION_CONFIG",
|
|
15
|
+
USER_CANCELLED = "USER_CANCELLED",
|
|
16
|
+
TIMEOUT_ERROR = "TIMEOUT_ERROR",
|
|
17
|
+
QR_DISPLAY_NOT_SUPPORTED = "QR_DISPLAY_NOT_SUPPORTED",
|
|
18
|
+
WEBSOCKET_NOT_SUPPORTED = "WEBSOCKET_NOT_SUPPORTED",
|
|
19
|
+
INVALID_CONFIG = "INVALID_CONFIG",
|
|
20
|
+
MISSING_REQUIRED_FIELD = "MISSING_REQUIRED_FIELD",
|
|
21
|
+
PROCESSING_FAILED = "PROCESSING_FAILED",
|
|
22
|
+
UNKNOWN_ERROR = "UNKNOWN_ERROR"
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Keyring SDK Error class with structured error information
|
|
26
|
+
*/
|
|
27
|
+
export declare class KeyringError extends Error {
|
|
28
|
+
readonly code: KeyringErrorCode;
|
|
29
|
+
readonly cause?: unknown | undefined;
|
|
30
|
+
readonly context?: Record<string, any> | undefined;
|
|
31
|
+
readonly name = "KeyringError";
|
|
32
|
+
readonly timestamp: string;
|
|
33
|
+
constructor(code: KeyringErrorCode, message: string, cause?: unknown | undefined, context?: Record<string, any> | undefined);
|
|
34
|
+
/**
|
|
35
|
+
* Create error from unknown source
|
|
36
|
+
*/
|
|
37
|
+
static fromUnknown(error: unknown, context?: Record<string, any>): KeyringError;
|
|
38
|
+
/**
|
|
39
|
+
* Convert to JSON for logging/debugging
|
|
40
|
+
*/
|
|
41
|
+
toJSON(): {
|
|
42
|
+
name: string;
|
|
43
|
+
code: KeyringErrorCode;
|
|
44
|
+
message: string;
|
|
45
|
+
timestamp: string;
|
|
46
|
+
context: Record<string, any> | undefined;
|
|
47
|
+
cause: unknown;
|
|
48
|
+
};
|
|
49
|
+
}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.KeyringError = exports.KeyringErrorCode = void 0;
|
|
4
|
+
/**
|
|
5
|
+
* Keyring SDK Error Codes
|
|
6
|
+
*/
|
|
7
|
+
var KeyringErrorCode;
|
|
8
|
+
(function (KeyringErrorCode) {
|
|
9
|
+
// Network related errors
|
|
10
|
+
KeyringErrorCode["NETWORK_ERROR"] = "NETWORK_ERROR";
|
|
11
|
+
KeyringErrorCode["SESSION_CREATION_FAILED"] = "SESSION_CREATION_FAILED";
|
|
12
|
+
KeyringErrorCode["WEBSOCKET_CONNECTION_FAILED"] = "WEBSOCKET_CONNECTION_FAILED";
|
|
13
|
+
// Extension related errors
|
|
14
|
+
KeyringErrorCode["EXTENSION_NOT_FOUND"] = "EXTENSION_NOT_FOUND";
|
|
15
|
+
KeyringErrorCode["EXTENSION_TIMEOUT"] = "EXTENSION_TIMEOUT";
|
|
16
|
+
KeyringErrorCode["CHROME_RUNTIME_ERROR"] = "CHROME_RUNTIME_ERROR";
|
|
17
|
+
KeyringErrorCode["EXTENSION_LAUNCH_FAILED"] = "EXTENSION_LAUNCH_FAILED";
|
|
18
|
+
// Session related errors
|
|
19
|
+
KeyringErrorCode["SESSION_EXPIRED"] = "SESSION_EXPIRED";
|
|
20
|
+
KeyringErrorCode["SESSION_NOT_FOUND"] = "SESSION_NOT_FOUND";
|
|
21
|
+
KeyringErrorCode["INVALID_SESSION_CONFIG"] = "INVALID_SESSION_CONFIG";
|
|
22
|
+
// User interaction errors
|
|
23
|
+
KeyringErrorCode["USER_CANCELLED"] = "USER_CANCELLED";
|
|
24
|
+
KeyringErrorCode["TIMEOUT_ERROR"] = "TIMEOUT_ERROR";
|
|
25
|
+
// Platform support errors
|
|
26
|
+
KeyringErrorCode["QR_DISPLAY_NOT_SUPPORTED"] = "QR_DISPLAY_NOT_SUPPORTED";
|
|
27
|
+
KeyringErrorCode["WEBSOCKET_NOT_SUPPORTED"] = "WEBSOCKET_NOT_SUPPORTED";
|
|
28
|
+
// Validation errors
|
|
29
|
+
KeyringErrorCode["INVALID_CONFIG"] = "INVALID_CONFIG";
|
|
30
|
+
KeyringErrorCode["MISSING_REQUIRED_FIELD"] = "MISSING_REQUIRED_FIELD";
|
|
31
|
+
// Processing errors
|
|
32
|
+
KeyringErrorCode["PROCESSING_FAILED"] = "PROCESSING_FAILED";
|
|
33
|
+
KeyringErrorCode["UNKNOWN_ERROR"] = "UNKNOWN_ERROR";
|
|
34
|
+
})(KeyringErrorCode || (exports.KeyringErrorCode = KeyringErrorCode = {}));
|
|
35
|
+
/**
|
|
36
|
+
* Keyring SDK Error class with structured error information
|
|
37
|
+
*/
|
|
38
|
+
class KeyringError extends Error {
|
|
39
|
+
constructor(code, message, cause, context) {
|
|
40
|
+
super(message);
|
|
41
|
+
this.code = code;
|
|
42
|
+
this.cause = cause;
|
|
43
|
+
this.context = context;
|
|
44
|
+
this.name = 'KeyringError';
|
|
45
|
+
this.timestamp = new Date().toISOString();
|
|
46
|
+
// Maintain proper stack trace for debugging
|
|
47
|
+
if (Error.captureStackTrace) {
|
|
48
|
+
Error.captureStackTrace(this, KeyringError);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Create error from unknown source
|
|
53
|
+
*/
|
|
54
|
+
static fromUnknown(error, context) {
|
|
55
|
+
if (error instanceof KeyringError) {
|
|
56
|
+
return error;
|
|
57
|
+
}
|
|
58
|
+
if (error instanceof Error) {
|
|
59
|
+
return new KeyringError(KeyringErrorCode.UNKNOWN_ERROR, error.message, error, context);
|
|
60
|
+
}
|
|
61
|
+
return new KeyringError(KeyringErrorCode.UNKNOWN_ERROR, String(error), error, context);
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Convert to JSON for logging/debugging
|
|
65
|
+
*/
|
|
66
|
+
toJSON() {
|
|
67
|
+
return {
|
|
68
|
+
name: this.name,
|
|
69
|
+
code: this.code,
|
|
70
|
+
message: this.message,
|
|
71
|
+
timestamp: this.timestamp,
|
|
72
|
+
context: this.context,
|
|
73
|
+
cause: this.cause instanceof Error ? {
|
|
74
|
+
name: this.cause.name,
|
|
75
|
+
message: this.cause.message,
|
|
76
|
+
stack: this.cause.stack
|
|
77
|
+
} : this.cause
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
exports.KeyringError = KeyringError;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { CrossDeviceSession, SessionConfig } from "../types/";
|
|
2
|
+
export declare class HttpClient {
|
|
3
|
+
private static instance;
|
|
4
|
+
private httpClient;
|
|
5
|
+
private logger;
|
|
6
|
+
private constructor();
|
|
7
|
+
private setupAxiosInterceptors;
|
|
8
|
+
static initializeHttpClient(apiKey: string): HttpClient;
|
|
9
|
+
createSession(sessionConfig: SessionConfig): Promise<CrossDeviceSession>;
|
|
10
|
+
closeSession(sessionId: string, sessionToken: string): Promise<void>;
|
|
11
|
+
getSession(sessionId: string, sessionToken: string): Promise<CrossDeviceSession | null>;
|
|
12
|
+
/**
|
|
13
|
+
* Start polling for session updates
|
|
14
|
+
* Returns a function to stop polling
|
|
15
|
+
*/
|
|
16
|
+
startPolling(sessionId: string, sessionToken: string, onUpdate: (session: CrossDeviceSession) => void, onError: (error: any) => void, interval?: number): () => void;
|
|
17
|
+
}
|