@fruga/sdk 1.0.2 → 1.3.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 +8 -1
- package/dist/connect.d.mts +16 -1
- package/dist/connect.d.ts +16 -1
- package/dist/connect.js +1 -1
- package/dist/connect.mjs +1 -1
- package/dist/index.d.mts +324 -21
- package/dist/index.d.ts +324 -21
- package/dist/index.js +356 -216
- package/dist/index.mjs +337 -210
- package/dist/loader-react.d.mts +140 -25
- package/dist/loader-react.d.ts +140 -25
- package/dist/loader-react.js +1 -1
- package/dist/loader-react.mjs +1 -1
- package/dist/loader.d.mts +168 -6
- package/dist/loader.d.ts +168 -6
- package/dist/loader.global.js +3 -42
- package/dist/loader.js +1 -1
- package/dist/loader.mjs +1 -1
- package/dist/relay.css +1 -1
- package/dist/relay.d.mts +20 -2
- package/dist/relay.d.ts +20 -2
- package/dist/relay.js +1 -1
- package/dist/relay.mjs +1 -1
- package/package.json +15 -3
package/dist/index.mjs
CHANGED
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
var __defProp = Object.defineProperty;
|
|
2
|
+
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
|
|
3
|
+
var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
|
|
4
|
+
|
|
1
5
|
// packages/core/src/domain/entities/Theme.ts
|
|
2
6
|
var DEFAULT_THEME = {
|
|
3
7
|
widgetTitle: "Savings Box",
|
|
@@ -51,50 +55,51 @@ function getResolvedTheme(theme) {
|
|
|
51
55
|
};
|
|
52
56
|
}
|
|
53
57
|
|
|
54
|
-
// packages/core/src/
|
|
55
|
-
var
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
58
|
+
// packages/core/src/domain/entities/Messages.ts
|
|
59
|
+
var PROTOCOL_VERSION = "1.0";
|
|
60
|
+
var MESSAGE_TYPES = [
|
|
61
|
+
"NEED_TOKEN",
|
|
62
|
+
"FRUGA_TOKEN_UPDATE",
|
|
63
|
+
"FRUGA_GET_BALANCE",
|
|
64
|
+
"FRUGA_BALANCE_RESPONSE",
|
|
65
|
+
"FRUGA_RESIZE",
|
|
66
|
+
"FRUGA_HOST_RESIZE",
|
|
67
|
+
"FRUGA_READY",
|
|
68
|
+
"FRUGA_INIT"
|
|
69
|
+
];
|
|
70
|
+
var isFrugaMessageType = (value) => typeof value === "string" && MESSAGE_TYPES.includes(value);
|
|
71
|
+
var NEED_TOKEN_REASONS = ["ttl", "unauthorized"];
|
|
72
|
+
var isFrugaMessage = (data) => {
|
|
73
|
+
if (typeof data !== "object" || data === null) return false;
|
|
74
|
+
const candidate = data;
|
|
75
|
+
if (!isFrugaMessageType(candidate.type)) return false;
|
|
76
|
+
if (candidate.version !== void 0 && typeof candidate.version !== "string") return false;
|
|
77
|
+
if (candidate.type === "NEED_TOKEN") {
|
|
78
|
+
const { reason } = data;
|
|
79
|
+
return typeof reason === "string" && NEED_TOKEN_REASONS.includes(reason);
|
|
59
80
|
}
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
pending: data.pending ?? 0,
|
|
77
|
-
totalBalance: (data.available || 0) + (data.pending || 0),
|
|
78
|
-
lifetimeEarned: data.lifetimeEarned ?? 0,
|
|
79
|
-
lifetimeRedeemed: data.lifetimeRedeemed ?? 0,
|
|
80
|
-
updatedAt: data.updatedAt ?? (/* @__PURE__ */ new Date()).toISOString(),
|
|
81
|
-
requiredActions: data.requiredActions ?? null
|
|
82
|
-
};
|
|
83
|
-
} catch (error) {
|
|
84
|
-
console.warn(`[FrugaSDK] Network error fetching wallet. Returning zeroed wallet.`, error);
|
|
85
|
-
return this.getEmptyWallet();
|
|
86
|
-
}
|
|
81
|
+
return true;
|
|
82
|
+
};
|
|
83
|
+
var protocolMajor = (v) => Number.parseInt(v.split(".")[0] ?? "", 10);
|
|
84
|
+
|
|
85
|
+
// packages/core/src/domain/entities/Constants.ts
|
|
86
|
+
var HOST_MOBILE_BREAKPOINT = 768;
|
|
87
|
+
|
|
88
|
+
// packages/core/src/domain/entities/index.ts
|
|
89
|
+
var resolvePayoutMode = (value) => value === "PARTNER" || value === "USER" ? value : null;
|
|
90
|
+
|
|
91
|
+
// packages/core/src/domain/errors/HttpError.ts
|
|
92
|
+
var HttpError = class extends Error {
|
|
93
|
+
constructor(status, message) {
|
|
94
|
+
super(message);
|
|
95
|
+
this.status = status;
|
|
96
|
+
this.name = "HttpError";
|
|
87
97
|
}
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
lifetimeEarned: 0,
|
|
94
|
-
lifetimeRedeemed: 0,
|
|
95
|
-
updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
96
|
-
requiredActions: null
|
|
97
|
-
};
|
|
98
|
+
};
|
|
99
|
+
var UnauthorizedError = class extends HttpError {
|
|
100
|
+
constructor(message = "Unauthorized") {
|
|
101
|
+
super(401, message);
|
|
102
|
+
this.name = "UnauthorizedError";
|
|
98
103
|
}
|
|
99
104
|
};
|
|
100
105
|
|
|
@@ -124,6 +129,41 @@ var BaseHttpService = class {
|
|
|
124
129
|
});
|
|
125
130
|
}
|
|
126
131
|
};
|
|
132
|
+
var assertOk = (response, context) => {
|
|
133
|
+
if (response.ok) return;
|
|
134
|
+
if (response.status === 401) {
|
|
135
|
+
throw new UnauthorizedError(`${context}: unauthorized`);
|
|
136
|
+
}
|
|
137
|
+
throw new HttpError(response.status, `${context}: ${response.status}`);
|
|
138
|
+
};
|
|
139
|
+
|
|
140
|
+
// packages/core/src/infrastructure/adapters/HttpWalletService.ts
|
|
141
|
+
var HttpWalletService = class {
|
|
142
|
+
constructor(baseUrl, token) {
|
|
143
|
+
this.baseUrl = baseUrl;
|
|
144
|
+
this.token = token;
|
|
145
|
+
}
|
|
146
|
+
async getWallet() {
|
|
147
|
+
const response = await fetch(`${this.baseUrl}/api/wallet`, {
|
|
148
|
+
method: "GET",
|
|
149
|
+
headers: {
|
|
150
|
+
"Accept": "application/json",
|
|
151
|
+
"Authorization": `Bearer ${this.token}`
|
|
152
|
+
}
|
|
153
|
+
});
|
|
154
|
+
assertOk(response, "Failed to fetch wallet");
|
|
155
|
+
const data = await response.json();
|
|
156
|
+
return {
|
|
157
|
+
available: data.available ?? 0,
|
|
158
|
+
pending: data.pending ?? 0,
|
|
159
|
+
totalBalance: (data.available || 0) + (data.pending || 0),
|
|
160
|
+
lifetimeEarned: data.lifetimeEarned ?? 0,
|
|
161
|
+
lifetimeRedeemed: data.lifetimeRedeemed ?? 0,
|
|
162
|
+
updatedAt: data.updatedAt ?? (/* @__PURE__ */ new Date()).toISOString(),
|
|
163
|
+
requiredActions: data.requiredActions ?? null
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
};
|
|
127
167
|
|
|
128
168
|
// packages/core/src/infrastructure/adapters/HttpActivityService.ts
|
|
129
169
|
var HttpActivityService = class extends BaseHttpService {
|
|
@@ -131,27 +171,12 @@ var HttpActivityService = class extends BaseHttpService {
|
|
|
131
171
|
super(baseUrl, token);
|
|
132
172
|
}
|
|
133
173
|
async getActivity(pageNumber, pageSize) {
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
console.warn(`[FrugaSDK] Failed to fetch activity: ${response.status}. Returning empty activity.`);
|
|
138
|
-
return this.getEmptyActivity();
|
|
139
|
-
}
|
|
140
|
-
const data = await response.json();
|
|
141
|
-
return {
|
|
142
|
-
...data,
|
|
143
|
-
transactions: data.transactions || []
|
|
144
|
-
};
|
|
145
|
-
} catch (error) {
|
|
146
|
-
console.warn(`[FrugaSDK] Network error fetching activity. Returning empty activity.`, error);
|
|
147
|
-
return this.getEmptyActivity();
|
|
148
|
-
}
|
|
149
|
-
}
|
|
150
|
-
getEmptyActivity() {
|
|
174
|
+
const response = await this.fetchQuery("/api/activity", { pageNumber, pageSize });
|
|
175
|
+
assertOk(response, "Failed to fetch activity");
|
|
176
|
+
const data = await response.json();
|
|
151
177
|
return {
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
transactions: []
|
|
178
|
+
...data,
|
|
179
|
+
transactions: data.transactions || []
|
|
155
180
|
};
|
|
156
181
|
}
|
|
157
182
|
};
|
|
@@ -162,53 +187,25 @@ var HttpOffersService = class extends BaseHttpService {
|
|
|
162
187
|
super(baseUrl, token);
|
|
163
188
|
}
|
|
164
189
|
async getCategories() {
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
return [];
|
|
170
|
-
}
|
|
171
|
-
const data = await response.json();
|
|
172
|
-
const categories = data.categories || [];
|
|
173
|
-
return categories;
|
|
174
|
-
} catch (error) {
|
|
175
|
-
console.warn(`[FrugaSDK] Network error fetching offer categories. Returning empty list.`, error);
|
|
176
|
-
return [];
|
|
177
|
-
}
|
|
190
|
+
const response = await this.fetchQuery("/api/offers/categories");
|
|
191
|
+
assertOk(response, "Failed to fetch offer categories");
|
|
192
|
+
const data = await response.json();
|
|
193
|
+
return data.categories || [];
|
|
178
194
|
}
|
|
179
195
|
async getOffers(query) {
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
console.warn(`[FrugaSDK] Failed to fetch offers: ${response.status}. Returning empty response.`);
|
|
191
|
-
return this.getEmptyPaginatedResponse();
|
|
192
|
-
}
|
|
193
|
-
const data = await response.json();
|
|
194
|
-
return {
|
|
195
|
-
...data,
|
|
196
|
-
content: data.content || []
|
|
197
|
-
};
|
|
198
|
-
} catch (error) {
|
|
199
|
-
console.warn(`[FrugaSDK] Network error fetching offers. Returning empty response.`, error);
|
|
200
|
-
return this.getEmptyPaginatedResponse();
|
|
201
|
-
}
|
|
202
|
-
}
|
|
203
|
-
getEmptyPaginatedResponse() {
|
|
196
|
+
const response = await this.fetchQuery("/api/offers", {
|
|
197
|
+
pageNumber: query.pageNumber ?? 0,
|
|
198
|
+
pageSize: query.pageSize ?? 10,
|
|
199
|
+
type: query.type,
|
|
200
|
+
query: query.query,
|
|
201
|
+
featured: query.featured,
|
|
202
|
+
categories: query.categories
|
|
203
|
+
});
|
|
204
|
+
assertOk(response, "Failed to fetch offers");
|
|
205
|
+
const data = await response.json();
|
|
204
206
|
return {
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
totalPages: 0,
|
|
208
|
-
number: 0,
|
|
209
|
-
size: 0,
|
|
210
|
-
numberOfElements: 0,
|
|
211
|
-
numberOfPages: 0
|
|
207
|
+
...data,
|
|
208
|
+
content: data.content || []
|
|
212
209
|
};
|
|
213
210
|
}
|
|
214
211
|
};
|
|
@@ -220,23 +217,16 @@ var HttpPayoutService = class {
|
|
|
220
217
|
this.token = token;
|
|
221
218
|
}
|
|
222
219
|
async listMethods() {
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
"Authorization": `Bearer ${this.token}`
|
|
229
|
-
}
|
|
230
|
-
});
|
|
231
|
-
if (!response.ok) {
|
|
232
|
-
throw new Error(`Failed to list payout methods: ${response.status}`);
|
|
220
|
+
const response = await fetch(`${this.baseUrl}/api/payout-methods`, {
|
|
221
|
+
method: "GET",
|
|
222
|
+
headers: {
|
|
223
|
+
"Accept": "application/json",
|
|
224
|
+
"Authorization": `Bearer ${this.token}`
|
|
233
225
|
}
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
return [];
|
|
239
|
-
}
|
|
226
|
+
});
|
|
227
|
+
assertOk(response, "Failed to list payout methods");
|
|
228
|
+
const data = await response.json();
|
|
229
|
+
return data.items || [];
|
|
240
230
|
}
|
|
241
231
|
async addBankMethod(payload) {
|
|
242
232
|
const response = await fetch(`${this.baseUrl}/api/payout-methods/bank`, {
|
|
@@ -248,9 +238,7 @@ var HttpPayoutService = class {
|
|
|
248
238
|
},
|
|
249
239
|
body: JSON.stringify(payload)
|
|
250
240
|
});
|
|
251
|
-
|
|
252
|
-
throw new Error(`Failed to add bank payout method: ${response.status}`);
|
|
253
|
-
}
|
|
241
|
+
assertOk(response, "Failed to add bank payout method");
|
|
254
242
|
return response.json();
|
|
255
243
|
}
|
|
256
244
|
async deleteMethod(id) {
|
|
@@ -261,53 +249,124 @@ var HttpPayoutService = class {
|
|
|
261
249
|
"Authorization": `Bearer ${this.token}`
|
|
262
250
|
}
|
|
263
251
|
});
|
|
264
|
-
|
|
265
|
-
|
|
252
|
+
assertOk(response, "Failed to delete payout method");
|
|
253
|
+
}
|
|
254
|
+
};
|
|
255
|
+
|
|
256
|
+
// packages/core/src/infrastructure/adapters/PostMessageTransport.ts
|
|
257
|
+
var WILDCARD_ORIGIN = "*";
|
|
258
|
+
var PostMessageTransport = class {
|
|
259
|
+
constructor(options) {
|
|
260
|
+
__publicField(this, "target");
|
|
261
|
+
__publicField(this, "acceptSource");
|
|
262
|
+
__publicField(this, "listenOn");
|
|
263
|
+
__publicField(this, "handlers", /* @__PURE__ */ new Set());
|
|
264
|
+
__publicField(this, "pinOriginFrom");
|
|
265
|
+
__publicField(this, "targetOrigin");
|
|
266
|
+
__publicField(this, "acceptOrigin");
|
|
267
|
+
__publicField(this, "pinned", null);
|
|
268
|
+
__publicField(this, "disposed", false);
|
|
269
|
+
__publicField(this, "onMessage", (event) => {
|
|
270
|
+
if (this.disposed) return;
|
|
271
|
+
if (this.acceptSource && event.source !== this.acceptSource) return;
|
|
272
|
+
if (this.acceptOrigin && event.origin !== this.acceptOrigin) return;
|
|
273
|
+
if (!isFrugaMessage(event.data)) return;
|
|
274
|
+
if (!this.admitWhileUnpinned(event.data.type, event.origin)) return;
|
|
275
|
+
const message = typeof event.data.version === "string" && event.data.version.length > 0 ? event.data : { ...event.data, version: PROTOCOL_VERSION };
|
|
276
|
+
for (const handler of [...this.handlers]) {
|
|
277
|
+
handler(message);
|
|
278
|
+
}
|
|
279
|
+
});
|
|
280
|
+
const listenOn = options.listenOn ?? (typeof window !== "undefined" ? window : void 0);
|
|
281
|
+
if (!listenOn) {
|
|
282
|
+
throw new Error("PostMessageTransport: no window to listen on; pass options.listenOn");
|
|
283
|
+
}
|
|
284
|
+
this.target = options.target;
|
|
285
|
+
this.targetOrigin = options.targetOrigin;
|
|
286
|
+
this.acceptSource = options.acceptSource;
|
|
287
|
+
this.acceptOrigin = options.acceptOrigin;
|
|
288
|
+
this.pinOriginFrom = options.pinOriginFrom;
|
|
289
|
+
this.listenOn = listenOn;
|
|
290
|
+
this.listenOn.addEventListener("message", this.onMessage);
|
|
291
|
+
}
|
|
292
|
+
/**
|
|
293
|
+
* Origin the transport has locked onto, `'*'` when it pinned to the wildcard
|
|
294
|
+
* after an opaque-origin handshake, or `null` while still unpinned.
|
|
295
|
+
*/
|
|
296
|
+
get pinnedOrigin() {
|
|
297
|
+
return this.pinned;
|
|
298
|
+
}
|
|
299
|
+
/** Lock both the outbound and inbound origin once the peer origin is known. */
|
|
300
|
+
pinOrigin(origin) {
|
|
301
|
+
this.targetOrigin = origin;
|
|
302
|
+
this.acceptOrigin = origin;
|
|
303
|
+
this.pinned = origin;
|
|
304
|
+
}
|
|
305
|
+
send(message) {
|
|
306
|
+
if (this.disposed) return;
|
|
307
|
+
const payload = typeof message.version === "string" && message.version.length > 0 ? message : { ...message, version: PROTOCOL_VERSION };
|
|
308
|
+
this.target.postMessage(payload, this.targetOrigin);
|
|
309
|
+
}
|
|
310
|
+
subscribe(handler) {
|
|
311
|
+
if (this.disposed) {
|
|
312
|
+
throw new Error("PostMessageTransport: cannot subscribe after dispose()");
|
|
313
|
+
}
|
|
314
|
+
this.handlers.add(handler);
|
|
315
|
+
return () => {
|
|
316
|
+
this.handlers.delete(handler);
|
|
317
|
+
};
|
|
318
|
+
}
|
|
319
|
+
dispose() {
|
|
320
|
+
if (this.disposed) return;
|
|
321
|
+
this.disposed = true;
|
|
322
|
+
this.handlers.clear();
|
|
323
|
+
this.listenOn.removeEventListener("message", this.onMessage);
|
|
324
|
+
}
|
|
325
|
+
/**
|
|
326
|
+
* Gate applied only while `pinOriginFrom` is set and the transport is still
|
|
327
|
+
* unpinned: nothing but the pinning message type gets through, and the first
|
|
328
|
+
* one that does completes the handshake for everything after it.
|
|
329
|
+
*/
|
|
330
|
+
admitWhileUnpinned(type, origin) {
|
|
331
|
+
if (!this.pinOriginFrom || this.pinned !== null) return true;
|
|
332
|
+
if (type !== this.pinOriginFrom) return false;
|
|
333
|
+
if (origin && origin !== "null") {
|
|
334
|
+
this.pinOrigin(origin);
|
|
335
|
+
return true;
|
|
266
336
|
}
|
|
337
|
+
this.pinned = WILDCARD_ORIGIN;
|
|
338
|
+
return true;
|
|
267
339
|
}
|
|
268
340
|
};
|
|
269
341
|
|
|
270
|
-
// packages/core/src/
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
transition: "opacity 0.2s",
|
|
285
|
-
backgroundColor: variant === "primary" ? "#007bff" : "#6c757d",
|
|
286
|
-
color: "white",
|
|
287
|
-
...style
|
|
288
|
-
};
|
|
289
|
-
return /* @__PURE__ */ jsx("button", { style: baseStyle, ...props, children });
|
|
342
|
+
// packages/core/src/infrastructure/adapters/createHostTransport.ts
|
|
343
|
+
var createHostTransport = (options) => {
|
|
344
|
+
const win = options?.listenOn ?? (typeof window !== "undefined" ? window : void 0);
|
|
345
|
+
if (!win) {
|
|
346
|
+
throw new Error("createHostTransport: no window available; pass options.listenOn");
|
|
347
|
+
}
|
|
348
|
+
const framed = win.parent !== win;
|
|
349
|
+
return new PostMessageTransport({
|
|
350
|
+
target: win.parent,
|
|
351
|
+
targetOrigin: "*",
|
|
352
|
+
pinOriginFrom: "FRUGA_INIT",
|
|
353
|
+
listenOn: win,
|
|
354
|
+
...framed ? { acceptSource: win.parent } : {}
|
|
355
|
+
});
|
|
290
356
|
};
|
|
291
|
-
var WidgetHeader = ({ title }) => /* @__PURE__ */ jsx("header", { style: {
|
|
292
|
-
padding: "16px",
|
|
293
|
-
borderBottom: "1px solid #eee",
|
|
294
|
-
display: "flex",
|
|
295
|
-
alignItems: "center",
|
|
296
|
-
justifyContent: "space-between"
|
|
297
|
-
}, children: /* @__PURE__ */ jsx("h3", { style: { margin: 0, fontSize: "1.1rem" }, children: title }) });
|
|
298
357
|
|
|
299
358
|
// packages/core/src/presentation/components/WidgetContainer.tsx
|
|
300
|
-
import { useState, useEffect as
|
|
359
|
+
import { useState, useEffect as useEffect3, useRef as useRef3 } from "react";
|
|
301
360
|
|
|
302
361
|
// packages/core/src/presentation/components/ui/LauncherIcon.tsx
|
|
303
362
|
import { Sparkles, CreditCard, MoveUpRight, Gift } from "lucide-react";
|
|
304
|
-
import { jsx
|
|
363
|
+
import { jsx } from "react/jsx-runtime";
|
|
305
364
|
function LauncherIcon({ theme }) {
|
|
306
365
|
if (theme.launcherIcon === "none") return null;
|
|
307
|
-
if (theme.launcherIcon === "sparkles") return /* @__PURE__ */
|
|
308
|
-
if (theme.launcherIcon === "credit") return /* @__PURE__ */
|
|
309
|
-
if (theme.launcherIcon === "arrow") return /* @__PURE__ */
|
|
310
|
-
return /* @__PURE__ */
|
|
366
|
+
if (theme.launcherIcon === "sparkles") return /* @__PURE__ */ jsx(Sparkles, { size: 20 });
|
|
367
|
+
if (theme.launcherIcon === "credit") return /* @__PURE__ */ jsx(CreditCard, { size: 20 });
|
|
368
|
+
if (theme.launcherIcon === "arrow") return /* @__PURE__ */ jsx(MoveUpRight, { size: 20 });
|
|
369
|
+
return /* @__PURE__ */ jsx(Gift, { size: 20 });
|
|
311
370
|
}
|
|
312
371
|
|
|
313
372
|
// packages/core/src/presentation/hooks/useActivityDetector.ts
|
|
@@ -367,6 +426,8 @@ var useWidgetStore = create((set) => ({
|
|
|
367
426
|
pending: 0,
|
|
368
427
|
mode: "light",
|
|
369
428
|
token: null,
|
|
429
|
+
tokenEpoch: 0,
|
|
430
|
+
authStatus: "pending",
|
|
370
431
|
partnerPayout: null,
|
|
371
432
|
isAddingPayoutFromAlert: false,
|
|
372
433
|
showPayoutSuccess: false,
|
|
@@ -376,8 +437,12 @@ var useWidgetStore = create((set) => ({
|
|
|
376
437
|
setIdle: (idle) => set({ isIdle: idle }),
|
|
377
438
|
setBalances: (available, pending) => set({ available, pending }),
|
|
378
439
|
setMode: (mode) => set({ mode }),
|
|
379
|
-
setToken: (token) => set(
|
|
380
|
-
|
|
440
|
+
setToken: (token) => set((state) => ({
|
|
441
|
+
token,
|
|
442
|
+
tokenEpoch: state.token === token ? state.tokenEpoch : state.tokenEpoch + 1,
|
|
443
|
+
authStatus: token ? "authenticated" : "pending"
|
|
444
|
+
})),
|
|
445
|
+
setPartnerPayout: (payout) => set({ partnerPayout: resolvePayoutMode(payout) }),
|
|
381
446
|
setIsAddingPayoutFromAlert: (val) => set({ isAddingPayoutFromAlert: val }),
|
|
382
447
|
setShowPayoutSuccess: (val) => set({ showPayoutSuccess: val })
|
|
383
448
|
}));
|
|
@@ -425,6 +490,42 @@ function useActivityDetector() {
|
|
|
425
490
|
}, [setFocused, setIdle]);
|
|
426
491
|
}
|
|
427
492
|
|
|
493
|
+
// packages/core/src/presentation/hooks/useMessageTransport.ts
|
|
494
|
+
import { useContext, useEffect as useEffect2, useRef as useRef2 } from "react";
|
|
495
|
+
|
|
496
|
+
// packages/core/src/presentation/context/MessageTransportContext.tsx
|
|
497
|
+
import { createContext } from "react";
|
|
498
|
+
import { jsx as jsx2 } from "react/jsx-runtime";
|
|
499
|
+
var MessageTransportContext = createContext(null);
|
|
500
|
+
var MessageTransportProvider = ({
|
|
501
|
+
transport,
|
|
502
|
+
children
|
|
503
|
+
}) => {
|
|
504
|
+
return /* @__PURE__ */ jsx2(MessageTransportContext.Provider, { value: transport, children });
|
|
505
|
+
};
|
|
506
|
+
|
|
507
|
+
// packages/core/src/presentation/hooks/useMessageTransport.ts
|
|
508
|
+
var useMessageTransport = () => {
|
|
509
|
+
const transport = useContext(MessageTransportContext);
|
|
510
|
+
if (!transport) {
|
|
511
|
+
throw new Error(
|
|
512
|
+
"useMessageTransport: no transport in context. Wrap the widget tree in <MessageTransportProvider transport={...}>."
|
|
513
|
+
);
|
|
514
|
+
}
|
|
515
|
+
return transport;
|
|
516
|
+
};
|
|
517
|
+
var useFrugaMessage = (type, handler) => {
|
|
518
|
+
const transport = useMessageTransport();
|
|
519
|
+
const handlerRef = useRef2(handler);
|
|
520
|
+
handlerRef.current = handler;
|
|
521
|
+
useEffect2(() => {
|
|
522
|
+
return transport.subscribe((message) => {
|
|
523
|
+
if (message.type !== type) return;
|
|
524
|
+
handlerRef.current(message);
|
|
525
|
+
});
|
|
526
|
+
}, [transport, type]);
|
|
527
|
+
};
|
|
528
|
+
|
|
428
529
|
// packages/core/src/presentation/components/WidgetContainer.tsx
|
|
429
530
|
import { X } from "lucide-react";
|
|
430
531
|
import { Fragment, jsx as jsx3, jsxs } from "react/jsx-runtime";
|
|
@@ -439,35 +540,45 @@ var WidgetContainer = ({
|
|
|
439
540
|
const theme = themeProp || DEFAULT_THEME;
|
|
440
541
|
const [hostHeight, setHostHeight] = useState(initialHostHeight);
|
|
441
542
|
const [isMobile, setIsMobile] = useState(initialIsMobile);
|
|
442
|
-
const
|
|
543
|
+
const [hostThemeMode, setHostThemeMode] = useState(void 0);
|
|
544
|
+
const effectiveTheme = theme.allowUserThemeControl ? { ...theme, mode: storeMode } : { ...theme, mode: hostThemeMode ?? theme.mode ?? "light" };
|
|
443
545
|
const resolved = getResolvedTheme(effectiveTheme);
|
|
444
|
-
const containerRef =
|
|
445
|
-
const buttonRef =
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
546
|
+
const containerRef = useRef3(null);
|
|
547
|
+
const buttonRef = useRef3(null);
|
|
548
|
+
const transport = useMessageTransport();
|
|
549
|
+
useFrugaMessage("FRUGA_HOST_RESIZE", (message) => {
|
|
550
|
+
setHostHeight(message.hostHeight);
|
|
551
|
+
setIsMobile(message.isHostMobile);
|
|
552
|
+
});
|
|
553
|
+
useFrugaMessage("FRUGA_TOKEN_UPDATE", (message) => {
|
|
554
|
+
useWidgetStore.getState().setToken(message.token);
|
|
555
|
+
});
|
|
556
|
+
useFrugaMessage("FRUGA_GET_BALANCE", (message) => {
|
|
557
|
+
const { available, pending } = useWidgetStore.getState();
|
|
558
|
+
transport.send({
|
|
559
|
+
type: "FRUGA_BALANCE_RESPONSE",
|
|
560
|
+
version: PROTOCOL_VERSION,
|
|
561
|
+
requestId: message.requestId,
|
|
562
|
+
available,
|
|
563
|
+
pending
|
|
564
|
+
});
|
|
565
|
+
});
|
|
566
|
+
useFrugaMessage("FRUGA_INIT", (message) => {
|
|
567
|
+
setHostHeight(message.host.height);
|
|
568
|
+
setIsMobile(message.host.isMobile);
|
|
569
|
+
if (message.token) {
|
|
570
|
+
useWidgetStore.getState().setToken(message.token);
|
|
571
|
+
}
|
|
572
|
+
if (message.overrides?.panelOpen) {
|
|
573
|
+
useWidgetStore.getState().setOpen(true);
|
|
574
|
+
}
|
|
575
|
+
const overrideThemeMode = message.overrides?.theme;
|
|
576
|
+
if (overrideThemeMode) {
|
|
577
|
+
setHostThemeMode(overrideThemeMode);
|
|
578
|
+
useWidgetStore.getState().setMode(overrideThemeMode);
|
|
579
|
+
}
|
|
580
|
+
});
|
|
581
|
+
useEffect3(() => {
|
|
471
582
|
if (!containerRef.current) return;
|
|
472
583
|
const sendResize = () => {
|
|
473
584
|
if (!containerRef.current) return;
|
|
@@ -480,18 +591,19 @@ var WidgetContainer = ({
|
|
|
480
591
|
expectedWidth = window.innerWidth;
|
|
481
592
|
expectedHeight = hostHeight;
|
|
482
593
|
}
|
|
483
|
-
|
|
594
|
+
transport.send({
|
|
484
595
|
type: "FRUGA_RESIZE",
|
|
596
|
+
version: PROTOCOL_VERSION,
|
|
485
597
|
height: expectedHeight,
|
|
486
598
|
width: expectedWidth,
|
|
487
599
|
isFullscreen: isMobile && isOpen
|
|
488
|
-
}
|
|
600
|
+
});
|
|
489
601
|
};
|
|
490
602
|
const observer = new ResizeObserver(() => sendResize());
|
|
491
603
|
observer.observe(containerRef.current);
|
|
492
604
|
sendResize();
|
|
493
605
|
return () => observer.disconnect();
|
|
494
|
-
}, [isOpen, hostHeight, isMobile]);
|
|
606
|
+
}, [isOpen, hostHeight, isMobile, transport]);
|
|
495
607
|
return /* @__PURE__ */ jsxs(
|
|
496
608
|
"div",
|
|
497
609
|
{
|
|
@@ -590,10 +702,10 @@ var useWidget = () => {
|
|
|
590
702
|
};
|
|
591
703
|
|
|
592
704
|
// packages/core/src/presentation/hooks/useDebounce.ts
|
|
593
|
-
import { useEffect as
|
|
705
|
+
import { useEffect as useEffect4, useState as useState2 } from "react";
|
|
594
706
|
var useDebounce = (value, delay) => {
|
|
595
707
|
const [debouncedValue, setDebouncedValue] = useState2(value);
|
|
596
|
-
|
|
708
|
+
useEffect4(() => {
|
|
597
709
|
const handleTimeout = setTimeout(() => {
|
|
598
710
|
setDebouncedValue(value);
|
|
599
711
|
}, delay);
|
|
@@ -605,10 +717,8 @@ var useDebounce = (value, delay) => {
|
|
|
605
717
|
};
|
|
606
718
|
|
|
607
719
|
// packages/core/src/infrastructure/storage/StorageService.ts
|
|
608
|
-
var
|
|
609
|
-
|
|
610
|
-
BOOTSTRAP_CONFIG: "fruga_active_config"
|
|
611
|
-
};
|
|
720
|
+
var LEGACY_BOOTSTRAP_STORAGE_KEY = "fruga_bootstrap";
|
|
721
|
+
var storageKey = (partnerKey, name) => `fruga:${partnerKey}:${name}`;
|
|
612
722
|
var StorageService = class {
|
|
613
723
|
static set(key, value) {
|
|
614
724
|
if (typeof window === "undefined") return;
|
|
@@ -630,20 +740,22 @@ var StorageService = class {
|
|
|
630
740
|
}
|
|
631
741
|
static remove(key) {
|
|
632
742
|
if (typeof window === "undefined") return;
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
743
|
+
try {
|
|
744
|
+
window.localStorage.removeItem(key);
|
|
745
|
+
} catch (error) {
|
|
746
|
+
console.warn(`[FrugaStorage] Failed to remove key "${key}":`, error);
|
|
747
|
+
}
|
|
638
748
|
}
|
|
639
749
|
};
|
|
640
750
|
|
|
641
751
|
// packages/core/src/infrastructure/config.ts
|
|
642
|
-
var DEFAULT_API_URL =
|
|
752
|
+
var DEFAULT_API_URL = "https://api.fruga.co.uk";
|
|
643
753
|
var getBaseUrl = (override) => {
|
|
644
754
|
if (override) return override;
|
|
645
|
-
return DEFAULT_API_URL;
|
|
755
|
+
return "" ? "" : DEFAULT_API_URL;
|
|
646
756
|
};
|
|
757
|
+
var isTestMode = () => false;
|
|
758
|
+
var SDK_VERSION = "1.3.0" ? "1.3.0" : "0.0.0-dev";
|
|
647
759
|
|
|
648
760
|
// packages/core/src/infrastructure/helpers/date.ts
|
|
649
761
|
var formatDate = (dateStr) => {
|
|
@@ -656,7 +768,7 @@ var formatDate = (dateStr) => {
|
|
|
656
768
|
const month = (date.getMonth() + 1).toString().padStart(2, "0");
|
|
657
769
|
const year = date.getFullYear();
|
|
658
770
|
return `${day}-${month}-${year}`;
|
|
659
|
-
} catch
|
|
771
|
+
} catch {
|
|
660
772
|
return dateStr;
|
|
661
773
|
}
|
|
662
774
|
};
|
|
@@ -702,23 +814,38 @@ export {
|
|
|
702
814
|
DEFAULT_API_URL,
|
|
703
815
|
DEFAULT_THEME,
|
|
704
816
|
DEFAULT_THEME as DefaultTheme,
|
|
817
|
+
HOST_MOBILE_BREAKPOINT,
|
|
705
818
|
HttpActivityService,
|
|
819
|
+
HttpError,
|
|
706
820
|
HttpOffersService,
|
|
707
821
|
HttpPayoutService,
|
|
708
822
|
HttpWalletService,
|
|
709
|
-
|
|
823
|
+
LEGACY_BOOTSTRAP_STORAGE_KEY,
|
|
824
|
+
MessageTransportContext,
|
|
825
|
+
MessageTransportProvider,
|
|
826
|
+
PROTOCOL_VERSION,
|
|
827
|
+
PostMessageTransport,
|
|
828
|
+
SDK_VERSION,
|
|
710
829
|
StorageService,
|
|
711
|
-
|
|
830
|
+
UnauthorizedError,
|
|
712
831
|
WidgetContainer,
|
|
713
|
-
WidgetHeader,
|
|
714
832
|
WidgetProvider,
|
|
833
|
+
createHostTransport,
|
|
715
834
|
formatDate,
|
|
716
835
|
getBaseUrl,
|
|
717
836
|
getDaysRemaining,
|
|
718
837
|
getResolvedTheme,
|
|
838
|
+
isFrugaMessage,
|
|
839
|
+
isFrugaMessageType,
|
|
840
|
+
isTestMode,
|
|
719
841
|
isWithinDays,
|
|
842
|
+
protocolMajor,
|
|
843
|
+
resolvePayoutMode,
|
|
844
|
+
storageKey,
|
|
720
845
|
truncateText,
|
|
721
846
|
useDebounce,
|
|
847
|
+
useFrugaMessage,
|
|
848
|
+
useMessageTransport,
|
|
722
849
|
useWidget,
|
|
723
850
|
useWidgetStore
|
|
724
851
|
};
|