@relayerfi/widget-kit-core 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +42 -0
- package/dist/index.cjs +625 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +270 -0
- package/dist/index.d.ts +270 -0
- package/dist/index.js +610 -0
- package/dist/index.js.map +1 -0
- package/package.json +58 -0
package/README.md
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# @relayerfi/widget-kit-core
|
|
2
|
+
|
|
3
|
+
[](https://www.npmjs.com/package/@relayerfi/widget-kit-core)
|
|
4
|
+
[](https://opensource.org/licenses/MIT)
|
|
5
|
+
|
|
6
|
+
Framework-agnostic runtime for Relayer widgets. Handles widget directory management, metadata fetching, URL security classification, and proxy communication.
|
|
7
|
+
|
|
8
|
+
This package is used internally by `@relayerfi/widget-kit-react`. Most integrators should install that package instead.
|
|
9
|
+
|
|
10
|
+
## Install
|
|
11
|
+
|
|
12
|
+
```bash
|
|
13
|
+
pnpm add @relayerfi/widget-kit-core
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
## What It Does
|
|
17
|
+
|
|
18
|
+
- **WidgetDirectory** - Classifies widget URLs as trusted, malicious, or unknown
|
|
19
|
+
- **Metadata fetching** - Resolves action metadata from widget URLs via the Relayer proxy
|
|
20
|
+
- **Error types** - Structured `WidgetError` with factory methods
|
|
21
|
+
- **Utilities** - URL formatting, client-key management, proxy endpoint builders
|
|
22
|
+
|
|
23
|
+
## Key Exports
|
|
24
|
+
|
|
25
|
+
```typescript
|
|
26
|
+
import { WidgetDirectory, WidgetError, PROXY_URL } from '@relayerfi/widget-kit-core';
|
|
27
|
+
|
|
28
|
+
const directory = WidgetDirectory.getInstance();
|
|
29
|
+
await directory.init();
|
|
30
|
+
const state = directory.lookup('https://app.relayer.fi');
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
## Related Packages
|
|
34
|
+
|
|
35
|
+
| Package | Purpose |
|
|
36
|
+
|---------|---------|
|
|
37
|
+
| [`@relayerfi/action-kit`](https://www.npmjs.com/package/@relayerfi/action-kit) | Action type definitions, validators, executors |
|
|
38
|
+
| [`@relayerfi/widget-kit-react`](https://www.npmjs.com/package/@relayerfi/widget-kit-react) | React components for rendering widgets |
|
|
39
|
+
|
|
40
|
+
## License
|
|
41
|
+
|
|
42
|
+
MIT
|
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,625 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/index.ts
|
|
21
|
+
var index_exports = {};
|
|
22
|
+
__export(index_exports, {
|
|
23
|
+
API_REPOSITORY_URL: () => API_REPOSITORY_URL,
|
|
24
|
+
BlockchainError: () => BlockchainError,
|
|
25
|
+
ErrorUtils: () => ErrorUtils,
|
|
26
|
+
InvalidOperationError: () => InvalidOperationError,
|
|
27
|
+
NetworkError: () => NetworkError,
|
|
28
|
+
NotFoundError: () => NotFoundError,
|
|
29
|
+
PROXY_TO_DEV_HEADERS: () => import_action_kit.PROXY_TO_DEV_HEADERS,
|
|
30
|
+
PROXY_URL: () => PROXY_URL,
|
|
31
|
+
RELAYER_VALUES: () => import_action_kit.RELAYER_VALUES,
|
|
32
|
+
RelayerError: () => RelayerError,
|
|
33
|
+
SDK_TO_PROXY_HEADERS: () => import_action_kit.SDK_TO_PROXY_HEADERS,
|
|
34
|
+
SecurityError: () => SecurityError,
|
|
35
|
+
VALID_OPERATIONS: () => import_action_kit.VALID_OPERATIONS,
|
|
36
|
+
VERSION: () => VERSION,
|
|
37
|
+
ValidationError: () => ValidationError,
|
|
38
|
+
WidgetDirectory: () => WidgetDirectory,
|
|
39
|
+
WidgetError: () => WidgetError,
|
|
40
|
+
addHexPrefix: () => addHexPrefix,
|
|
41
|
+
clearClientKey: () => clearClientKey,
|
|
42
|
+
execute: () => execute,
|
|
43
|
+
formatNumber: () => formatNumber,
|
|
44
|
+
getClientKey: () => getClientKey,
|
|
45
|
+
getMetadata: () => getMetadata,
|
|
46
|
+
proxify: () => proxify,
|
|
47
|
+
removeHexPrefix: () => removeHexPrefix,
|
|
48
|
+
setClientKey: () => setClientKey,
|
|
49
|
+
truncateAddress: () => truncateAddress,
|
|
50
|
+
truncateString: () => truncateString
|
|
51
|
+
});
|
|
52
|
+
module.exports = __toCommonJS(index_exports);
|
|
53
|
+
|
|
54
|
+
// src/utils/client-key.ts
|
|
55
|
+
var globalClientKey = "";
|
|
56
|
+
var EXTENSION_STORAGE_KEY = "relayer_client_key";
|
|
57
|
+
function getStorageAPI() {
|
|
58
|
+
if (typeof chrome !== "undefined" && chrome.storage) {
|
|
59
|
+
return chrome.storage;
|
|
60
|
+
}
|
|
61
|
+
if (typeof browser !== "undefined" && browser.storage) {
|
|
62
|
+
return browser.storage;
|
|
63
|
+
}
|
|
64
|
+
return null;
|
|
65
|
+
}
|
|
66
|
+
async function setClientKey(key) {
|
|
67
|
+
if (!key || key.trim() === "") {
|
|
68
|
+
throw new Error("Client key is required and cannot be empty");
|
|
69
|
+
}
|
|
70
|
+
globalClientKey = key;
|
|
71
|
+
const storage = getStorageAPI();
|
|
72
|
+
if (storage) {
|
|
73
|
+
try {
|
|
74
|
+
await storage.local.set({ [EXTENSION_STORAGE_KEY]: key });
|
|
75
|
+
} catch (error) {
|
|
76
|
+
throw new Error("Failed to store client key in storage", { cause: error });
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
async function getClientKey() {
|
|
81
|
+
if (globalClientKey && globalClientKey.trim() !== "") {
|
|
82
|
+
return globalClientKey;
|
|
83
|
+
}
|
|
84
|
+
const storage = getStorageAPI();
|
|
85
|
+
if (storage) {
|
|
86
|
+
try {
|
|
87
|
+
const result = await storage.local.get(EXTENSION_STORAGE_KEY);
|
|
88
|
+
const storedKey = result[EXTENSION_STORAGE_KEY];
|
|
89
|
+
if (storedKey) {
|
|
90
|
+
globalClientKey = storedKey;
|
|
91
|
+
return globalClientKey;
|
|
92
|
+
}
|
|
93
|
+
} catch (error) {
|
|
94
|
+
throw new Error("Failed to get client key from storage", { cause: error });
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
throw new Error("Client key not found");
|
|
98
|
+
}
|
|
99
|
+
function clearClientKey() {
|
|
100
|
+
globalClientKey = "";
|
|
101
|
+
return globalClientKey;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// src/utils/format.ts
|
|
105
|
+
function removeHexPrefix(address) {
|
|
106
|
+
return address.startsWith("0x") ? address.slice(2) : address;
|
|
107
|
+
}
|
|
108
|
+
function addHexPrefix(address) {
|
|
109
|
+
return address.startsWith("0x") ? address : `0x${address}`;
|
|
110
|
+
}
|
|
111
|
+
function formatNumber(value, decimals = 2) {
|
|
112
|
+
const num = typeof value === "string" ? parseFloat(value) : value;
|
|
113
|
+
return num.toLocaleString(void 0, {
|
|
114
|
+
minimumFractionDigits: decimals,
|
|
115
|
+
maximumFractionDigits: decimals
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
function truncateString(str, length = 4) {
|
|
119
|
+
if (str.length <= length * 2 + 3) return str;
|
|
120
|
+
return `${str.substring(0, length)}...${str.substring(str.length - length)}`;
|
|
121
|
+
}
|
|
122
|
+
function truncateAddress(address) {
|
|
123
|
+
return truncateString(removeHexPrefix(address), 4);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// src/utils/constants.ts
|
|
127
|
+
var import_action_kit = require("@relayerfi/action-kit");
|
|
128
|
+
var PROXY_URL = "https://proxy.relayer.fi";
|
|
129
|
+
var API_REPOSITORY_URL = "https://api.relayer.fi/v1/directory/v1";
|
|
130
|
+
|
|
131
|
+
// src/utils/proxify.ts
|
|
132
|
+
var import_action_kit2 = require("@relayerfi/action-kit");
|
|
133
|
+
async function proxify(url, isExtension = false) {
|
|
134
|
+
const clientKey = await getClientKey();
|
|
135
|
+
if (!clientKey) {
|
|
136
|
+
throw new Error("Client key not found", { cause: new Error("Client key not found") });
|
|
137
|
+
}
|
|
138
|
+
const proxyEndpoint = `${PROXY_URL}/proxy`;
|
|
139
|
+
const queryParams = new URLSearchParams();
|
|
140
|
+
queryParams.set("url", url);
|
|
141
|
+
const proxifiedUrl = `${proxyEndpoint}?${queryParams.toString()}`;
|
|
142
|
+
const headers = {
|
|
143
|
+
Accept: "application/json",
|
|
144
|
+
[import_action_kit.SDK_TO_PROXY_HEADERS.USER_AGENT]: import_action_kit.RELAYER_VALUES.USER_AGENT,
|
|
145
|
+
[import_action_kit.SDK_TO_PROXY_HEADERS.CONTENT_TYPE]: import_action_kit.RELAYER_VALUES.CONTENT_TYPE_JSON,
|
|
146
|
+
[import_action_kit.SDK_TO_PROXY_HEADERS.CLIENT_KEY]: clientKey
|
|
147
|
+
};
|
|
148
|
+
return { url: proxifiedUrl, headers };
|
|
149
|
+
}
|
|
150
|
+
async function getDynamicExecutor(key) {
|
|
151
|
+
if (!key) {
|
|
152
|
+
return await (0, import_action_kit2.createDynamicExecutor)();
|
|
153
|
+
} else {
|
|
154
|
+
return (0, import_action_kit2.createDynamicExecutor)(key);
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
async function execute(action, inputs, context, options) {
|
|
158
|
+
const clientKey = await getClientKey();
|
|
159
|
+
if (!clientKey) {
|
|
160
|
+
const executor = await getDynamicExecutor();
|
|
161
|
+
return await executor.execute(action, inputs, context, options);
|
|
162
|
+
} else {
|
|
163
|
+
const executor = await getDynamicExecutor(clientKey);
|
|
164
|
+
return await executor.execute(action, inputs, context, options);
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
async function getMetadata(url) {
|
|
168
|
+
const clientKey = await getClientKey();
|
|
169
|
+
const executor = clientKey ? new import_action_kit2.WidgetExecutor(clientKey) : new import_action_kit2.WidgetExecutor();
|
|
170
|
+
return await executor.getMetadata(url);
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
// src/errors/Errors.ts
|
|
174
|
+
var RelayerError = class extends Error {
|
|
175
|
+
code;
|
|
176
|
+
httpStatus;
|
|
177
|
+
originalError;
|
|
178
|
+
context;
|
|
179
|
+
constructor({
|
|
180
|
+
message,
|
|
181
|
+
code = "UNKNOWN_ERROR",
|
|
182
|
+
httpStatus,
|
|
183
|
+
originalError,
|
|
184
|
+
context
|
|
185
|
+
}) {
|
|
186
|
+
super(message);
|
|
187
|
+
this.name = this.constructor.name;
|
|
188
|
+
this.code = code;
|
|
189
|
+
this.httpStatus = httpStatus;
|
|
190
|
+
this.originalError = originalError;
|
|
191
|
+
this.context = context;
|
|
192
|
+
if (Error.captureStackTrace) {
|
|
193
|
+
Error.captureStackTrace(this, this.constructor);
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
/**
|
|
197
|
+
* Convert error to a plain object for logging or serialization
|
|
198
|
+
*/
|
|
199
|
+
toJSON() {
|
|
200
|
+
return {
|
|
201
|
+
name: this.name,
|
|
202
|
+
message: this.message,
|
|
203
|
+
code: this.code,
|
|
204
|
+
httpStatus: this.httpStatus,
|
|
205
|
+
stack: this.stack,
|
|
206
|
+
context: this.context,
|
|
207
|
+
originalError: this.originalError ? {
|
|
208
|
+
message: this.originalError.message,
|
|
209
|
+
name: this.originalError.name,
|
|
210
|
+
stack: this.originalError.stack
|
|
211
|
+
} : void 0
|
|
212
|
+
};
|
|
213
|
+
}
|
|
214
|
+
};
|
|
215
|
+
var NetworkError = class _NetworkError extends RelayerError {
|
|
216
|
+
constructor({
|
|
217
|
+
message,
|
|
218
|
+
code = "NETWORK_ERROR",
|
|
219
|
+
httpStatus,
|
|
220
|
+
originalError,
|
|
221
|
+
context
|
|
222
|
+
}) {
|
|
223
|
+
super({ message, code, httpStatus, originalError, context });
|
|
224
|
+
}
|
|
225
|
+
static fromFetchError(url, error, httpStatus) {
|
|
226
|
+
return new _NetworkError({
|
|
227
|
+
message: `Network request failed for ${url}`,
|
|
228
|
+
code: "FETCH_ERROR",
|
|
229
|
+
httpStatus,
|
|
230
|
+
originalError: error,
|
|
231
|
+
context: { url }
|
|
232
|
+
});
|
|
233
|
+
}
|
|
234
|
+
};
|
|
235
|
+
var ValidationError = class _ValidationError extends RelayerError {
|
|
236
|
+
invalidFields;
|
|
237
|
+
constructor({
|
|
238
|
+
message,
|
|
239
|
+
code = "VALIDATION_ERROR",
|
|
240
|
+
invalidFields,
|
|
241
|
+
context
|
|
242
|
+
}) {
|
|
243
|
+
super({ message, code, context });
|
|
244
|
+
this.invalidFields = invalidFields;
|
|
245
|
+
}
|
|
246
|
+
static missingField(fieldName) {
|
|
247
|
+
return new _ValidationError({
|
|
248
|
+
message: `Missing required field: ${fieldName}`,
|
|
249
|
+
code: "MISSING_FIELD",
|
|
250
|
+
invalidFields: { [fieldName]: "Field is required" }
|
|
251
|
+
});
|
|
252
|
+
}
|
|
253
|
+
static invalidFormat(fieldName, reason) {
|
|
254
|
+
return new _ValidationError({
|
|
255
|
+
message: `Invalid format for ${fieldName}: ${reason}`,
|
|
256
|
+
code: "INVALID_FORMAT",
|
|
257
|
+
invalidFields: { [fieldName]: reason }
|
|
258
|
+
});
|
|
259
|
+
}
|
|
260
|
+
};
|
|
261
|
+
var SecurityError = class extends RelayerError {
|
|
262
|
+
constructor({
|
|
263
|
+
message,
|
|
264
|
+
code = "SECURITY_ERROR",
|
|
265
|
+
context
|
|
266
|
+
}) {
|
|
267
|
+
super({ message, code, context });
|
|
268
|
+
}
|
|
269
|
+
};
|
|
270
|
+
var BlockchainError = class _BlockchainError extends RelayerError {
|
|
271
|
+
constructor({
|
|
272
|
+
message,
|
|
273
|
+
code = "BLOCKCHAIN_ERROR",
|
|
274
|
+
originalError,
|
|
275
|
+
context
|
|
276
|
+
}) {
|
|
277
|
+
super({ message, code, originalError, context });
|
|
278
|
+
}
|
|
279
|
+
static transactionFailed(reason, context) {
|
|
280
|
+
return new _BlockchainError({
|
|
281
|
+
message: `Transaction failed: ${reason}`,
|
|
282
|
+
code: "TRANSACTION_FAILED",
|
|
283
|
+
context
|
|
284
|
+
});
|
|
285
|
+
}
|
|
286
|
+
static walletConnectionFailed(reason) {
|
|
287
|
+
return new _BlockchainError({
|
|
288
|
+
message: `Failed to connect to wallet: ${reason}`,
|
|
289
|
+
code: "WALLET_CONNECTION_ERROR"
|
|
290
|
+
});
|
|
291
|
+
}
|
|
292
|
+
};
|
|
293
|
+
var NotFoundError = class _NotFoundError extends RelayerError {
|
|
294
|
+
constructor({
|
|
295
|
+
message,
|
|
296
|
+
code = "NOT_FOUND",
|
|
297
|
+
httpStatus = 404,
|
|
298
|
+
context
|
|
299
|
+
}) {
|
|
300
|
+
super({ message, code, httpStatus, context });
|
|
301
|
+
}
|
|
302
|
+
static resourceNotFound(resourceType, identifier) {
|
|
303
|
+
return new _NotFoundError({
|
|
304
|
+
message: `${resourceType} not found: ${identifier}`,
|
|
305
|
+
context: { resourceType, identifier }
|
|
306
|
+
});
|
|
307
|
+
}
|
|
308
|
+
};
|
|
309
|
+
var InvalidOperationError = class _InvalidOperationError extends RelayerError {
|
|
310
|
+
constructor({
|
|
311
|
+
message,
|
|
312
|
+
code = "INVALID_OPERATION",
|
|
313
|
+
context
|
|
314
|
+
}) {
|
|
315
|
+
super({ message, code, context });
|
|
316
|
+
}
|
|
317
|
+
static actionNotPermitted(action, reason) {
|
|
318
|
+
return new _InvalidOperationError({
|
|
319
|
+
message: `Operation not permitted: ${action}. Reason: ${reason}`,
|
|
320
|
+
context: { action, reason }
|
|
321
|
+
});
|
|
322
|
+
}
|
|
323
|
+
};
|
|
324
|
+
var WidgetError = class _WidgetError extends RelayerError {
|
|
325
|
+
constructor({
|
|
326
|
+
message,
|
|
327
|
+
code = "WIDGET_ERROR",
|
|
328
|
+
httpStatus,
|
|
329
|
+
originalError,
|
|
330
|
+
context
|
|
331
|
+
}) {
|
|
332
|
+
super({ message, code, httpStatus, originalError, context });
|
|
333
|
+
}
|
|
334
|
+
static metadataFetchFailed(url, httpStatus) {
|
|
335
|
+
return new _WidgetError({
|
|
336
|
+
message: `Failed to fetch widget metadata from ${url}`,
|
|
337
|
+
code: "METADATA_FETCH_FAILED",
|
|
338
|
+
httpStatus,
|
|
339
|
+
context: { url }
|
|
340
|
+
});
|
|
341
|
+
}
|
|
342
|
+
static invalidUrl(url, reason) {
|
|
343
|
+
return new _WidgetError({
|
|
344
|
+
message: `Invalid widget URL: ${url}. Reason: ${reason}`,
|
|
345
|
+
code: "INVALID_URL",
|
|
346
|
+
context: { url, reason }
|
|
347
|
+
});
|
|
348
|
+
}
|
|
349
|
+
static invalidMetadata(reason, originalError) {
|
|
350
|
+
return new _WidgetError({
|
|
351
|
+
message: `Invalid widget metadata: ${reason}`,
|
|
352
|
+
code: "INVALID_METADATA",
|
|
353
|
+
originalError,
|
|
354
|
+
context: { reason }
|
|
355
|
+
});
|
|
356
|
+
}
|
|
357
|
+
static actionError(actionIndex, reason) {
|
|
358
|
+
return new _WidgetError({
|
|
359
|
+
message: `Failed to execute widget action at index ${actionIndex}: ${reason}`,
|
|
360
|
+
code: "ACTION_EXECUTION_ERROR",
|
|
361
|
+
context: { actionIndex, reason }
|
|
362
|
+
});
|
|
363
|
+
}
|
|
364
|
+
/**
|
|
365
|
+
* Error when fetching the widget directory fails.
|
|
366
|
+
* @param url The URL that was fetched.
|
|
367
|
+
* @param httpStatus The HTTP status code received.
|
|
368
|
+
* @param statusText Optional status text from the response.
|
|
369
|
+
*/
|
|
370
|
+
static directoryFetchFailed(url, httpStatus, statusText) {
|
|
371
|
+
return new _WidgetError({
|
|
372
|
+
message: `Failed to fetch widget directory from ${url}. Status: ${httpStatus}${statusText ? ` (${statusText})` : ""}`,
|
|
373
|
+
code: "REGISTRY_FETCH_FAILED",
|
|
374
|
+
httpStatus,
|
|
375
|
+
context: { url }
|
|
376
|
+
});
|
|
377
|
+
}
|
|
378
|
+
/**
|
|
379
|
+
* Error when the fetched directory data has an invalid format.
|
|
380
|
+
* @param reason Description of why the format is invalid.
|
|
381
|
+
*/
|
|
382
|
+
static invalidDirectoryFormat(reason) {
|
|
383
|
+
return new _WidgetError({
|
|
384
|
+
message: `Invalid widget directory format: ${reason}`,
|
|
385
|
+
code: "INVALID_REGISTRY_FORMAT",
|
|
386
|
+
context: { reason }
|
|
387
|
+
});
|
|
388
|
+
}
|
|
389
|
+
/**
|
|
390
|
+
* Generic error during the directory refresh process.
|
|
391
|
+
* @param reason Description of the error.
|
|
392
|
+
* @param originalError The original error caught, if any.
|
|
393
|
+
*/
|
|
394
|
+
static directoryRefreshError(reason, originalError) {
|
|
395
|
+
return new _WidgetError({
|
|
396
|
+
message: `Error during widget directory refresh: ${reason}`,
|
|
397
|
+
code: "REGISTRY_REFRESH_ERROR",
|
|
398
|
+
originalError,
|
|
399
|
+
context: { reason }
|
|
400
|
+
});
|
|
401
|
+
}
|
|
402
|
+
};
|
|
403
|
+
var ErrorUtils = {
|
|
404
|
+
/**
|
|
405
|
+
* Safely wrap async operations with consistent error handling
|
|
406
|
+
*/
|
|
407
|
+
async safeAsync(promise, errorMapper) {
|
|
408
|
+
try {
|
|
409
|
+
return await promise;
|
|
410
|
+
} catch (error) {
|
|
411
|
+
if (errorMapper) {
|
|
412
|
+
throw errorMapper(error);
|
|
413
|
+
}
|
|
414
|
+
if (error instanceof RelayerError) {
|
|
415
|
+
throw error;
|
|
416
|
+
}
|
|
417
|
+
throw new RelayerError({
|
|
418
|
+
message: error instanceof Error ? error.message : String(error),
|
|
419
|
+
originalError: error instanceof Error ? error : void 0
|
|
420
|
+
});
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
};
|
|
424
|
+
|
|
425
|
+
// src/directory/WidgetDirectory.ts
|
|
426
|
+
var WidgetDirectory = class _WidgetDirectory {
|
|
427
|
+
static instance = null;
|
|
428
|
+
widgetsByHostAndPath = /* @__PURE__ */ new Map();
|
|
429
|
+
widgetsByHost = /* @__PURE__ */ new Map();
|
|
430
|
+
lastUpdated = /* @__PURE__ */ new Date(0);
|
|
431
|
+
isRefreshing = false;
|
|
432
|
+
isExtension = false;
|
|
433
|
+
defaultDirectoryUrl;
|
|
434
|
+
initializationPromise = null;
|
|
435
|
+
isInitialized = false;
|
|
436
|
+
refreshIntervalId = null;
|
|
437
|
+
// Para almacenar el ID del intervalo
|
|
438
|
+
constructor(config = {}) {
|
|
439
|
+
this.isExtension = typeof chrome !== "undefined" && chrome.runtime && !!chrome.runtime.id;
|
|
440
|
+
this.defaultDirectoryUrl = API_REPOSITORY_URL;
|
|
441
|
+
}
|
|
442
|
+
static getInstance(config = {}) {
|
|
443
|
+
if (!this.instance) {
|
|
444
|
+
this.instance = new _WidgetDirectory(config);
|
|
445
|
+
}
|
|
446
|
+
return this.instance;
|
|
447
|
+
}
|
|
448
|
+
async init(config = {}) {
|
|
449
|
+
if (this.isInitialized && !config.forceRefresh) {
|
|
450
|
+
return;
|
|
451
|
+
}
|
|
452
|
+
if (this.initializationPromise && !config.forceRefresh) {
|
|
453
|
+
return this.initializationPromise;
|
|
454
|
+
}
|
|
455
|
+
if (config.forceRefresh) {
|
|
456
|
+
this.isInitialized = false;
|
|
457
|
+
}
|
|
458
|
+
this.initializationPromise = (async () => {
|
|
459
|
+
try {
|
|
460
|
+
const urlToRefresh = this.defaultDirectoryUrl;
|
|
461
|
+
await this.refresh(urlToRefresh);
|
|
462
|
+
if (this.isExtension && config?.refreshInterval) {
|
|
463
|
+
if (this.refreshIntervalId) {
|
|
464
|
+
clearInterval(this.refreshIntervalId);
|
|
465
|
+
}
|
|
466
|
+
this.refreshIntervalId = setInterval(() => {
|
|
467
|
+
this.refresh(urlToRefresh).catch((error) => {
|
|
468
|
+
});
|
|
469
|
+
}, config.refreshInterval);
|
|
470
|
+
}
|
|
471
|
+
this.isInitialized = true;
|
|
472
|
+
} catch (error) {
|
|
473
|
+
this.isInitialized = false;
|
|
474
|
+
throw error;
|
|
475
|
+
} finally {
|
|
476
|
+
this.initializationPromise = null;
|
|
477
|
+
}
|
|
478
|
+
})();
|
|
479
|
+
return this.initializationPromise;
|
|
480
|
+
}
|
|
481
|
+
createKey(host, pathname) {
|
|
482
|
+
return `${host}${pathname}`;
|
|
483
|
+
}
|
|
484
|
+
normalizeHost(hostInput) {
|
|
485
|
+
let host = hostInput;
|
|
486
|
+
if (host.startsWith("http://") || host.startsWith("https://")) {
|
|
487
|
+
try {
|
|
488
|
+
const url = new URL(hostInput);
|
|
489
|
+
host = url.host;
|
|
490
|
+
} catch (e) {
|
|
491
|
+
const match = hostInput.match(/^https?:\/\/([^/?#]+)/i);
|
|
492
|
+
if (match && match[1]) {
|
|
493
|
+
host = match[1];
|
|
494
|
+
} else {
|
|
495
|
+
host = hostInput.split("/")[0] || hostInput;
|
|
496
|
+
}
|
|
497
|
+
}
|
|
498
|
+
}
|
|
499
|
+
if (host.startsWith("www.")) {
|
|
500
|
+
host = host.substring(4);
|
|
501
|
+
}
|
|
502
|
+
return host;
|
|
503
|
+
}
|
|
504
|
+
lookup(url) {
|
|
505
|
+
if (!this.isInitialized) {
|
|
506
|
+
return { state: "unknown" };
|
|
507
|
+
}
|
|
508
|
+
try {
|
|
509
|
+
const urlObj = typeof url === "string" ? new URL(url) : url;
|
|
510
|
+
const host = this.normalizeHost(urlObj.host);
|
|
511
|
+
const pathname = urlObj.pathname;
|
|
512
|
+
const exactKey = this.createKey(host, pathname);
|
|
513
|
+
const exactMatch = this.widgetsByHostAndPath.get(exactKey);
|
|
514
|
+
if (exactMatch) {
|
|
515
|
+
return { state: exactMatch.state, id: exactMatch.id };
|
|
516
|
+
}
|
|
517
|
+
const hostApps = this.widgetsByHost.get(host);
|
|
518
|
+
if (hostApps && hostApps.length > 0) {
|
|
519
|
+
for (const app of hostApps) {
|
|
520
|
+
const appPath = app.path || "/";
|
|
521
|
+
if (pathname.startsWith(appPath)) {
|
|
522
|
+
return { state: app.state, id: app.id };
|
|
523
|
+
}
|
|
524
|
+
}
|
|
525
|
+
const maliciousApp = hostApps.find((app) => app.state === "malicious");
|
|
526
|
+
if (maliciousApp) {
|
|
527
|
+
return { state: "malicious", id: maliciousApp.id };
|
|
528
|
+
}
|
|
529
|
+
const rootPathApp = hostApps.find((app) => (app.path || "/") === "/");
|
|
530
|
+
if (rootPathApp) {
|
|
531
|
+
return { state: rootPathApp.state, id: rootPathApp.id };
|
|
532
|
+
}
|
|
533
|
+
return { state: "unknown" };
|
|
534
|
+
}
|
|
535
|
+
return { state: "unknown" };
|
|
536
|
+
} catch (error) {
|
|
537
|
+
return { state: "unknown" };
|
|
538
|
+
}
|
|
539
|
+
}
|
|
540
|
+
async refresh(directoryUrl) {
|
|
541
|
+
if (this.isRefreshing) {
|
|
542
|
+
return;
|
|
543
|
+
}
|
|
544
|
+
const urlToFetch = directoryUrl || this.defaultDirectoryUrl;
|
|
545
|
+
this.isRefreshing = true;
|
|
546
|
+
try {
|
|
547
|
+
const response = await fetch(urlToFetch, {
|
|
548
|
+
headers: { "Cache-Control": "no-cache, no-store", Pragma: "no-cache" }
|
|
549
|
+
});
|
|
550
|
+
if (!response.ok) {
|
|
551
|
+
throw WidgetError.directoryFetchFailed(
|
|
552
|
+
urlToFetch,
|
|
553
|
+
response.status,
|
|
554
|
+
response.statusText
|
|
555
|
+
);
|
|
556
|
+
}
|
|
557
|
+
const data = await response.json();
|
|
558
|
+
if (!data || typeof data !== "object" || !Array.isArray(data.mini_apps)) {
|
|
559
|
+
throw WidgetError.invalidDirectoryFormat("Missing expected fields (mini_apps)");
|
|
560
|
+
}
|
|
561
|
+
const newWidgetsByHostAndPath = /* @__PURE__ */ new Map();
|
|
562
|
+
const newWidgetsByHost = /* @__PURE__ */ new Map();
|
|
563
|
+
for (const app of data.mini_apps) {
|
|
564
|
+
if (app && typeof app === "object" && app.host && app.state) {
|
|
565
|
+
const originalAppHost = app.host;
|
|
566
|
+
const normalizedHost = this.normalizeHost(app.host);
|
|
567
|
+
const path = app.path || "/";
|
|
568
|
+
const normalizedApp = { ...app, host: normalizedHost, path };
|
|
569
|
+
const key = this.createKey(normalizedHost, path);
|
|
570
|
+
newWidgetsByHostAndPath.set(key, normalizedApp);
|
|
571
|
+
if (!newWidgetsByHost.has(normalizedHost)) {
|
|
572
|
+
newWidgetsByHost.set(normalizedHost, []);
|
|
573
|
+
}
|
|
574
|
+
newWidgetsByHost.get(normalizedHost).push(normalizedApp);
|
|
575
|
+
} else {
|
|
576
|
+
console.warn(
|
|
577
|
+
"[widget-kit] Skipping invalid WidgetEntry during refresh:",
|
|
578
|
+
app
|
|
579
|
+
);
|
|
580
|
+
}
|
|
581
|
+
}
|
|
582
|
+
for (const [host, apps] of newWidgetsByHost) {
|
|
583
|
+
apps.sort((a, b) => b.path.length - a.path.length);
|
|
584
|
+
newWidgetsByHost.set(host, apps);
|
|
585
|
+
}
|
|
586
|
+
this.widgetsByHostAndPath = newWidgetsByHostAndPath;
|
|
587
|
+
this.widgetsByHost = newWidgetsByHost;
|
|
588
|
+
this.lastUpdated = new Date(data.lastUpdated);
|
|
589
|
+
} catch (error) {
|
|
590
|
+
if (error instanceof WidgetError) throw error;
|
|
591
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
592
|
+
throw WidgetError.directoryRefreshError(
|
|
593
|
+
errorMessage,
|
|
594
|
+
error instanceof Error ? error : void 0
|
|
595
|
+
);
|
|
596
|
+
} finally {
|
|
597
|
+
this.isRefreshing = false;
|
|
598
|
+
}
|
|
599
|
+
}
|
|
600
|
+
getLastUpdated() {
|
|
601
|
+
return this.lastUpdated;
|
|
602
|
+
}
|
|
603
|
+
getCurrentDirectory() {
|
|
604
|
+
return this.widgetsByHostAndPath;
|
|
605
|
+
}
|
|
606
|
+
debug() {
|
|
607
|
+
console.log("=== Directory Debug ===");
|
|
608
|
+
console.log(`Is Initialized: ${this.isInitialized}`);
|
|
609
|
+
console.log(`Last Updated: ${this.lastUpdated.toISOString()}`);
|
|
610
|
+
console.log("By Host + Path:");
|
|
611
|
+
for (const [key, app] of this.widgetsByHostAndPath) {
|
|
612
|
+
console.log(` ${key} -> State: ${app.state}, Path in App: ${app.path}`);
|
|
613
|
+
}
|
|
614
|
+
console.log("By Host (Sorted by path specificity):");
|
|
615
|
+
for (const [host, apps] of this.widgetsByHost) {
|
|
616
|
+
console.log(` ${host}:`);
|
|
617
|
+
apps.forEach((app) => console.log(` Path: ${app.path} -> State: ${app.state}`));
|
|
618
|
+
}
|
|
619
|
+
console.log("=======================");
|
|
620
|
+
}
|
|
621
|
+
};
|
|
622
|
+
|
|
623
|
+
// src/index.ts
|
|
624
|
+
var VERSION = "0.1.2";
|
|
625
|
+
//# sourceMappingURL=index.cjs.map
|