@frak-labs/core-sdk 0.0.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/index.cjs ADDED
@@ -0,0 +1,326 @@
1
+ "use strict";Object.defineProperty(exports, "__esModule", {value: true}); function _nullishCoalesce(lhs, rhsFn) { if (lhs != null) { return lhs; } else { return rhsFn(); } } function _optionalChain(ops) { let lastAccessLHS = undefined; let value = ops[0]; let i = 1; while (i < ops.length) { const op = ops[i]; const fn = ops[i + 1]; i += 2; if ((op === 'optionalAccess' || op === 'optionalCall') && value == null) { return undefined; } if (op === 'access' || op === 'optionalAccess') { lastAccessLHS = value; value = fn(value); } else if (op === 'call' || op === 'optionalCall') { value = fn((...args) => value.call(lastAccessLHS, ...args)); lastAccessLHS = undefined; } } return value; }
2
+
3
+
4
+
5
+
6
+
7
+
8
+
9
+
10
+
11
+
12
+
13
+
14
+
15
+ var _chunkNJJQPEELcjs = require('./chunk-NJJQPEEL.cjs');
16
+
17
+
18
+
19
+
20
+ var _chunkPR3T7O5Icjs = require('./chunk-PR3T7O5I.cjs');
21
+
22
+ // src/utils/constants.ts
23
+ var BACKUP_KEY = "nexus-wallet-backup";
24
+
25
+ // src/clients/transports/iframeChannelManager.ts
26
+ function createIFrameChannelManager() {
27
+ const channels = /* @__PURE__ */ new Map();
28
+ return {
29
+ // TODO: Better id system?? uid stuff?
30
+ createChannel: (resolver) => {
31
+ const id = Math.random().toString(36).substring(7);
32
+ channels.set(id, resolver);
33
+ return id;
34
+ },
35
+ getRpcResolver: (id) => channels.get(id),
36
+ removeChannel: (id) => channels.delete(id),
37
+ destroy: () => channels.clear()
38
+ };
39
+ }
40
+
41
+ // src/clients/transports/iframeLifecycleManager.ts
42
+ function createIFrameLifecycleManager({
43
+ iframe
44
+ }) {
45
+ const isConnectedDeferred = new (0, _chunkNJJQPEELcjs.Deferred)();
46
+ const handler = async (messageEvent) => {
47
+ switch (messageEvent.iframeLifecycle) {
48
+ // Resolve the isConnected promise
49
+ case "connected":
50
+ isConnectedDeferred.resolve(true);
51
+ break;
52
+ // Perform a nexus backup
53
+ case "do-backup":
54
+ if (messageEvent.data.backup) {
55
+ localStorage.setItem(BACKUP_KEY, messageEvent.data.backup);
56
+ } else {
57
+ localStorage.removeItem(BACKUP_KEY);
58
+ }
59
+ break;
60
+ // Remove nexus backup
61
+ case "remove-backup":
62
+ localStorage.removeItem(BACKUP_KEY);
63
+ break;
64
+ // Change iframe visibility
65
+ case "show":
66
+ case "hide":
67
+ _chunkNJJQPEELcjs.changeIframeVisibility.call(void 0, {
68
+ iframe,
69
+ isVisible: messageEvent.iframeLifecycle === "show"
70
+ });
71
+ break;
72
+ }
73
+ };
74
+ return {
75
+ handleEvent: handler,
76
+ isConnected: isConnectedDeferred.promise
77
+ };
78
+ }
79
+
80
+ // src/clients/transports/iframeMessageHandler.ts
81
+ function createIFrameMessageHandler({
82
+ frakWalletUrl,
83
+ iframe,
84
+ channelManager,
85
+ iframeLifecycleManager
86
+ }) {
87
+ if (typeof window === "undefined") {
88
+ throw new (0, _chunkNJJQPEELcjs.FrakRpcError)(
89
+ _chunkNJJQPEELcjs.RpcErrorCodes.configError,
90
+ "iframe client should be used in the browser"
91
+ );
92
+ }
93
+ if (!iframe.contentWindow) {
94
+ throw new (0, _chunkNJJQPEELcjs.FrakRpcError)(
95
+ _chunkNJJQPEELcjs.RpcErrorCodes.configError,
96
+ "The iframe does not have a product window"
97
+ );
98
+ }
99
+ const contentWindow = iframe.contentWindow;
100
+ const msgHandler = async (event) => {
101
+ if (!(event.origin && URL.canParse(event.origin))) {
102
+ return;
103
+ }
104
+ if (new URL(event.origin).origin.toLowerCase() !== new URL(frakWalletUrl).origin.toLowerCase()) {
105
+ return;
106
+ }
107
+ if (typeof event.data !== "object") {
108
+ return;
109
+ }
110
+ if ("iframeLifecycle" in event.data) {
111
+ await iframeLifecycleManager.handleEvent(event.data);
112
+ return;
113
+ }
114
+ if ("clientLifecycle" in event.data) {
115
+ console.error(
116
+ "Client lifecycle event received on the client side, dismissing it"
117
+ );
118
+ return;
119
+ }
120
+ const channel = event.data.id;
121
+ const resolver = channelManager.getRpcResolver(channel);
122
+ if (!resolver) {
123
+ return;
124
+ }
125
+ await resolver(event.data);
126
+ };
127
+ window.addEventListener("message", msgHandler);
128
+ const sendEvent = (message) => {
129
+ contentWindow.postMessage(message, {
130
+ targetOrigin: frakWalletUrl
131
+ });
132
+ };
133
+ const cleanup = () => {
134
+ window.removeEventListener("message", msgHandler);
135
+ };
136
+ return {
137
+ sendEvent,
138
+ cleanup
139
+ };
140
+ }
141
+
142
+ // src/clients/createIFrameFrakClient.ts
143
+ function createIFrameFrakClient({
144
+ config,
145
+ iframe
146
+ }) {
147
+ const channelManager = createIFrameChannelManager();
148
+ const lifecycleManager = createIFrameLifecycleManager({ iframe });
149
+ const messageHandler = createIFrameMessageHandler({
150
+ frakWalletUrl: _nullishCoalesce(_optionalChain([config, 'optionalAccess', _ => _.walletUrl]), () => ( "https://wallet.frak.id")),
151
+ iframe,
152
+ channelManager,
153
+ iframeLifecycleManager: lifecycleManager
154
+ });
155
+ const request = async (args) => {
156
+ const isConnected = await lifecycleManager.isConnected;
157
+ if (!isConnected) {
158
+ throw new (0, _chunkNJJQPEELcjs.FrakRpcError)(
159
+ _chunkNJJQPEELcjs.RpcErrorCodes.clientNotConnected,
160
+ "The iframe provider isn't connected yet"
161
+ );
162
+ }
163
+ const result = new (0, _chunkNJJQPEELcjs.Deferred)();
164
+ const channelId = channelManager.createChannel(async (message) => {
165
+ const decompressed = await _chunkNJJQPEELcjs.decompressDataAndCheckHash.call(void 0, message.data);
166
+ if (decompressed.error) {
167
+ result.reject(
168
+ new (0, _chunkNJJQPEELcjs.FrakRpcError)(
169
+ decompressed.error.code,
170
+ decompressed.error.message,
171
+ _optionalChain([decompressed, 'access', _2 => _2.error, 'optionalAccess', _3 => _3.data])
172
+ )
173
+ );
174
+ } else {
175
+ result.resolve(decompressed.result);
176
+ }
177
+ channelManager.removeChannel(channelId);
178
+ });
179
+ const compressedMessage = await _chunkNJJQPEELcjs.hashAndCompressData.call(void 0, args);
180
+ messageHandler.sendEvent({
181
+ id: channelId,
182
+ topic: args.method,
183
+ data: compressedMessage
184
+ });
185
+ return result.promise;
186
+ };
187
+ const listenerRequest = async (args, callback) => {
188
+ const isConnected = await lifecycleManager.isConnected;
189
+ if (!isConnected) {
190
+ throw new (0, _chunkNJJQPEELcjs.FrakRpcError)(
191
+ _chunkNJJQPEELcjs.RpcErrorCodes.clientNotConnected,
192
+ "The iframe provider isn't connected yet"
193
+ );
194
+ }
195
+ const channelId = channelManager.createChannel(async (message) => {
196
+ const decompressed = await _chunkNJJQPEELcjs.decompressDataAndCheckHash.call(void 0, message.data);
197
+ if (decompressed.result) {
198
+ callback(decompressed.result);
199
+ } else {
200
+ throw new (0, _chunkNJJQPEELcjs.InternalError)("No valid result in the response");
201
+ }
202
+ });
203
+ const compressedMessage = await _chunkNJJQPEELcjs.hashAndCompressData.call(void 0, args);
204
+ messageHandler.sendEvent({
205
+ id: channelId,
206
+ topic: args.method,
207
+ data: compressedMessage
208
+ });
209
+ };
210
+ const stopHeartbeat = setupHeartbeat(messageHandler, lifecycleManager);
211
+ const destroy = async () => {
212
+ stopHeartbeat();
213
+ channelManager.destroy();
214
+ messageHandler.cleanup();
215
+ iframe.remove();
216
+ };
217
+ const waitForSetup = postConnectionSetup({
218
+ config,
219
+ messageHandler,
220
+ lifecycleManager
221
+ });
222
+ return {
223
+ config,
224
+ waitForConnection: lifecycleManager.isConnected,
225
+ waitForSetup,
226
+ request,
227
+ listenerRequest,
228
+ destroy
229
+ };
230
+ }
231
+ function setupHeartbeat(messageHandler, lifecycleManager) {
232
+ const HEARTBEAT_INTERVAL = 100;
233
+ const HEARTBEAT_TIMEOUT = 3e4;
234
+ let heartbeatInterval;
235
+ let timeoutId;
236
+ const sendHeartbeat = () => messageHandler.sendEvent({
237
+ iframeLifecycle: "heartbeat"
238
+ });
239
+ async function startHeartbeat() {
240
+ sendHeartbeat();
241
+ heartbeatInterval = setInterval(sendHeartbeat, HEARTBEAT_INTERVAL);
242
+ timeoutId = setTimeout(() => {
243
+ stopHeartbeat();
244
+ console.log("Heartbeat timeout: connection failed");
245
+ }, HEARTBEAT_TIMEOUT);
246
+ await lifecycleManager.isConnected;
247
+ stopHeartbeat();
248
+ }
249
+ function stopHeartbeat() {
250
+ if (heartbeatInterval) {
251
+ clearInterval(heartbeatInterval);
252
+ }
253
+ if (timeoutId) {
254
+ clearTimeout(timeoutId);
255
+ }
256
+ }
257
+ startHeartbeat();
258
+ return stopHeartbeat;
259
+ }
260
+ async function postConnectionSetup({
261
+ config,
262
+ messageHandler,
263
+ lifecycleManager
264
+ }) {
265
+ await lifecycleManager.isConnected;
266
+ const pushCss = async () => {
267
+ const cssLink = config.metadata.css;
268
+ if (!cssLink) return;
269
+ messageHandler.sendEvent({
270
+ clientLifecycle: "modal-css",
271
+ data: { cssLink }
272
+ });
273
+ };
274
+ const pushBackup = async () => {
275
+ if (typeof window === "undefined") return;
276
+ const backup = window.localStorage.getItem(BACKUP_KEY);
277
+ if (!backup) return;
278
+ messageHandler.sendEvent({
279
+ clientLifecycle: "restore-backup",
280
+ data: { backup }
281
+ });
282
+ };
283
+ await Promise.all([pushCss(), pushBackup()]);
284
+ }
285
+
286
+ // src/clients/setupClient.ts
287
+ async function setupClient({
288
+ config
289
+ }) {
290
+ const iframe = await _chunkNJJQPEELcjs.createIframe.call(void 0, {
291
+ config
292
+ });
293
+ if (!iframe) {
294
+ console.error("Failed to create iframe");
295
+ return;
296
+ }
297
+ const client = createIFrameFrakClient({
298
+ config,
299
+ iframe
300
+ });
301
+ await client.waitForSetup;
302
+ const waitForConnection = await client.waitForConnection;
303
+ if (!waitForConnection) {
304
+ console.error("Failed to connect to client");
305
+ return;
306
+ }
307
+ return client;
308
+ }
309
+
310
+
311
+
312
+
313
+
314
+
315
+
316
+
317
+
318
+
319
+
320
+
321
+
322
+
323
+
324
+
325
+
326
+ exports.ClientNotFound = _chunkNJJQPEELcjs.ClientNotFound; exports.Deferred = _chunkNJJQPEELcjs.Deferred; exports.FrakContextManager = _chunkNJJQPEELcjs.FrakContextManager; exports.FrakRpcError = _chunkNJJQPEELcjs.FrakRpcError; exports.RpcErrorCodes = _chunkNJJQPEELcjs.RpcErrorCodes; exports.baseIframeProps = _chunkNJJQPEELcjs.baseIframeProps; exports.compressJson = _chunkNJJQPEELcjs.compressJson; exports.createIFrameFrakClient = createIFrameFrakClient; exports.createIframe = _chunkNJJQPEELcjs.createIframe; exports.decompressDataAndCheckHash = _chunkNJJQPEELcjs.decompressDataAndCheckHash; exports.decompressJson = _chunkNJJQPEELcjs.decompressJson; exports.hashAndCompressData = _chunkNJJQPEELcjs.hashAndCompressData; exports.interactionTypes = _chunkPR3T7O5Icjs.interactionTypes; exports.productTypes = _chunkPR3T7O5Icjs.productTypes; exports.productTypesMask = _chunkPR3T7O5Icjs.productTypesMask; exports.setupClient = setupClient;
@@ -0,0 +1,269 @@
1
+ import { F as FrakWalletSdkConfig, a as FrakClient, b as FrakContext } from './context-GkNATUkF.cjs';
2
+ export { C as ClientLifecycleEvent, D as DisplayModalParamsType, E as ExtractedParametersFromRpc, w as ExtractedReturnTypeFromRpc, r as FinalActionType, q as FinalModalStepType, G as GetProductInformationReturnType, u as IFrameEvent, v as IFrameLifecycleEvent, t as IFrameRpcEvent, I as IFrameRpcSchema, s as IFrameTransport, L as LoginModalStepType, d as ModalRpcMetadata, e as ModalRpcStepsInput, f as ModalRpcStepsResultType, g as ModalStepMetadata, M as ModalStepTypes, o as OpenInteractionSessionModalStepType, n as OpenInteractionSessionReturnType, O as OpenSsoParamsType, P as ProductTypesKey, R as RpcResponse, l as SendTransactionModalStepType, m as SendTransactionReturnType, k as SendTransactionTxType, h as SiweAuthenticateModalStepType, j as SiweAuthenticateReturnType, i as SiweAuthenticationParams, S as SsoMetadata, W as WalletStatusReturnType, p as productTypes, c as productTypesMask } from './context-GkNATUkF.cjs';
3
+ export { P as PreparedInteraction, S as SendInteractionParamsType, a as SendInteractionReturnType } from './interaction-CTQ5-kqe.cjs';
4
+ import 'viem';
5
+ import 'viem/chains';
6
+ import 'viem/siwe';
7
+
8
+ /**
9
+ * Generic Frak RPC error
10
+ * @ignore
11
+ */
12
+ declare class FrakRpcError<T = undefined> extends Error {
13
+ code: number;
14
+ data?: T | undefined;
15
+ constructor(code: number, message: string, data?: T | undefined);
16
+ }
17
+ /** @ignore */
18
+ declare class ClientNotFound extends FrakRpcError {
19
+ constructor();
20
+ }
21
+ /**
22
+ * The different Frak RPC error codes
23
+ */
24
+ declare const RpcErrorCodes: {
25
+ readonly parseError: -32700;
26
+ readonly invalidRequest: -32600;
27
+ readonly methodNotFound: -32601;
28
+ readonly invalidParams: -32602;
29
+ readonly internalError: -32603;
30
+ readonly serverError: -32000;
31
+ readonly clientNotConnected: -32001;
32
+ readonly configError: -32002;
33
+ readonly corruptedResponse: -32003;
34
+ readonly clientAborted: -32004;
35
+ readonly walletNotConnected: -32005;
36
+ readonly serverErrorForInteractionDelegation: -32006;
37
+ };
38
+
39
+ /**
40
+ * The received encoded data from a client
41
+ * -> The encoded should contain a HashProtectedData once decoded
42
+ * @ignore
43
+ */
44
+ type CompressedData = Readonly<{
45
+ compressed: string;
46
+ compressedHash: string;
47
+ }>;
48
+ /**
49
+ * The encoded data to send to a client / received by a client
50
+ * @ignore
51
+ */
52
+ type HashProtectedData<DataType> = Readonly<DataType & {
53
+ validationHash: string;
54
+ }>;
55
+ /**
56
+ * Represent a key provider used for the hashed and secure compression
57
+ * @ignore
58
+ */
59
+ type KeyProvider<DataType> = (value: DataType) => string[];
60
+
61
+ /**
62
+ * Create a new iframe Frak client
63
+ * @param args
64
+ * @param args.config - The configuration to use for the Frak Wallet SDK
65
+ * @param args.iframe - The iframe to use for the communication
66
+ * @returns The created Frak Client
67
+ *
68
+ * @example
69
+ * const frakConfig: FrakWalletSdkConfig = {
70
+ * metadata: {
71
+ * name: "My app title",
72
+ * },
73
+ * }
74
+ * const iframe = await createIframe({ config: frakConfig });
75
+ * const client = createIFrameFrakClient({ config: frakConfig, iframe });
76
+ */
77
+ declare function createIFrameFrakClient({ config, iframe, }: {
78
+ config: FrakWalletSdkConfig;
79
+ iframe: HTMLIFrameElement;
80
+ }): FrakClient;
81
+
82
+ /**
83
+ * Directly setup the Frak client with an iframe
84
+ * Return when the FrakClient is ready (setup and communication estbalished with the wallet)
85
+ *
86
+ * @param config - The configuration to use for the Frak Wallet SDK
87
+ * @returns a Promise with the Frak Client
88
+ *
89
+ * @example
90
+ * const frakConfig: FrakWalletSdkConfig = {
91
+ * metadata: {
92
+ * name: "My app title",
93
+ * },
94
+ * }
95
+ * const client = await setupClient({ config: frakConfig });
96
+ */
97
+ declare function setupClient({ config, }: {
98
+ config: FrakWalletSdkConfig;
99
+ }): Promise<FrakClient | undefined>;
100
+
101
+ /**
102
+ * Base props for the iframe
103
+ * @ignore
104
+ */
105
+ declare const baseIframeProps: {
106
+ id: string;
107
+ name: string;
108
+ allow: string;
109
+ style: {
110
+ width: string;
111
+ height: string;
112
+ border: string;
113
+ position: string;
114
+ zIndex: number;
115
+ top: string;
116
+ left: string;
117
+ };
118
+ };
119
+ /**
120
+ * Create the Frak iframe
121
+ * @param args
122
+ * @param args.walletBaseUrl - Use `config.walletUrl` instead. Will be removed in future versions.
123
+ * @param args.config - The configuration object containing iframe options, including the replacement for `walletBaseUrl`.
124
+ */
125
+ declare function createIframe({ walletBaseUrl, config, }: {
126
+ walletBaseUrl?: string;
127
+ config?: FrakWalletSdkConfig;
128
+ }): Promise<HTMLIFrameElement | undefined>;
129
+
130
+ /**
131
+ * Compress the given params, and add hash protection to (rapidly) prevent interception modification
132
+ * @param data The params to encode
133
+ * @ignore
134
+ */
135
+ declare function hashAndCompressData<T>(data: T): Promise<CompressedData>;
136
+ /**
137
+ * Compress json data
138
+ * @param data
139
+ * @ignore
140
+ */
141
+ declare function compressJson(data: unknown): Promise<string>;
142
+
143
+ /**
144
+ * Decompress the given string
145
+ * @param compressedData The params to encode
146
+ * @ignore
147
+ */
148
+ declare function decompressDataAndCheckHash<T>(compressedData: CompressedData): Promise<HashProtectedData<T>>;
149
+ /**
150
+ * Decompress json data
151
+ * @param data
152
+ * @ignore
153
+ */
154
+ declare function decompressJson<T>(data: string): Promise<T | null>;
155
+
156
+ /**
157
+ * Compress the current Frak context
158
+ * @param context - The context to be compressed
159
+ * @returns A compressed string containing the Frak context
160
+ */
161
+ declare function compress(context?: Partial<FrakContext>): string | undefined;
162
+ /**
163
+ * Decompress the given Frak context
164
+ * @param context - The raw context to be decompressed into a `FrakContext`
165
+ * @returns The decompressed Frak context, or undefined if it fails
166
+ */
167
+ declare function decompress(context?: string): FrakContext | undefined;
168
+ /**
169
+ * Parse the current URL into a Frak Context
170
+ * @param args
171
+ * @param args.url - The url to parse
172
+ * @returns The parsed Frak context
173
+ */
174
+ declare function parse({ url }: {
175
+ url: string;
176
+ }): FrakContext | null | undefined;
177
+ /**
178
+ * Populate the current url with the given Frak context
179
+ * @param args
180
+ * @param args.url - The url to update
181
+ * @param args.context - The context to update
182
+ * @returns The new url with the Frak context
183
+ */
184
+ declare function update({ url, context, }: {
185
+ url?: string;
186
+ context: Partial<FrakContext>;
187
+ }): string | null;
188
+ /**
189
+ * Remove Frak context from current url
190
+ * @param url - The url to update
191
+ * @returns The new url without the Frak context
192
+ */
193
+ declare function remove(url: string): string;
194
+ /**
195
+ * Replace the current url with the given Frak context
196
+ * @param args
197
+ * @param args.url - The url to update
198
+ * @param args.context - The context to update
199
+ */
200
+ declare function replaceUrl({ url: baseUrl, context, }: {
201
+ url?: string;
202
+ context: Partial<FrakContext> | null;
203
+ }): void;
204
+ /**
205
+ * Export our frak context "class"
206
+ */
207
+ declare const FrakContextManager: {
208
+ compress: typeof compress;
209
+ decompress: typeof decompress;
210
+ parse: typeof parse;
211
+ update: typeof update;
212
+ remove: typeof remove;
213
+ replaceUrl: typeof replaceUrl;
214
+ };
215
+
216
+ /**
217
+ * Simple deferred promise wrapper
218
+ * @ignore
219
+ */
220
+ declare class Deferred<T> {
221
+ private readonly _promise;
222
+ private _resolve;
223
+ private _reject;
224
+ constructor();
225
+ get promise(): Promise<T>;
226
+ resolve: (value: T | PromiseLike<T>) => void;
227
+ reject: (reason?: unknown) => void;
228
+ }
229
+
230
+ /**
231
+ * The final keys for each interaction types (e.g. `openArticle`) -> interaction type
232
+ * @inline
233
+ */
234
+ type InteractionTypesKey = {
235
+ [K in keyof typeof interactionTypes]: keyof (typeof interactionTypes)[K];
236
+ }[keyof typeof interactionTypes];
237
+ /**
238
+ * The keys for each interaction types (e.g. `press.openArticle`) -> category_type.interaction_type
239
+ * @inline
240
+ */
241
+ type FullInteractionTypesKey = {
242
+ [Category in keyof typeof interactionTypes]: `${Category & string}.${keyof (typeof interactionTypes)[Category] & string}`;
243
+ }[keyof typeof interactionTypes];
244
+ /**
245
+ * Each interactions types according to the product types
246
+ */
247
+ declare const interactionTypes: {
248
+ readonly press: {
249
+ readonly openArticle: "0xc0a24ffb";
250
+ readonly readArticle: "0xd5bd0fbe";
251
+ };
252
+ readonly dapp: {
253
+ readonly proofVerifiableStorageUpdate: "0x2ab2aeef";
254
+ readonly callableVerifiableStorageUpdate: "0xa07da986";
255
+ };
256
+ readonly webshop: {
257
+ readonly open: "0xb311798f";
258
+ };
259
+ readonly referral: {
260
+ readonly referred: "0x010cc3b9";
261
+ readonly createLink: "0xb2c0f17c";
262
+ };
263
+ readonly purchase: {
264
+ readonly started: "0xd87e90c3";
265
+ readonly completed: "0x8403aeb4";
266
+ };
267
+ };
268
+
269
+ export { ClientNotFound, type CompressedData, Deferred, FrakClient, FrakContext, FrakContextManager, FrakRpcError, FrakWalletSdkConfig, type FullInteractionTypesKey, type HashProtectedData, type InteractionTypesKey, type KeyProvider, RpcErrorCodes, baseIframeProps, compressJson, createIFrameFrakClient, createIframe, decompressDataAndCheckHash, decompressJson, hashAndCompressData, interactionTypes, setupClient };