capybara-game-sdk 0.0.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.js ADDED
@@ -0,0 +1,698 @@
1
+ 'use strict';
2
+
3
+ var react = require('react');
4
+
5
+ // src/AuthModule.ts
6
+ var AuthModule = class {
7
+ constructor(bridge) {
8
+ this.bridge = bridge;
9
+ this.currentUser = null;
10
+ this.authChangeListeners = /* @__PURE__ */ new Set();
11
+ this.initialize();
12
+ }
13
+ async initialize() {
14
+ this.currentUser = await this.bridge.getCurrentUser();
15
+ this.bridge.on("auth_changed", (user) => {
16
+ this.currentUser = user;
17
+ this.notifyAuthChange();
18
+ });
19
+ }
20
+ notifyAuthChange() {
21
+ this.authChangeListeners.forEach((listener) => {
22
+ try {
23
+ listener(this.currentUser);
24
+ } catch (error) {
25
+ console.error("Error in auth change listener:", error);
26
+ }
27
+ });
28
+ }
29
+ /**
30
+ * Get the currently authenticated user
31
+ */
32
+ async getCurrentUser() {
33
+ if (!this.currentUser) {
34
+ this.currentUser = await this.bridge.getCurrentUser();
35
+ }
36
+ return this.currentUser;
37
+ }
38
+ /**
39
+ * Get bearer token for the authenticated user
40
+ */
41
+ async getBearerToken() {
42
+ return await this.bridge.getBearerToken();
43
+ }
44
+ /**
45
+ * Request user authentication (triggers parent app UI)
46
+ */
47
+ async requestLogin() {
48
+ await this.bridge.requestLogin();
49
+ await new Promise((resolve) => setTimeout(resolve, 500));
50
+ this.currentUser = await this.bridge.getCurrentUser();
51
+ this.notifyAuthChange();
52
+ }
53
+ /**
54
+ * Logout the current user
55
+ */
56
+ async logout() {
57
+ await this.bridge.logout();
58
+ this.currentUser = null;
59
+ this.notifyAuthChange();
60
+ }
61
+ /**
62
+ * Listen for authentication state changes
63
+ */
64
+ onAuthChange(callback) {
65
+ this.authChangeListeners.add(callback);
66
+ callback(this.currentUser);
67
+ return () => {
68
+ this.authChangeListeners.delete(callback);
69
+ };
70
+ }
71
+ /**
72
+ * Check if user is authenticated
73
+ */
74
+ isAuthenticated() {
75
+ return this.currentUser !== null;
76
+ }
77
+ };
78
+
79
+ // src/ParentBridge.ts
80
+ var ParentBridge = class {
81
+ constructor(allowedOrigins = ["*"]) {
82
+ this.messageHandlers = /* @__PURE__ */ new Map();
83
+ this.pendingRequests = /* @__PURE__ */ new Map();
84
+ this.requestIdCounter = 0;
85
+ this.allowedOrigins = new Set(allowedOrigins);
86
+ this.setupMessageListener();
87
+ }
88
+ setupMessageListener() {
89
+ window.addEventListener("message", (event) => {
90
+ if (this.allowedOrigins.size > 0 && !this.allowedOrigins.has("*") && !this.allowedOrigins.has(event.origin)) {
91
+ console.warn(
92
+ "Rejected message from unauthorized origin:",
93
+ event.origin
94
+ );
95
+ return;
96
+ }
97
+ const message = event.data;
98
+ if (!message || typeof message !== "object" || !message.type) {
99
+ return;
100
+ }
101
+ const requestId = message.requestId;
102
+ if (requestId && this.pendingRequests.has(requestId)) {
103
+ const { resolve, reject } = this.pendingRequests.get(requestId);
104
+ this.pendingRequests.delete(requestId);
105
+ if (message.error) {
106
+ reject(new Error(message.error));
107
+ } else {
108
+ resolve(message.data);
109
+ }
110
+ return;
111
+ }
112
+ const handler = this.messageHandlers.get(message.type);
113
+ if (handler) {
114
+ handler(message.data);
115
+ }
116
+ });
117
+ }
118
+ /**
119
+ * Send a message to parent and wait for response
120
+ */
121
+ sendRequest(type, data) {
122
+ return new Promise((resolve, reject) => {
123
+ const requestId = `req_${this.requestIdCounter++}_${Date.now()}`;
124
+ this.pendingRequests.set(requestId, { resolve, reject });
125
+ const message = {
126
+ type,
127
+ data,
128
+ requestId
129
+ };
130
+ window.parent.postMessage(message, "*");
131
+ setTimeout(() => {
132
+ if (this.pendingRequests.has(requestId)) {
133
+ this.pendingRequests.delete(requestId);
134
+ reject(new Error("Request timeout"));
135
+ }
136
+ }, 3e4);
137
+ });
138
+ }
139
+ /**
140
+ * Send a message to parent without expecting a response
141
+ */
142
+ sendMessage(type, data) {
143
+ const message = {
144
+ type,
145
+ data
146
+ };
147
+ window.parent.postMessage(message, "*");
148
+ }
149
+ /**
150
+ * Listen for messages from parent
151
+ */
152
+ on(type, handler) {
153
+ this.messageHandlers.set(type, handler);
154
+ }
155
+ /**
156
+ * Remove message listener
157
+ */
158
+ off(type) {
159
+ this.messageHandlers.delete(type);
160
+ }
161
+ /**
162
+ * Get current authenticated user
163
+ */
164
+ async getCurrentUser() {
165
+ try {
166
+ return await this.sendRequest("get_user");
167
+ } catch (error) {
168
+ console.error("Failed to get current user:", error);
169
+ return null;
170
+ }
171
+ }
172
+ /**
173
+ * Request user to login (triggers parent app UI)
174
+ */
175
+ async requestLogin() {
176
+ return this.sendRequest("request_login");
177
+ }
178
+ /**
179
+ * Logout current user
180
+ */
181
+ async logout() {
182
+ return this.sendRequest("logout");
183
+ }
184
+ /**
185
+ * Get bearer token for authenticated user
186
+ */
187
+ async getBearerToken() {
188
+ try {
189
+ return await this.sendRequest("get_bearer_token");
190
+ } catch (error) {
191
+ console.error("Failed to get bearer token:", error);
192
+ return null;
193
+ }
194
+ }
195
+ /**
196
+ * Get user's credit balance
197
+ */
198
+ async getBalance() {
199
+ return this.sendRequest("get_balance");
200
+ }
201
+ /**
202
+ * Charge credits from user's balance
203
+ */
204
+ async chargeCredits(amount, description) {
205
+ return this.sendRequest("charge_credits", {
206
+ amount,
207
+ description
208
+ });
209
+ }
210
+ /**
211
+ * Notify parent that SDK is ready
212
+ */
213
+ notifyReady() {
214
+ this.sendMessage("ready");
215
+ }
216
+ };
217
+
218
+ // src/PaymentsModule.ts
219
+ var PaymentsModule = class {
220
+ constructor(bridge, auth, devMode) {
221
+ this.bridge = bridge;
222
+ this.auth = auth;
223
+ this.devMode = devMode;
224
+ this.currentBalance = { credits: 0 };
225
+ this.balanceChangeListeners = /* @__PURE__ */ new Set();
226
+ this.transactions = [];
227
+ this.initialize();
228
+ }
229
+ async initialize() {
230
+ if (this.auth.isAuthenticated()) {
231
+ await this.refreshBalance();
232
+ }
233
+ this.auth.onAuthChange(async (user) => {
234
+ if (user) {
235
+ await this.refreshBalance();
236
+ } else {
237
+ this.currentBalance = { credits: 0 };
238
+ this.notifyBalanceChange();
239
+ }
240
+ });
241
+ this.bridge.on("balance_updated", (balance) => {
242
+ this.currentBalance = balance;
243
+ this.notifyBalanceChange();
244
+ });
245
+ }
246
+ async refreshBalance() {
247
+ try {
248
+ this.currentBalance = await this.bridge.getBalance();
249
+ this.notifyBalanceChange();
250
+ } catch (error) {
251
+ console.error("Failed to refresh balance:", error);
252
+ }
253
+ }
254
+ notifyBalanceChange() {
255
+ this.balanceChangeListeners.forEach((listener) => {
256
+ try {
257
+ listener(this.currentBalance);
258
+ } catch (error) {
259
+ console.error("Error in balance change listener:", error);
260
+ }
261
+ });
262
+ }
263
+ /**
264
+ * Get the current credit balance
265
+ */
266
+ async getBalance() {
267
+ if (!this.auth.isAuthenticated()) {
268
+ throw new Error("User must be authenticated to check balance");
269
+ }
270
+ if (this.devMode) {
271
+ return { credits: 999999 };
272
+ }
273
+ await this.refreshBalance();
274
+ return this.currentBalance;
275
+ }
276
+ /**
277
+ * Charge credits from the user's balance
278
+ * In dev mode, this is a no-op (free)
279
+ */
280
+ async charge(amount, description) {
281
+ if (!this.auth.isAuthenticated()) {
282
+ throw new Error("User must be authenticated to make payments");
283
+ }
284
+ if (amount <= 0) {
285
+ throw new Error("Amount must be greater than 0");
286
+ }
287
+ if (this.devMode) {
288
+ console.warn(
289
+ `Dev mode: Charging ${amount} credits for "${description}" - no actual charge applied.`
290
+ );
291
+ return {
292
+ success: true,
293
+ newBalance: 999999
294
+ };
295
+ }
296
+ try {
297
+ const result = await this.bridge.chargeCredits(amount, description);
298
+ if (result.success) {
299
+ this.currentBalance = { credits: result.newBalance };
300
+ this.notifyBalanceChange();
301
+ this.transactions.push({
302
+ amount: -amount,
303
+ description,
304
+ timestamp: Date.now()
305
+ });
306
+ }
307
+ return result;
308
+ } catch (error) {
309
+ return {
310
+ success: false,
311
+ newBalance: this.currentBalance.credits,
312
+ error: error instanceof Error ? error.message : "Failed to charge credits"
313
+ };
314
+ }
315
+ }
316
+ /**
317
+ * Listen for balance changes
318
+ */
319
+ onBalanceChange(callback) {
320
+ this.balanceChangeListeners.add(callback);
321
+ callback(this.currentBalance);
322
+ return () => {
323
+ this.balanceChangeListeners.delete(callback);
324
+ };
325
+ }
326
+ /**
327
+ * Get transaction history
328
+ * Note: This only includes transactions made during the current session
329
+ * For full history, the parent app should provide an API
330
+ */
331
+ async getHistory() {
332
+ return [...this.transactions];
333
+ }
334
+ /**
335
+ * Gift credits to another user (requires server-side implementation)
336
+ */
337
+ async giftCredits(userId, amount, description) {
338
+ throw new Error("Gift credits not yet implemented");
339
+ }
340
+ /**
341
+ * Set development mode
342
+ */
343
+ setDevMode(enabled) {
344
+ this.devMode = enabled;
345
+ }
346
+ };
347
+
348
+ // src/GameSDK.ts
349
+ var _GameSDK = class _GameSDK {
350
+ constructor(config) {
351
+ this.config = config;
352
+ this.gameId = config.gameId;
353
+ this.bridge = new ParentBridge();
354
+ this.auth = new AuthModule(this.bridge);
355
+ this.payments = new PaymentsModule(
356
+ this.bridge,
357
+ this.auth,
358
+ config.devMode || false
359
+ );
360
+ this.bridge.notifyReady();
361
+ if (config.onReady) {
362
+ setTimeout(config.onReady, 0);
363
+ }
364
+ }
365
+ /**
366
+ * Initialize the Game SDK
367
+ */
368
+ static async init(config) {
369
+ if (_GameSDK.instance) {
370
+ console.warn("GameSDK already initialized. Returning existing instance.");
371
+ return _GameSDK.instance;
372
+ }
373
+ if (!config.gameId) {
374
+ throw new Error("gameId is required");
375
+ }
376
+ _GameSDK.instance = new _GameSDK(config);
377
+ return _GameSDK.instance;
378
+ }
379
+ /**
380
+ * Get the current SDK instance
381
+ */
382
+ static getInstance() {
383
+ if (!_GameSDK.instance) {
384
+ throw new Error("GameSDK not initialized. Call GameSDK.init() first.");
385
+ }
386
+ return _GameSDK.instance;
387
+ }
388
+ /**
389
+ * Set development mode
390
+ */
391
+ setDevMode(enabled) {
392
+ this.config.devMode = enabled;
393
+ this.payments.setDevMode(enabled);
394
+ }
395
+ /**
396
+ * Get SDK configuration
397
+ */
398
+ getConfig() {
399
+ return { ...this.config };
400
+ }
401
+ /**
402
+ * Clean up SDK resources
403
+ */
404
+ async destroy() {
405
+ _GameSDK.instance = null;
406
+ }
407
+ };
408
+ _GameSDK.instance = null;
409
+ var GameSDK = _GameSDK;
410
+
411
+ // src/ApiClient.ts
412
+ var API_BASE_URL = "https://api.gamesdk.com";
413
+ var ApiClient = class {
414
+ constructor(auth) {
415
+ this.auth = auth;
416
+ }
417
+ async getHeaders() {
418
+ const token = await this.auth.getBearerToken();
419
+ return {
420
+ "Content-Type": "application/json",
421
+ ...token ? { Authorization: `Bearer ${token}` } : {}
422
+ };
423
+ }
424
+ async makeRequest(endpoint, options = {}) {
425
+ const url = `${API_BASE_URL}${endpoint}`;
426
+ const headers = await this.getHeaders();
427
+ const response = await fetch(url, {
428
+ ...options,
429
+ headers: {
430
+ ...headers,
431
+ ...options.headers
432
+ }
433
+ });
434
+ if (!response.ok) {
435
+ throw new Error(
436
+ `API request failed: ${response.status} ${response.statusText}`
437
+ );
438
+ }
439
+ return response.json();
440
+ }
441
+ /**
442
+ * Save game data to the server
443
+ */
444
+ async saveGameData(gameId, data) {
445
+ await this.makeRequest("/api/storage/save", {
446
+ method: "POST",
447
+ body: JSON.stringify({ gameId, data })
448
+ });
449
+ }
450
+ /**
451
+ * Load game data from the server
452
+ */
453
+ async loadGameData(gameId) {
454
+ try {
455
+ const result = await this.makeRequest(
456
+ "/api/storage/load",
457
+ {
458
+ method: "GET"
459
+ }
460
+ );
461
+ return result.data;
462
+ } catch (error) {
463
+ if (error instanceof Error && error.message.includes("404")) {
464
+ return null;
465
+ }
466
+ throw error;
467
+ }
468
+ }
469
+ /**
470
+ * Clear game data from the server
471
+ */
472
+ async clearGameData(gameId) {
473
+ await this.makeRequest("/api/storage/clear", {
474
+ method: "DELETE"
475
+ });
476
+ }
477
+ /**
478
+ * Check if game data exists on the server
479
+ */
480
+ async gameDataExists(gameId) {
481
+ try {
482
+ await this.makeRequest("/api/storage/exists", {
483
+ method: "GET"
484
+ });
485
+ return true;
486
+ } catch (error) {
487
+ if (error instanceof Error && error.message.includes("404")) {
488
+ return false;
489
+ }
490
+ throw error;
491
+ }
492
+ }
493
+ /**
494
+ * Get the size of stored game data
495
+ */
496
+ async getGameDataSize(gameId) {
497
+ const result = await this.makeRequest(
498
+ "/api/storage/size",
499
+ {
500
+ method: "GET"
501
+ }
502
+ );
503
+ return result.size;
504
+ }
505
+ };
506
+
507
+ // src/StorageModule.ts
508
+ var StorageModule = class {
509
+ constructor(gameId, auth) {
510
+ this.auth = auth;
511
+ this.gameId = gameId;
512
+ this.apiClient = new ApiClient(auth);
513
+ }
514
+ /**
515
+ * Save data to storage
516
+ * Data is automatically scoped to current user and game
517
+ * Maximum size: 5MB
518
+ */
519
+ async save(data) {
520
+ try {
521
+ const serialized = JSON.stringify(data);
522
+ const sizeInBytes = new Blob([serialized]).size;
523
+ if (sizeInBytes > 5 * 1024 * 1024) {
524
+ throw new Error("Storage limit exceeded: Maximum 5MB allowed");
525
+ }
526
+ await this.apiClient.saveGameData(this.gameId, data);
527
+ } catch (error) {
528
+ if (error instanceof Error) {
529
+ throw error;
530
+ }
531
+ throw new Error("Failed to save data to storage");
532
+ }
533
+ }
534
+ /**
535
+ * Load data from storage
536
+ * Returns null if no data exists
537
+ */
538
+ async load() {
539
+ try {
540
+ return await this.apiClient.loadGameData(this.gameId);
541
+ } catch (error) {
542
+ console.error("Failed to load data from storage:", error);
543
+ return null;
544
+ }
545
+ }
546
+ /**
547
+ * Clear all stored data for current user and game
548
+ */
549
+ async clear() {
550
+ try {
551
+ await this.apiClient.clearGameData(this.gameId);
552
+ } catch (error) {
553
+ console.error("Failed to clear storage:", error);
554
+ }
555
+ }
556
+ /**
557
+ * Check if data exists in storage
558
+ */
559
+ async exists() {
560
+ return this.apiClient.gameDataExists(this.gameId);
561
+ }
562
+ /**
563
+ * Get the size of stored data in bytes
564
+ */
565
+ async getSize() {
566
+ try {
567
+ return await this.apiClient.getGameDataSize(this.gameId);
568
+ } catch (error) {
569
+ return 0;
570
+ }
571
+ }
572
+ };
573
+ function useGameSDK(config) {
574
+ const [sdk, setSDK] = react.useState(null);
575
+ const [isReady, setIsReady] = react.useState(false);
576
+ const [error, setError] = react.useState(null);
577
+ react.useEffect(() => {
578
+ let mounted = true;
579
+ const initSDK = async () => {
580
+ try {
581
+ const configWithReady = {
582
+ ...config,
583
+ onReady: () => {
584
+ if (mounted) {
585
+ setIsReady(true);
586
+ config.onReady?.();
587
+ }
588
+ }
589
+ };
590
+ const instance = await GameSDK.init(configWithReady);
591
+ if (mounted) {
592
+ setSDK(instance);
593
+ }
594
+ } catch (err) {
595
+ if (mounted) {
596
+ setError(
597
+ err instanceof Error ? err : new Error("Failed to initialize SDK")
598
+ );
599
+ }
600
+ }
601
+ };
602
+ initSDK();
603
+ return () => {
604
+ mounted = false;
605
+ };
606
+ }, [config.gameId]);
607
+ return { sdk, isReady, error };
608
+ }
609
+ function useAuth() {
610
+ const sdk = GameSDK.getInstance();
611
+ const [user, setUser] = react.useState(null);
612
+ const [isLoading, setIsLoading] = react.useState(true);
613
+ react.useEffect(() => {
614
+ let mounted = true;
615
+ const loadUser = async () => {
616
+ const currentUser = await sdk.auth.getCurrentUser();
617
+ if (mounted) {
618
+ setUser(currentUser);
619
+ setIsLoading(false);
620
+ }
621
+ };
622
+ loadUser();
623
+ const unsubscribe = sdk.auth.onAuthChange((newUser) => {
624
+ if (mounted) {
625
+ setUser(newUser);
626
+ }
627
+ });
628
+ return () => {
629
+ mounted = false;
630
+ unsubscribe();
631
+ };
632
+ }, [sdk]);
633
+ const login = react.useCallback(async () => {
634
+ await sdk.auth.requestLogin();
635
+ }, [sdk]);
636
+ const logout = react.useCallback(async () => {
637
+ await sdk.auth.logout();
638
+ }, [sdk]);
639
+ return {
640
+ user,
641
+ isLoading,
642
+ isAuthenticated: !!user,
643
+ login,
644
+ logout
645
+ };
646
+ }
647
+ function useCredits() {
648
+ const sdk = GameSDK.getInstance();
649
+ const [balance, setBalance] = react.useState({ credits: 0 });
650
+ const [isLoading, setIsLoading] = react.useState(true);
651
+ react.useEffect(() => {
652
+ let mounted = true;
653
+ const loadBalance = async () => {
654
+ try {
655
+ const currentBalance = await sdk.payments.getBalance();
656
+ if (mounted) {
657
+ setBalance(currentBalance);
658
+ setIsLoading(false);
659
+ }
660
+ } catch (error) {
661
+ if (mounted) {
662
+ setIsLoading(false);
663
+ }
664
+ }
665
+ };
666
+ loadBalance();
667
+ const unsubscribe = sdk.payments.onBalanceChange((newBalance) => {
668
+ if (mounted) {
669
+ setBalance(newBalance);
670
+ }
671
+ });
672
+ return () => {
673
+ mounted = false;
674
+ unsubscribe();
675
+ };
676
+ }, [sdk]);
677
+ const charge = react.useCallback(
678
+ async (amount, description) => {
679
+ return await sdk.payments.charge(amount, description);
680
+ },
681
+ [sdk]
682
+ );
683
+ return {
684
+ balance: balance.credits,
685
+ isLoading,
686
+ charge
687
+ };
688
+ }
689
+
690
+ exports.AuthModule = AuthModule;
691
+ exports.GameSDK = GameSDK;
692
+ exports.PaymentsModule = PaymentsModule;
693
+ exports.StorageModule = StorageModule;
694
+ exports.useAuth = useAuth;
695
+ exports.useCredits = useCredits;
696
+ exports.useGameSDK = useGameSDK;
697
+ //# sourceMappingURL=index.js.map
698
+ //# sourceMappingURL=index.js.map