@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,160 @@
|
|
|
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
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
12
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
13
|
+
};
|
|
14
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
15
|
+
exports.HttpClient = void 0;
|
|
16
|
+
const axios_1 = __importDefault(require("axios"));
|
|
17
|
+
const environment_1 = require("../utils/environment");
|
|
18
|
+
const logger_1 = require("../utils/logger");
|
|
19
|
+
const platformUtils_1 = require("../utils/platformUtils");
|
|
20
|
+
const errors_1 = require("./errors");
|
|
21
|
+
class HttpClient {
|
|
22
|
+
constructor(apiKey) {
|
|
23
|
+
this.logger = logger_1.KeyringLogger.init("HttpClient");
|
|
24
|
+
this.httpClient = axios_1.default.create({
|
|
25
|
+
baseURL: environment_1.Environment.getConfig().apiUrl,
|
|
26
|
+
timeout: environment_1.Environment.getConfig().timeout,
|
|
27
|
+
headers: {
|
|
28
|
+
"Content-Type": "application/json",
|
|
29
|
+
"X-API-KEY": apiKey,
|
|
30
|
+
},
|
|
31
|
+
});
|
|
32
|
+
this.setupAxiosInterceptors();
|
|
33
|
+
this.logger.debug({ apiUrl: environment_1.Environment.getConfig().apiUrl }, "HTTP client initialized");
|
|
34
|
+
HttpClient.instance = this;
|
|
35
|
+
}
|
|
36
|
+
setupAxiosInterceptors() {
|
|
37
|
+
this.httpClient.interceptors.request.use((config) => {
|
|
38
|
+
var _a;
|
|
39
|
+
this.logger.debug({ method: (_a = config.method) === null || _a === void 0 ? void 0 : _a.toUpperCase(), url: config.url }, "HTTP Request");
|
|
40
|
+
return config;
|
|
41
|
+
}, (error) => {
|
|
42
|
+
this.logger.error("HTTP Request failed", error);
|
|
43
|
+
return Promise.reject(errors_1.KeyringError.fromUnknown(error));
|
|
44
|
+
});
|
|
45
|
+
this.httpClient.interceptors.response.use((response) => {
|
|
46
|
+
this.logger.debug({ status: response.status, url: response.config.url }, "HTTP Response");
|
|
47
|
+
return response;
|
|
48
|
+
}, (error) => {
|
|
49
|
+
var _a, _b, _c;
|
|
50
|
+
this.logger.error({
|
|
51
|
+
status: (_a = error.response) === null || _a === void 0 ? void 0 : _a.status,
|
|
52
|
+
url: (_b = error.config) === null || _b === void 0 ? void 0 : _b.url,
|
|
53
|
+
message: error.message || ((_c = error.response) === null || _c === void 0 ? void 0 : _c.data),
|
|
54
|
+
}, "HTTP Response error");
|
|
55
|
+
return Promise.reject(errors_1.KeyringError.fromUnknown(error));
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
static initializeHttpClient(apiKey) {
|
|
59
|
+
if (!this.instance) {
|
|
60
|
+
this.instance = new HttpClient(apiKey);
|
|
61
|
+
}
|
|
62
|
+
return this.instance;
|
|
63
|
+
}
|
|
64
|
+
createSession(sessionConfig) {
|
|
65
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
66
|
+
this.logger.debug("Creating session on backend");
|
|
67
|
+
const response = yield this.httpClient.post("/api/v1/connect/sessions/create", {
|
|
68
|
+
config: sessionConfig,
|
|
69
|
+
origin_url: platformUtils_1.PlatformUtils.isBrowser() ? window.location.href : "server",
|
|
70
|
+
});
|
|
71
|
+
this.logger.debug({ sessionId: response.data.session_id, expiresAt: response.data.expires_at }, "Session creation response received");
|
|
72
|
+
return response.data;
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
closeSession(sessionId, sessionToken) {
|
|
76
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
77
|
+
yield this.httpClient.delete(`/api/v1/connect/sessions/${sessionId}`, {
|
|
78
|
+
headers: {
|
|
79
|
+
"X-SESSION-TOKEN": sessionToken,
|
|
80
|
+
},
|
|
81
|
+
});
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
getSession(sessionId, sessionToken) {
|
|
85
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
86
|
+
var _a, _b, _c;
|
|
87
|
+
this.logger.debug({ sessionId }, "Getting session from backend");
|
|
88
|
+
try {
|
|
89
|
+
const response = yield this.httpClient.get(`/api/v1/connect/sessions/${sessionId}`, {
|
|
90
|
+
headers: {
|
|
91
|
+
"x-session-token": sessionToken,
|
|
92
|
+
},
|
|
93
|
+
});
|
|
94
|
+
// Handle 204 No Content (session deleted/expired)
|
|
95
|
+
if (response.status === 204 || !response.data) {
|
|
96
|
+
this.logger.debug({ sessionId }, "Session not found (204 or empty response)");
|
|
97
|
+
return null;
|
|
98
|
+
}
|
|
99
|
+
this.logger.debug({ sessionId: response.data.session_id, status: response.data.status }, "Session retrieved");
|
|
100
|
+
return response.data;
|
|
101
|
+
}
|
|
102
|
+
catch (error) {
|
|
103
|
+
// Return null for 404 or 401 (session not found or expired)
|
|
104
|
+
if (((_a = error.response) === null || _a === void 0 ? void 0 : _a.status) === 404 || ((_b = error.response) === null || _b === void 0 ? void 0 : _b.status) === 401) {
|
|
105
|
+
this.logger.debug({ sessionId, status: (_c = error.response) === null || _c === void 0 ? void 0 : _c.status }, "Session not found");
|
|
106
|
+
return null;
|
|
107
|
+
}
|
|
108
|
+
// Re-throw other errors
|
|
109
|
+
throw error;
|
|
110
|
+
}
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* Start polling for session updates
|
|
115
|
+
* Returns a function to stop polling
|
|
116
|
+
*/
|
|
117
|
+
startPolling(sessionId, sessionToken, onUpdate, onError, interval = 2000) {
|
|
118
|
+
this.logger.debug({ sessionId, interval }, "Starting session polling");
|
|
119
|
+
let isPolling = true;
|
|
120
|
+
let timeoutId = null;
|
|
121
|
+
const poll = () => __awaiter(this, void 0, void 0, function* () {
|
|
122
|
+
if (!isPolling)
|
|
123
|
+
return;
|
|
124
|
+
try {
|
|
125
|
+
const session = yield this.getSession(sessionId, sessionToken);
|
|
126
|
+
// Stop polling if session not found (expired or deleted)
|
|
127
|
+
if (!session) {
|
|
128
|
+
this.logger.debug({ sessionId }, "Session not found, stopping polling");
|
|
129
|
+
isPolling = false;
|
|
130
|
+
onError(new Error("Session not found or expired"));
|
|
131
|
+
return;
|
|
132
|
+
}
|
|
133
|
+
onUpdate(session);
|
|
134
|
+
// Continue polling if still active
|
|
135
|
+
if (isPolling) {
|
|
136
|
+
timeoutId = setTimeout(poll, interval);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
catch (error) {
|
|
140
|
+
this.logger.error(error, "Polling error");
|
|
141
|
+
onError(error);
|
|
142
|
+
// Continue polling despite errors (network might be temporarily down)
|
|
143
|
+
if (isPolling) {
|
|
144
|
+
timeoutId = setTimeout(poll, interval);
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
});
|
|
148
|
+
// Start polling
|
|
149
|
+
poll();
|
|
150
|
+
// Return stop function
|
|
151
|
+
return () => {
|
|
152
|
+
this.logger.debug({ sessionId }, "Stopping session polling");
|
|
153
|
+
isPolling = false;
|
|
154
|
+
if (timeoutId) {
|
|
155
|
+
clearTimeout(timeoutId);
|
|
156
|
+
}
|
|
157
|
+
};
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
exports.HttpClient = HttpClient;
|
|
@@ -1,11 +1,14 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { CredentialData, ExtensionState, SessionConfig } from "../types";
|
|
2
2
|
/**
|
|
3
3
|
* Class for interacting with the Keyring Connect browser extension
|
|
4
4
|
*/
|
|
5
|
-
export declare class
|
|
5
|
+
export declare class KeyringConnectExtension {
|
|
6
6
|
private static readonly EXTENSION_TIMEOUT;
|
|
7
7
|
private static readonly EXTENSION_ID;
|
|
8
8
|
private static keyringConnectUrl;
|
|
9
|
+
private static resultPromise?;
|
|
10
|
+
private static resolveResult?;
|
|
11
|
+
private static rejectResult?;
|
|
9
12
|
/**
|
|
10
13
|
* Tries to launch the Keyring Connect Extension with the provided configuration.
|
|
11
14
|
* If the extension is not installed, it will redirect to the Keyring Connect page.
|
|
@@ -14,7 +17,7 @@ export declare class KeyringConnect {
|
|
|
14
17
|
* if extension communication times out, or if the extension returns an error.
|
|
15
18
|
* @param data - The configuration for the extension
|
|
16
19
|
*/
|
|
17
|
-
static launchExtension(data:
|
|
20
|
+
static launchExtension(data: SessionConfig): Promise<CredentialData | null>;
|
|
18
21
|
/**
|
|
19
22
|
* Check if Keyring Connect Extension is installed and ready to use
|
|
20
23
|
* @returns Promise that resolves with true if extension is installed and ready to use, false otherwise
|
|
@@ -38,6 +41,5 @@ export declare class KeyringConnect {
|
|
|
38
41
|
private static validateCredentialConfig;
|
|
39
42
|
private static validateConfigData;
|
|
40
43
|
private static convertToLaunchConfig;
|
|
41
|
-
private static isChromeRuntimeAvailable;
|
|
42
44
|
private static sendChromeMessage;
|
|
43
45
|
}
|
|
@@ -9,12 +9,13 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
|
|
|
9
9
|
});
|
|
10
10
|
};
|
|
11
11
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
-
exports.
|
|
13
|
-
const types_1 = require("
|
|
12
|
+
exports.KeyringConnectExtension = void 0;
|
|
13
|
+
const types_1 = require("../types");
|
|
14
|
+
const platformUtils_1 = require("../utils/platformUtils");
|
|
14
15
|
/**
|
|
15
16
|
* Class for interacting with the Keyring Connect browser extension
|
|
16
17
|
*/
|
|
17
|
-
class
|
|
18
|
+
class KeyringConnectExtension {
|
|
18
19
|
/**
|
|
19
20
|
* Tries to launch the Keyring Connect Extension with the provided configuration.
|
|
20
21
|
* If the extension is not installed, it will redirect to the Keyring Connect page.
|
|
@@ -35,14 +36,36 @@ class KeyringConnect {
|
|
|
35
36
|
else {
|
|
36
37
|
throw new Error("Keyring Connect is not installed");
|
|
37
38
|
}
|
|
38
|
-
return;
|
|
39
|
+
return null;
|
|
39
40
|
}
|
|
41
|
+
// Create result promise that will be resolved when verification completes
|
|
42
|
+
this.resultPromise = new Promise((resolve, reject) => {
|
|
43
|
+
this.resolveResult = resolve;
|
|
44
|
+
this.rejectResult = reject;
|
|
45
|
+
});
|
|
40
46
|
const validatedConfig = this.validateConfigData(data);
|
|
41
47
|
const launchConfig = this.convertToLaunchConfig(validatedConfig);
|
|
42
48
|
yield this.sendChromeMessage({
|
|
43
49
|
type: types_1.EVENT_ACTIONS.LAUNCH_KEYRING_CONNECT,
|
|
44
50
|
data: launchConfig,
|
|
45
51
|
});
|
|
52
|
+
const unsubscribe = this.subscribeToExtensionState((state) => {
|
|
53
|
+
var _a, _b, _c;
|
|
54
|
+
if ((state === null || state === void 0 ? void 0 : state.status) === "credential_success") {
|
|
55
|
+
if (!state.credentialData) {
|
|
56
|
+
(_a = this.rejectResult) === null || _a === void 0 ? void 0 : _a.call(this, new Error("No credential data found"));
|
|
57
|
+
}
|
|
58
|
+
else {
|
|
59
|
+
(_b = this.resolveResult) === null || _b === void 0 ? void 0 : _b.call(this, state.credentialData);
|
|
60
|
+
}
|
|
61
|
+
unsubscribe();
|
|
62
|
+
}
|
|
63
|
+
else if (state === null || state === void 0 ? void 0 : state.error) {
|
|
64
|
+
(_c = this.rejectResult) === null || _c === void 0 ? void 0 : _c.call(this, new Error(state.error));
|
|
65
|
+
unsubscribe();
|
|
66
|
+
}
|
|
67
|
+
});
|
|
68
|
+
return this.resultPromise;
|
|
46
69
|
});
|
|
47
70
|
}
|
|
48
71
|
/**
|
|
@@ -66,7 +89,7 @@ class KeyringConnect {
|
|
|
66
89
|
setTimeout(() => resolve(null), this.EXTENSION_TIMEOUT);
|
|
67
90
|
});
|
|
68
91
|
// Check if Chrome is available first
|
|
69
|
-
if (!
|
|
92
|
+
if (!platformUtils_1.PlatformUtils.isChromeRuntimeAvailable()) {
|
|
70
93
|
return null;
|
|
71
94
|
}
|
|
72
95
|
let extensionResponse;
|
|
@@ -153,14 +176,12 @@ class KeyringConnect {
|
|
|
153
176
|
return config;
|
|
154
177
|
}
|
|
155
178
|
static convertToLaunchConfig(config) {
|
|
156
|
-
var _a;
|
|
157
179
|
const launchConfig = {
|
|
158
180
|
client: {
|
|
159
181
|
client_name: config.name,
|
|
160
182
|
client_app_url: config.app_url,
|
|
161
183
|
client_logo_url: config.logo_url,
|
|
162
184
|
client_policy_id: config.policy_id,
|
|
163
|
-
client_entity_id: (_a = config.krn_config) === null || _a === void 0 ? void 0 : _a.entity_id,
|
|
164
185
|
credential_config: config.credential_config,
|
|
165
186
|
},
|
|
166
187
|
krn_config: config.krn_config
|
|
@@ -175,14 +196,10 @@ class KeyringConnect {
|
|
|
175
196
|
};
|
|
176
197
|
return launchConfig;
|
|
177
198
|
}
|
|
178
|
-
static isChromeRuntimeAvailable() {
|
|
179
|
-
var _a;
|
|
180
|
-
return typeof ((_a = chrome === null || chrome === void 0 ? void 0 : chrome.runtime) === null || _a === void 0 ? void 0 : _a.sendMessage) === "function";
|
|
181
|
-
}
|
|
182
199
|
static sendChromeMessage(message) {
|
|
183
200
|
return __awaiter(this, void 0, void 0, function* () {
|
|
184
201
|
return new Promise((resolve, reject) => {
|
|
185
|
-
if (!
|
|
202
|
+
if (!platformUtils_1.PlatformUtils.isChromeRuntimeAvailable()) {
|
|
186
203
|
reject(new Error("Chrome runtime not available"));
|
|
187
204
|
return;
|
|
188
205
|
}
|
|
@@ -218,7 +235,7 @@ class KeyringConnect {
|
|
|
218
235
|
});
|
|
219
236
|
}
|
|
220
237
|
}
|
|
221
|
-
exports.
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
238
|
+
exports.KeyringConnectExtension = KeyringConnectExtension;
|
|
239
|
+
KeyringConnectExtension.EXTENSION_TIMEOUT = 2000;
|
|
240
|
+
KeyringConnectExtension.EXTENSION_ID = "jgogeidclfccfoedhfjjaclnaojcllpi";
|
|
241
|
+
KeyringConnectExtension.keyringConnectUrl = "https://app.keyring.network/connect";
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { SDKCallbacks, WebSocketMessage } from "../types";
|
|
2
|
+
export declare class KeyringWebSocketClient {
|
|
3
|
+
private logger;
|
|
4
|
+
private socket;
|
|
5
|
+
private sessionId;
|
|
6
|
+
private sessionToken;
|
|
7
|
+
private wsUrl;
|
|
8
|
+
private callbacks;
|
|
9
|
+
private reconnectAttempts;
|
|
10
|
+
private maxReconnectAttempts;
|
|
11
|
+
private heartbeatInterval;
|
|
12
|
+
private isConnected;
|
|
13
|
+
constructor(sessionId: string, sessionToken: string, callbacks: SDKCallbacks);
|
|
14
|
+
connect(): Promise<void>;
|
|
15
|
+
private handleMessage;
|
|
16
|
+
sendMessage(message: Partial<WebSocketMessage>): void;
|
|
17
|
+
private startHeartbeat;
|
|
18
|
+
private scheduleReconnect;
|
|
19
|
+
private cleanup;
|
|
20
|
+
disconnect(): void;
|
|
21
|
+
getConnectionState(): string;
|
|
22
|
+
private createError;
|
|
23
|
+
}
|
|
@@ -0,0 +1,220 @@
|
|
|
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.KeyringWebSocketClient = void 0;
|
|
13
|
+
const environment_1 = require("../utils/environment");
|
|
14
|
+
const logger_1 = require("../utils/logger");
|
|
15
|
+
class KeyringWebSocketClient {
|
|
16
|
+
constructor(sessionId, sessionToken, callbacks) {
|
|
17
|
+
this.socket = null;
|
|
18
|
+
this.wsUrl = environment_1.Environment.getConfig().apiUrl;
|
|
19
|
+
this.reconnectAttempts = 0;
|
|
20
|
+
this.maxReconnectAttempts = 5;
|
|
21
|
+
this.heartbeatInterval = null;
|
|
22
|
+
this.isConnected = false;
|
|
23
|
+
this.sessionId = sessionId;
|
|
24
|
+
this.sessionToken = sessionToken;
|
|
25
|
+
this.callbacks = callbacks;
|
|
26
|
+
this.logger = logger_1.KeyringLogger.init("KeyringWebSocketClient").child({
|
|
27
|
+
sessionId,
|
|
28
|
+
wsUrl: this.wsUrl,
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
connect() {
|
|
32
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
33
|
+
return new Promise((resolve, reject) => {
|
|
34
|
+
try {
|
|
35
|
+
// Convert HTTP/HTTPS URL to WS/WSS for native WebSocket
|
|
36
|
+
const wsUrl = this.wsUrl.replace("http://", "ws://").replace("https://", "wss://");
|
|
37
|
+
// Build WebSocket URL with sessionId and sessionToken
|
|
38
|
+
const url = `${wsUrl}/ws/connect/session?sessionId=${encodeURIComponent(this.sessionId)}&sessionToken=${encodeURIComponent(this.sessionToken)}`;
|
|
39
|
+
this.logger.debug({
|
|
40
|
+
wsUrl,
|
|
41
|
+
sessionId: this.sessionId,
|
|
42
|
+
hasSessionToken: !!this.sessionToken,
|
|
43
|
+
}, "Connecting to WebSocket");
|
|
44
|
+
this.socket = new WebSocket(url);
|
|
45
|
+
this.socket.onopen = () => {
|
|
46
|
+
var _a, _b;
|
|
47
|
+
this.logger.info("WebSocket connected successfully");
|
|
48
|
+
this.isConnected = true;
|
|
49
|
+
this.reconnectAttempts = 0;
|
|
50
|
+
this.startHeartbeat();
|
|
51
|
+
(_b = (_a = this.callbacks).onConnectionStatusChanged) === null || _b === void 0 ? void 0 : _b.call(_a, true);
|
|
52
|
+
resolve();
|
|
53
|
+
};
|
|
54
|
+
this.socket.onmessage = (event) => {
|
|
55
|
+
try {
|
|
56
|
+
const message = JSON.parse(event.data);
|
|
57
|
+
this.handleMessage(message);
|
|
58
|
+
}
|
|
59
|
+
catch (error) {
|
|
60
|
+
this.logger.error({ error, data: event.data }, "Failed to parse WebSocket message");
|
|
61
|
+
}
|
|
62
|
+
};
|
|
63
|
+
this.socket.onclose = (event) => {
|
|
64
|
+
var _a, _b;
|
|
65
|
+
if (!this.isConnected)
|
|
66
|
+
return;
|
|
67
|
+
this.logger.info({ code: event.code, reason: event.reason }, "WebSocket disconnected");
|
|
68
|
+
this.isConnected = false;
|
|
69
|
+
this.cleanup();
|
|
70
|
+
if (event.code !== 1000) {
|
|
71
|
+
if (this.reconnectAttempts < this.maxReconnectAttempts) {
|
|
72
|
+
this.scheduleReconnect();
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
else {
|
|
76
|
+
(_b = (_a = this.callbacks).onConnectionStatusChanged) === null || _b === void 0 ? void 0 : _b.call(_a, false);
|
|
77
|
+
}
|
|
78
|
+
};
|
|
79
|
+
this.socket.onerror = (error) => {
|
|
80
|
+
var _a, _b;
|
|
81
|
+
this.logger.error({ error, sessionId: this.sessionId }, "WebSocket error");
|
|
82
|
+
(_b = (_a = this.callbacks).onConnectionStatusChanged) === null || _b === void 0 ? void 0 : _b.call(_a, false);
|
|
83
|
+
reject(error);
|
|
84
|
+
};
|
|
85
|
+
// Set connection timeout
|
|
86
|
+
setTimeout(() => {
|
|
87
|
+
if (this.socket && !this.isConnected) {
|
|
88
|
+
this.socket.close();
|
|
89
|
+
reject(new Error("WebSocket connection timeout"));
|
|
90
|
+
}
|
|
91
|
+
}, 10000); // 10 second timeout
|
|
92
|
+
}
|
|
93
|
+
catch (error) {
|
|
94
|
+
reject(error);
|
|
95
|
+
}
|
|
96
|
+
});
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
handleMessage(message) {
|
|
100
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p;
|
|
101
|
+
try {
|
|
102
|
+
this.logger.debug({ message }, "Received message");
|
|
103
|
+
switch (message.type) {
|
|
104
|
+
case "connected":
|
|
105
|
+
this.logger.info("Connection confirmed by server");
|
|
106
|
+
break;
|
|
107
|
+
case "mobile_connected":
|
|
108
|
+
(_b = (_a = this.callbacks).onMobileConnected) === null || _b === void 0 ? void 0 : _b.call(_a, message.data);
|
|
109
|
+
break;
|
|
110
|
+
case "processing_started":
|
|
111
|
+
(_d = (_c = this.callbacks).onProcessingStarted) === null || _d === void 0 ? void 0 : _d.call(_c, message.data);
|
|
112
|
+
break;
|
|
113
|
+
case "processing_completed":
|
|
114
|
+
(_f = (_e = this.callbacks).onProcessingCompleted) === null || _f === void 0 ? void 0 : _f.call(_e, message.data);
|
|
115
|
+
break;
|
|
116
|
+
case "processing_failed":
|
|
117
|
+
(_h = (_g = this.callbacks).onProcessingFailed) === null || _h === void 0 ? void 0 : _h.call(_g, message.data);
|
|
118
|
+
break;
|
|
119
|
+
case "session_expired":
|
|
120
|
+
(_k = (_j = this.callbacks).onSessionExpired) === null || _k === void 0 ? void 0 : _k.call(_j, message.data);
|
|
121
|
+
this.disconnect();
|
|
122
|
+
break;
|
|
123
|
+
case "error":
|
|
124
|
+
(_m = (_l = this.callbacks).onError) === null || _m === void 0 ? void 0 : _m.call(_l, this.createError("MOBILE_PROCESSING_FAILED", message.data.message || "Mobile processing error", message.data));
|
|
125
|
+
break;
|
|
126
|
+
case "heartbeat_response":
|
|
127
|
+
this.logger.debug("Heartbeat response received");
|
|
128
|
+
break;
|
|
129
|
+
default:
|
|
130
|
+
this.logger.warn({ messageType: message.type }, "Unknown message type");
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
catch (error) {
|
|
134
|
+
this.logger.error({ error }, "Error handling WebSocket message");
|
|
135
|
+
(_p = (_o = this.callbacks).onError) === null || _p === void 0 ? void 0 : _p.call(_o, this.createError("NETWORK_ERROR", "Failed to handle WebSocket message", error));
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
sendMessage(message) {
|
|
139
|
+
var _a, _b;
|
|
140
|
+
if (!this.socket || !this.isConnected || this.socket.readyState !== WebSocket.OPEN) {
|
|
141
|
+
this.logger.warn("Cannot send message - WebSocket not connected");
|
|
142
|
+
return;
|
|
143
|
+
}
|
|
144
|
+
const fullMessage = Object.assign({ session_id: this.sessionId, timestamp: Date.now() }, message);
|
|
145
|
+
try {
|
|
146
|
+
this.socket.send(JSON.stringify(fullMessage));
|
|
147
|
+
this.logger.debug({ message: fullMessage }, "Sent message");
|
|
148
|
+
}
|
|
149
|
+
catch (error) {
|
|
150
|
+
this.logger.error({ error }, "Error sending message");
|
|
151
|
+
(_b = (_a = this.callbacks).onError) === null || _b === void 0 ? void 0 : _b.call(_a, this.createError("WEBSOCKET_CONNECTION_FAILED", "Failed to send WebSocket message", error));
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
startHeartbeat() {
|
|
155
|
+
this.heartbeatInterval = setInterval(() => {
|
|
156
|
+
this.sendMessage({
|
|
157
|
+
type: "heartbeat",
|
|
158
|
+
});
|
|
159
|
+
}, 30000); // 30 second heartbeat
|
|
160
|
+
}
|
|
161
|
+
scheduleReconnect() {
|
|
162
|
+
this.reconnectAttempts++;
|
|
163
|
+
const delay = Math.min(1000 * Math.pow(2, this.reconnectAttempts), 30000); // Exponential backoff, max 30s
|
|
164
|
+
this.logger.info({
|
|
165
|
+
attempt: this.reconnectAttempts,
|
|
166
|
+
maxAttempts: this.maxReconnectAttempts,
|
|
167
|
+
delay,
|
|
168
|
+
}, "Scheduling reconnection attempt");
|
|
169
|
+
setTimeout(() => __awaiter(this, void 0, void 0, function* () {
|
|
170
|
+
try {
|
|
171
|
+
yield this.connect();
|
|
172
|
+
this.logger.info("Reconnection successful");
|
|
173
|
+
}
|
|
174
|
+
catch (error) {
|
|
175
|
+
this.logger.error({ error }, "Reconnection failed");
|
|
176
|
+
}
|
|
177
|
+
}), delay);
|
|
178
|
+
}
|
|
179
|
+
cleanup() {
|
|
180
|
+
if (this.heartbeatInterval) {
|
|
181
|
+
clearInterval(this.heartbeatInterval);
|
|
182
|
+
this.heartbeatInterval = null;
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
disconnect() {
|
|
186
|
+
var _a, _b;
|
|
187
|
+
this.logger.info("Disconnecting WebSocket");
|
|
188
|
+
this.cleanup();
|
|
189
|
+
this.isConnected = false;
|
|
190
|
+
if (this.socket) {
|
|
191
|
+
this.socket.close(1000, "Normal closure");
|
|
192
|
+
this.socket = null;
|
|
193
|
+
}
|
|
194
|
+
(_b = (_a = this.callbacks).onConnectionStatusChanged) === null || _b === void 0 ? void 0 : _b.call(_a, false);
|
|
195
|
+
}
|
|
196
|
+
getConnectionState() {
|
|
197
|
+
if (!this.socket)
|
|
198
|
+
return "CLOSED";
|
|
199
|
+
switch (this.socket.readyState) {
|
|
200
|
+
case WebSocket.CONNECTING:
|
|
201
|
+
return "CONNECTING";
|
|
202
|
+
case WebSocket.OPEN:
|
|
203
|
+
return "CONNECTED";
|
|
204
|
+
case WebSocket.CLOSING:
|
|
205
|
+
return "CLOSING";
|
|
206
|
+
case WebSocket.CLOSED:
|
|
207
|
+
return "CLOSED";
|
|
208
|
+
default:
|
|
209
|
+
return "UNKNOWN";
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
createError(code, message, details) {
|
|
213
|
+
return {
|
|
214
|
+
code,
|
|
215
|
+
message,
|
|
216
|
+
details,
|
|
217
|
+
};
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
exports.KeyringWebSocketClient = KeyringWebSocketClient;
|
package/dist/index.d.ts
CHANGED
package/dist/index.js
CHANGED
|
@@ -14,8 +14,8 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
|
14
14
|
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
15
15
|
};
|
|
16
16
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
-
exports.
|
|
17
|
+
exports.VerificationSession = void 0;
|
|
18
18
|
__exportStar(require("@keyringnetwork/contracts-abi"), exports);
|
|
19
|
-
var
|
|
20
|
-
Object.defineProperty(exports, "
|
|
19
|
+
var VerificationSession_1 = require("./core/VerificationSession");
|
|
20
|
+
Object.defineProperty(exports, "VerificationSession", { enumerable: true, get: function () { return VerificationSession_1.VerificationSession; } });
|
|
21
21
|
__exportStar(require("./types"), exports);
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { DatasourceTypes } from "@keyringnetwork/keyring-dtos";
|
|
2
|
+
import { CredentialData, SessionConfig } from "./core";
|
|
3
|
+
export type SessionStatus = "session_created" | "mobile_connected" | "processing_started" | "processing_completed" | "processing_failed" | "session_expired";
|
|
4
|
+
export interface CrossDeviceSession {
|
|
5
|
+
session_id: string;
|
|
6
|
+
session_token: string;
|
|
7
|
+
config: SessionConfig;
|
|
8
|
+
origin_url: string;
|
|
9
|
+
status: SessionStatus;
|
|
10
|
+
created_at: number | string;
|
|
11
|
+
expires_at: number | string;
|
|
12
|
+
result?: {
|
|
13
|
+
credential_data?: CredentialData;
|
|
14
|
+
entity_type?: string;
|
|
15
|
+
datasourceObject?: DatasourceTypes.DatasourceObject;
|
|
16
|
+
};
|
|
17
|
+
error?: string;
|
|
18
|
+
platform: "web" | "mobile";
|
|
19
|
+
qr_code_data: string;
|
|
20
|
+
connected_clients: {
|
|
21
|
+
web?: string;
|
|
22
|
+
mobile?: string;
|
|
23
|
+
};
|
|
24
|
+
}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { MobileConnectedMessage, ProcessingCompletedMessage, ProcessingFailedMessage, ProcessingStartedMessage, SessionCreatedMessage, SessionExpiredMessage } from "./websocket";
|
|
2
|
+
export interface SessionConfig {
|
|
3
|
+
/**
|
|
4
|
+
* The name of the client.
|
|
5
|
+
*/
|
|
6
|
+
name: string;
|
|
7
|
+
/**
|
|
8
|
+
* The URL of the client app which the chrome extension will communicate with.
|
|
9
|
+
*/
|
|
10
|
+
app_url: string;
|
|
11
|
+
/**
|
|
12
|
+
* The URL of the client logo.
|
|
13
|
+
*/
|
|
14
|
+
logo_url: string;
|
|
15
|
+
/**
|
|
16
|
+
* The onchain policy ID of the client.
|
|
17
|
+
*/
|
|
18
|
+
policy_id: number;
|
|
19
|
+
/**
|
|
20
|
+
* The wallet address and chain ID of the user.
|
|
21
|
+
*/
|
|
22
|
+
credential_config: {
|
|
23
|
+
chain_id: number;
|
|
24
|
+
wallet_address: string;
|
|
25
|
+
};
|
|
26
|
+
/**
|
|
27
|
+
* The API key of the client.
|
|
28
|
+
*/
|
|
29
|
+
api_key: string;
|
|
30
|
+
/**
|
|
31
|
+
* Keyring Network configuration for the extension.
|
|
32
|
+
* @note this is meant to be used for internal testing and development purposes.
|
|
33
|
+
*/
|
|
34
|
+
krn_config?: {
|
|
35
|
+
keyring_api_url?: string;
|
|
36
|
+
keyring_user_app_url?: string;
|
|
37
|
+
analytics_api_url?: string;
|
|
38
|
+
notaryUrl?: string;
|
|
39
|
+
websocketProxyUrl?: string;
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
export interface CredentialData {
|
|
43
|
+
trader: `0x${string}`;
|
|
44
|
+
policyId: number;
|
|
45
|
+
chainId: number;
|
|
46
|
+
validUntil: number;
|
|
47
|
+
cost: number;
|
|
48
|
+
key: string;
|
|
49
|
+
signature: string;
|
|
50
|
+
backdoor: string;
|
|
51
|
+
}
|
|
52
|
+
export interface SDKError {
|
|
53
|
+
code: string;
|
|
54
|
+
message: string;
|
|
55
|
+
details?: any;
|
|
56
|
+
}
|
|
57
|
+
export type SDKErrorCode = "BACKEND_UNAVAILABLE" | "SESSION_CREATION_FAILED" | "SESSION_NOT_FOUND" | "SESSION_EXPIRED" | "WEBSOCKET_CONNECTION_FAILED" | "WEBSOCKET_DISCONNECTED" | "MOBILE_PROCESSING_FAILED" | "NETWORK_ERROR" | "VALIDATION_ERROR" | "TIMEOUT_ERROR" | "QR_DISPLAY_NOT_SUPPORTED";
|
|
58
|
+
export interface SDKCallbacks {
|
|
59
|
+
onSessionCreated?: (data: SessionCreatedMessage["data"]) => void;
|
|
60
|
+
onMobileConnected?: (data: MobileConnectedMessage["data"]) => void;
|
|
61
|
+
onProcessingStarted?: (data: ProcessingStartedMessage["data"]) => void;
|
|
62
|
+
onProcessingCompleted?: (data: ProcessingCompletedMessage["data"]) => void;
|
|
63
|
+
onProcessingFailed?: (data: ProcessingFailedMessage["data"]) => void;
|
|
64
|
+
onSessionExpired?: (data: SessionExpiredMessage["data"]) => void;
|
|
65
|
+
onError?: (error: SDKError) => void;
|
|
66
|
+
onConnectionStatusChanged?: (connected: boolean) => void;
|
|
67
|
+
}
|
|
68
|
+
export type SDKEventTypes = "sessionCreated" | "mobileConnected" | "processingStarted" | "processingCompleted" | "processingFailed" | "sessionExpired" | "error" | "connectionStatusChanged";
|
|
69
|
+
export type SDKEventData = {
|
|
70
|
+
sessionCreated: SessionCreatedMessage["data"];
|
|
71
|
+
mobileConnected: MobileConnectedMessage["data"];
|
|
72
|
+
processingStarted: ProcessingStartedMessage["data"];
|
|
73
|
+
processingCompleted: ProcessingCompletedMessage["data"];
|
|
74
|
+
processingFailed: ProcessingFailedMessage["data"];
|
|
75
|
+
sessionExpired: SessionExpiredMessage["data"];
|
|
76
|
+
error: SDKError;
|
|
77
|
+
connectionStatusChanged: boolean;
|
|
78
|
+
};
|