@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/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/infrastructure/adapters/HttpWalletService.ts
55
- var HttpWalletService = class {
56
- constructor(baseUrl, token) {
57
- this.baseUrl = baseUrl;
58
- this.token = token;
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
- async getWallet() {
61
- try {
62
- const response = await fetch(`${this.baseUrl}/api/wallet`, {
63
- method: "GET",
64
- headers: {
65
- "Accept": "application/json",
66
- "Authorization": `Bearer ${this.token}`
67
- }
68
- });
69
- if (!response.ok) {
70
- console.warn(`[FrugaSDK] Failed to fetch wallet: ${response.status}. Returning zeroed wallet.`);
71
- return this.getEmptyWallet();
72
- }
73
- const data = await response.json();
74
- return {
75
- available: data.available ?? 0,
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
- getEmptyWallet() {
89
- return {
90
- available: 0,
91
- pending: 0,
92
- totalBalance: 0,
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
- try {
135
- const response = await this.fetchQuery("/api/activity", { pageNumber, pageSize });
136
- if (!response.ok) {
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
- actorType: "",
153
- partnerId: 0,
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
- try {
166
- const response = await this.fetchQuery("/api/offers/categories");
167
- if (!response.ok) {
168
- console.warn(`[FrugaSDK] Failed to fetch offer categories: ${response.status}. Returning empty list.`);
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
- try {
181
- const response = await this.fetchQuery("/api/offers", {
182
- pageNumber: query.pageNumber ?? 0,
183
- pageSize: query.pageSize ?? 10,
184
- type: query.type,
185
- query: query.query,
186
- featured: query.featured,
187
- categories: query.categories
188
- });
189
- if (!response.ok) {
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
- content: [],
206
- totalElements: 0,
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
- try {
224
- const response = await fetch(`${this.baseUrl}/api/payout-methods`, {
225
- method: "GET",
226
- headers: {
227
- "Accept": "application/json",
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
- const data = await response.json();
235
- return data.items || [];
236
- } catch (error) {
237
- console.error("[FrugaSDK] Error listing payout methods:", error);
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
- if (!response.ok) {
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
- if (!response.ok) {
265
- throw new Error(`Failed to delete payout method: ${response.status}`);
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/presentation/components/index.tsx
271
- import { jsx } from "react/jsx-runtime";
272
- var WidgetButton = ({
273
- children,
274
- variant = "primary",
275
- style,
276
- ...props
277
- }) => {
278
- const baseStyle = {
279
- padding: "10px 20px",
280
- borderRadius: "8px",
281
- border: "none",
282
- cursor: "pointer",
283
- fontWeight: 600,
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 useEffect2, useRef as useRef2 } from "react";
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 as jsx2 } from "react/jsx-runtime";
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__ */ jsx2(Sparkles, { size: 20 });
308
- if (theme.launcherIcon === "credit") return /* @__PURE__ */ jsx2(CreditCard, { size: 20 });
309
- if (theme.launcherIcon === "arrow") return /* @__PURE__ */ jsx2(MoveUpRight, { size: 20 });
310
- return /* @__PURE__ */ jsx2(Gift, { size: 20 });
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({ token }),
380
- setPartnerPayout: (payout) => set({ partnerPayout: payout }),
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 effectiveTheme = theme.allowUserThemeControl ? { ...theme, mode: storeMode } : { ...theme, mode: theme.mode || "light" };
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 = useRef2(null);
445
- const buttonRef = useRef2(null);
446
- useEffect2(() => {
447
- const handler = (event) => {
448
- if (event.data?.type === "FRUGA_HOST_RESIZE") {
449
- setHostHeight(event.data.hostHeight);
450
- setIsMobile(event.data.isHostMobile);
451
- }
452
- if (event.data?.type === "FRUGA_TOKEN_UPDATE") {
453
- const { setToken } = useWidgetStore.getState();
454
- setToken(event.data.token);
455
- }
456
- if (event.data?.type === "FRUGA_GET_BALANCE") {
457
- const { available, pending } = useWidgetStore.getState();
458
- console.log("FRUGA_GET_BALANCE", available, pending);
459
- window.parent.postMessage({
460
- type: "FRUGA_BALANCE_RESPONSE",
461
- requestId: event.data.requestId,
462
- available,
463
- pending
464
- }, "*");
465
- }
466
- };
467
- window.addEventListener("message", handler);
468
- return () => window.removeEventListener("message", handler);
469
- }, []);
470
- useEffect2(() => {
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
- window.parent.postMessage({
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 useEffect3, useState as useState2 } from "react";
705
+ import { useEffect as useEffect4, useState as useState2 } from "react";
594
706
  var useDebounce = (value, delay) => {
595
707
  const [debouncedValue, setDebouncedValue] = useState2(value);
596
- useEffect3(() => {
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 STORAGE_KEYS = {
609
- BOOTSTRAP_DATA: "fruga_bootstrap",
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
- window.localStorage.removeItem(key);
634
- }
635
- static clear() {
636
- if (typeof window === "undefined") return;
637
- window.localStorage.clear();
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 = typeof process !== "undefined" && process.env.VITE_FRUGA_API_URL || "https://api-dev.fruga.co.uk";
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 (e) {
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
- STORAGE_KEYS,
823
+ LEGACY_BOOTSTRAP_STORAGE_KEY,
824
+ MessageTransportContext,
825
+ MessageTransportProvider,
826
+ PROTOCOL_VERSION,
827
+ PostMessageTransport,
828
+ SDK_VERSION,
710
829
  StorageService,
711
- WidgetButton,
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
  };