playe-developer-sdk 1.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.
@@ -0,0 +1,759 @@
1
+ 'use strict';
2
+
3
+ class HttpClient {
4
+ config;
5
+ baseHeaders;
6
+ constructor(config) {
7
+ this.config = config;
8
+ this.baseHeaders = {
9
+ 'Content-Type': 'application/json',
10
+ 'User-Agent': 'Playe-Developer-SDK/1.0.0',
11
+ };
12
+ if (config.apiKey) {
13
+ this.baseHeaders['Authorization'] = `Bearer ${config.apiKey}`;
14
+ }
15
+ }
16
+ async request(endpoint, options = {}) {
17
+ const url = `${this.config.apiBaseUrl}${endpoint}`;
18
+ const timeout = options.timeout || this.config.timeoutMs || 30000;
19
+ const retries = options.retries || this.config.retryAttempts || 3;
20
+ const requestOptions = {
21
+ method: options.method || 'GET',
22
+ headers: {
23
+ ...this.baseHeaders,
24
+ ...options.headers,
25
+ },
26
+ signal: AbortSignal.timeout(timeout),
27
+ };
28
+ if (options.body && options.method !== 'GET') {
29
+ requestOptions.body = JSON.stringify(options.body);
30
+ }
31
+ let lastError;
32
+ for (let attempt = 0; attempt <= retries; attempt++) {
33
+ try {
34
+ if (this.config.enableDebugMode) {
35
+ console.log(`[Playe SDK] ${options.method || 'GET'} ${url}`, {
36
+ attempt: attempt + 1,
37
+ body: options.body,
38
+ });
39
+ }
40
+ const response = await fetch(url, requestOptions);
41
+ if (!response.ok) {
42
+ const errorText = await response.text();
43
+ throw new Error(`HTTP ${response.status}: ${errorText}`);
44
+ }
45
+ const data = await response.json();
46
+ if (this.config.enableDebugMode) {
47
+ console.log(`[Playe SDK] Response:`, data);
48
+ }
49
+ return data;
50
+ }
51
+ catch (error) {
52
+ lastError = error;
53
+ if (this.config.enableDebugMode) {
54
+ console.warn(`[Playe SDK] Request failed (attempt ${attempt + 1}/${retries + 1}):`, error);
55
+ }
56
+ // Don't retry on client errors (4xx)
57
+ if (error instanceof Error && error.message.includes('HTTP 4')) {
58
+ break;
59
+ }
60
+ // Wait before retry (exponential backoff)
61
+ if (attempt < retries) {
62
+ await new Promise(resolve => setTimeout(resolve, Math.pow(2, attempt) * 1000));
63
+ }
64
+ }
65
+ }
66
+ throw lastError;
67
+ }
68
+ async get(endpoint, options) {
69
+ return this.request(endpoint, { ...options, method: 'GET' });
70
+ }
71
+ async post(endpoint, body, options) {
72
+ return this.request(endpoint, { ...options, method: 'POST', body });
73
+ }
74
+ async put(endpoint, body, options) {
75
+ return this.request(endpoint, { ...options, method: 'PUT', body });
76
+ }
77
+ async delete(endpoint, options) {
78
+ return this.request(endpoint, { ...options, method: 'DELETE' });
79
+ }
80
+ async patch(endpoint, body, options) {
81
+ return this.request(endpoint, { ...options, method: 'PATCH', body });
82
+ }
83
+ }
84
+
85
+ class PlayeSDKError extends Error {
86
+ originalError;
87
+ context;
88
+ timestamp;
89
+ constructor(message, originalError, context) {
90
+ super(message);
91
+ this.name = 'PlayeSDKError';
92
+ this.originalError = originalError;
93
+ this.context = context;
94
+ this.timestamp = new Date().toISOString();
95
+ // Maintains proper stack trace for where our error was thrown (only available on V8)
96
+ if (Error.captureStackTrace) {
97
+ Error.captureStackTrace(this, PlayeSDKError);
98
+ }
99
+ }
100
+ static fromError(error, context) {
101
+ return new PlayeSDKError(error.message, error, context);
102
+ }
103
+ toJSON() {
104
+ return {
105
+ name: this.name,
106
+ message: this.message,
107
+ context: this.context,
108
+ timestamp: this.timestamp,
109
+ stack: this.stack,
110
+ originalError: this.originalError ? {
111
+ name: this.originalError.name,
112
+ message: this.originalError.message,
113
+ stack: this.originalError.stack,
114
+ } : undefined,
115
+ };
116
+ }
117
+ }
118
+ class ValidationError extends PlayeSDKError {
119
+ constructor(message, context) {
120
+ super(message, undefined, context);
121
+ this.name = 'ValidationError';
122
+ }
123
+ }
124
+ class NetworkError extends PlayeSDKError {
125
+ constructor(message, originalError, context) {
126
+ super(message, originalError, context);
127
+ this.name = 'NetworkError';
128
+ }
129
+ }
130
+ class AuthenticationError extends PlayeSDKError {
131
+ constructor(message, context) {
132
+ super(message, undefined, context);
133
+ this.name = 'AuthenticationError';
134
+ }
135
+ }
136
+ class GameSessionError extends PlayeSDKError {
137
+ constructor(message, context) {
138
+ super(message, undefined, context);
139
+ this.name = 'GameSessionError';
140
+ }
141
+ }
142
+
143
+ class DeviceUtils {
144
+ /**
145
+ * Automatically detect device information from the browser
146
+ */
147
+ static getDeviceInfo() {
148
+ const deviceInfo = {};
149
+ if (typeof navigator !== 'undefined') {
150
+ deviceInfo.userAgent = navigator.userAgent;
151
+ deviceInfo.platform = navigator.platform;
152
+ deviceInfo.language = navigator.language;
153
+ deviceInfo.touchSupported = 'ontouchstart' in window || navigator.maxTouchPoints > 0;
154
+ }
155
+ if (typeof screen !== 'undefined') {
156
+ deviceInfo.screenResolution = `${screen.width}x${screen.height}`;
157
+ }
158
+ if (typeof Intl !== 'undefined') {
159
+ try {
160
+ deviceInfo.timezone = Intl.DateTimeFormat().resolvedOptions().timeZone;
161
+ }
162
+ catch (error) {
163
+ // Fallback for older browsers
164
+ deviceInfo.timezone = 'Unknown';
165
+ }
166
+ }
167
+ return deviceInfo;
168
+ }
169
+ /**
170
+ * Get current geolocation data
171
+ */
172
+ static async getGeolocationData() {
173
+ return new Promise((resolve, reject) => {
174
+ if (!navigator.geolocation) {
175
+ reject(new Error('Geolocation is not supported by this browser'));
176
+ return;
177
+ }
178
+ navigator.geolocation.getCurrentPosition((position) => {
179
+ resolve({
180
+ latitude: position.coords.latitude,
181
+ longitude: position.coords.longitude,
182
+ accuracy: position.coords.accuracy,
183
+ timestamp: new Date().toISOString(),
184
+ });
185
+ }, (error) => {
186
+ reject(new Error(`Geolocation error: ${error.message}`));
187
+ }, {
188
+ enableHighAccuracy: true,
189
+ timeout: 10000,
190
+ maximumAge: 300000, // 5 minutes
191
+ });
192
+ });
193
+ }
194
+ /**
195
+ * Generate a simple browser fingerprint
196
+ */
197
+ static generateFingerprint() {
198
+ const components = [];
199
+ if (typeof navigator !== 'undefined') {
200
+ components.push(navigator.userAgent);
201
+ components.push(navigator.language);
202
+ components.push(navigator.platform);
203
+ components.push(navigator.cookieEnabled.toString());
204
+ }
205
+ if (typeof screen !== 'undefined') {
206
+ components.push(`${screen.width}x${screen.height}`);
207
+ components.push(screen.colorDepth.toString());
208
+ }
209
+ if (typeof window !== 'undefined') {
210
+ components.push(window.devicePixelRatio?.toString() || '1');
211
+ }
212
+ // Add timezone
213
+ try {
214
+ components.push(Intl.DateTimeFormat().resolvedOptions().timeZone);
215
+ }
216
+ catch (error) {
217
+ components.push('Unknown');
218
+ }
219
+ // Simple hash function
220
+ const str = components.join('|');
221
+ let hash = 0;
222
+ for (let i = 0; i < str.length; i++) {
223
+ const char = str.charCodeAt(i);
224
+ hash = ((hash << 5) - hash) + char;
225
+ hash = hash & hash; // Convert to 32-bit integer
226
+ }
227
+ return Math.abs(hash).toString(36);
228
+ }
229
+ /**
230
+ * Check if the current environment is a browser
231
+ */
232
+ static isBrowser() {
233
+ return typeof window !== 'undefined' && typeof document !== 'undefined';
234
+ }
235
+ /**
236
+ * Check if the device supports specific features
237
+ */
238
+ static getFeatureSupport() {
239
+ return {
240
+ geolocation: 'geolocation' in navigator,
241
+ camera: 'mediaDevices' in navigator && 'getUserMedia' in navigator.mediaDevices,
242
+ localStorage: typeof Storage !== 'undefined',
243
+ webGL: (() => {
244
+ try {
245
+ const canvas = document.createElement('canvas');
246
+ return !!(canvas.getContext('webgl') || canvas.getContext('experimental-webgl'));
247
+ }
248
+ catch (e) {
249
+ return false;
250
+ }
251
+ })(),
252
+ webWorkers: typeof Worker !== 'undefined',
253
+ notifications: 'Notification' in window,
254
+ serviceWorker: 'serviceWorker' in navigator,
255
+ };
256
+ }
257
+ }
258
+
259
+ class ValidationUtils {
260
+ /**
261
+ * Validate initialization parameters
262
+ */
263
+ static validateInitializeParams(params) {
264
+ if (!params.campaignId || typeof params.campaignId !== 'string') {
265
+ throw new ValidationError('campaignId is required and must be a string');
266
+ }
267
+ if (!params.gameId || typeof params.gameId !== 'string') {
268
+ throw new ValidationError('gameId is required and must be a string');
269
+ }
270
+ if (!params.playerFingerprint || typeof params.playerFingerprint !== 'string') {
271
+ throw new ValidationError('playerFingerprint is required and must be a string');
272
+ }
273
+ if (params.playerEmail && !this.isValidEmail(params.playerEmail)) {
274
+ throw new ValidationError('playerEmail must be a valid email address');
275
+ }
276
+ if (params.receiptImage && !this.isValidBase64(params.receiptImage)) {
277
+ throw new ValidationError('receiptImage must be a valid base64 string');
278
+ }
279
+ if (params.geolocationData) {
280
+ this.validateGeolocationData(params.geolocationData);
281
+ }
282
+ }
283
+ /**
284
+ * Validate progress update parameters
285
+ */
286
+ static validateProgressParams(params) {
287
+ if (typeof params.currentScore !== 'number' || params.currentScore < 0) {
288
+ throw new ValidationError('currentScore must be a non-negative number');
289
+ }
290
+ if (typeof params.elapsedTime !== 'number' || params.elapsedTime < 0) {
291
+ throw new ValidationError('elapsedTime must be a non-negative number');
292
+ }
293
+ if (params.clientTimestamp && !this.isValidISODate(params.clientTimestamp)) {
294
+ throw new ValidationError('clientTimestamp must be a valid ISO date string');
295
+ }
296
+ if (params.achievements) {
297
+ params.achievements.forEach((achievement, index) => {
298
+ if (!achievement.id || typeof achievement.id !== 'string') {
299
+ throw new ValidationError(`Achievement at index ${index} must have a valid id`);
300
+ }
301
+ if (!achievement.name || typeof achievement.name !== 'string') {
302
+ throw new ValidationError(`Achievement at index ${index} must have a valid name`);
303
+ }
304
+ if (!achievement.unlockedAt || !this.isValidISODate(achievement.unlockedAt)) {
305
+ throw new ValidationError(`Achievement at index ${index} must have a valid unlockedAt date`);
306
+ }
307
+ });
308
+ }
309
+ }
310
+ /**
311
+ * Validate game completion parameters
312
+ */
313
+ static validateCompleteParams(params) {
314
+ if (typeof params.finalScore !== 'number' || params.finalScore < 0) {
315
+ throw new ValidationError('finalScore must be a non-negative number');
316
+ }
317
+ if (params.gameMetrics) {
318
+ const metrics = params.gameMetrics;
319
+ if (typeof metrics.totalPlayTime !== 'number' || metrics.totalPlayTime < 0) {
320
+ throw new ValidationError('gameMetrics.totalPlayTime must be a non-negative number');
321
+ }
322
+ if (typeof metrics.actionsPerformed !== 'number' || metrics.actionsPerformed < 0) {
323
+ throw new ValidationError('gameMetrics.actionsPerformed must be a non-negative number');
324
+ }
325
+ if (typeof metrics.powerUpsUsed !== 'number' || metrics.powerUpsUsed < 0) {
326
+ throw new ValidationError('gameMetrics.powerUpsUsed must be a non-negative number');
327
+ }
328
+ if (typeof metrics.levelsCompleted !== 'number' || metrics.levelsCompleted < 0) {
329
+ throw new ValidationError('gameMetrics.levelsCompleted must be a non-negative number');
330
+ }
331
+ }
332
+ }
333
+ /**
334
+ * Validate session ID format
335
+ */
336
+ static validateSessionId(sessionId) {
337
+ if (!sessionId || typeof sessionId !== 'string') {
338
+ throw new ValidationError('sessionId is required and must be a string');
339
+ }
340
+ if (sessionId.length < 10) {
341
+ throw new ValidationError('sessionId appears to be invalid (too short)');
342
+ }
343
+ }
344
+ /**
345
+ * Validate email format
346
+ */
347
+ static isValidEmail(email) {
348
+ const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
349
+ return emailRegex.test(email);
350
+ }
351
+ /**
352
+ * Validate base64 format
353
+ */
354
+ static isValidBase64(str) {
355
+ try {
356
+ return btoa(atob(str)) === str;
357
+ }
358
+ catch (err) {
359
+ return false;
360
+ }
361
+ }
362
+ /**
363
+ * Validate ISO date string
364
+ */
365
+ static isValidISODate(dateString) {
366
+ const date = new Date(dateString);
367
+ return date instanceof Date && !isNaN(date.getTime()) && date.toISOString() === dateString;
368
+ }
369
+ /**
370
+ * Validate geolocation data
371
+ */
372
+ static validateGeolocationData(data) {
373
+ if (typeof data.latitude !== 'number' || data.latitude < -90 || data.latitude > 90) {
374
+ throw new ValidationError('geolocationData.latitude must be a number between -90 and 90');
375
+ }
376
+ if (typeof data.longitude !== 'number' || data.longitude < -180 || data.longitude > 180) {
377
+ throw new ValidationError('geolocationData.longitude must be a number between -180 and 180');
378
+ }
379
+ if (typeof data.accuracy !== 'number' || data.accuracy < 0) {
380
+ throw new ValidationError('geolocationData.accuracy must be a non-negative number');
381
+ }
382
+ if (!data.timestamp || !this.isValidISODate(data.timestamp)) {
383
+ throw new ValidationError('geolocationData.timestamp must be a valid ISO date string');
384
+ }
385
+ }
386
+ /**
387
+ * Sanitize user input to prevent XSS
388
+ */
389
+ static sanitizeString(input) {
390
+ return input
391
+ .replace(/[<>]/g, '')
392
+ .replace(/javascript:/gi, '')
393
+ .replace(/on\w+=/gi, '')
394
+ .trim();
395
+ }
396
+ /**
397
+ * Validate score against potential cheating patterns
398
+ */
399
+ static validateScore(score, previousScore = 0, timeElapsed = 0) {
400
+ // Basic sanity checks
401
+ if (score < 0 || !Number.isFinite(score)) {
402
+ return false;
403
+ }
404
+ // Check for impossible score increases
405
+ const scoreDiff = score - previousScore;
406
+ if (timeElapsed > 0 && scoreDiff > 0) {
407
+ const scoreRate = scoreDiff / timeElapsed; // points per second
408
+ // This is a basic example - you'd want more sophisticated checks
409
+ if (scoreRate > 1000) { // More than 1000 points per second seems suspicious
410
+ return false;
411
+ }
412
+ }
413
+ return true;
414
+ }
415
+ }
416
+
417
+ class PlayeDeveloperSDK {
418
+ httpClient;
419
+ config;
420
+ currentSession = null;
421
+ heartbeatInterval = null;
422
+ _isDemo = false;
423
+ constructor(config) {
424
+ this.validateConfig(config);
425
+ this.config = { ...this.getDefaultConfig(), ...config };
426
+ this.httpClient = new HttpClient(this.config);
427
+ if (this.config.enableDebugMode) {
428
+ console.log('[Playe SDK] Initialized with config:', {
429
+ apiBaseUrl: this.config.apiBaseUrl,
430
+ enableDebugMode: this.config.enableDebugMode,
431
+ retryAttempts: this.config.retryAttempts,
432
+ timeoutMs: this.config.timeoutMs,
433
+ });
434
+ }
435
+ }
436
+ // Core session management
437
+ async initializeGameSession(params) {
438
+ try {
439
+ ValidationUtils.validateInitializeParams(params);
440
+ // Auto-detect device info if not provided
441
+ if (!params.deviceInfo) {
442
+ params.deviceInfo = DeviceUtils.getDeviceInfo();
443
+ }
444
+ const response = await this.httpClient.post('/api/game-sessions/initialize', params);
445
+ if (this.config.enableDebugMode) {
446
+ console.log('[Playe SDK] Game session initialized:', response.sessionId);
447
+ }
448
+ return response;
449
+ }
450
+ catch (error) {
451
+ throw this.handleError(error, 'Failed to initialize game session');
452
+ }
453
+ }
454
+ async startGameSession(sessionId, config) {
455
+ try {
456
+ ValidationUtils.validateSessionId(sessionId);
457
+ const response = await this.httpClient.post(`/api/game-sessions/${sessionId}/start`, {
458
+ config,
459
+ startTime: new Date().toISOString(),
460
+ });
461
+ // Start automatic heartbeat
462
+ this.startHeartbeat(sessionId);
463
+ if (this.config.enableDebugMode) {
464
+ console.log('[Playe SDK] Game session started:', sessionId);
465
+ }
466
+ return response;
467
+ }
468
+ catch (error) {
469
+ throw this.handleError(error, 'Failed to start game session');
470
+ }
471
+ }
472
+ async updateGameProgress(sessionId, progressData) {
473
+ try {
474
+ ValidationUtils.validateSessionId(sessionId);
475
+ ValidationUtils.validateProgressParams(progressData);
476
+ // Add client timestamp if not provided
477
+ if (!progressData.clientTimestamp) {
478
+ progressData.clientTimestamp = new Date().toISOString();
479
+ }
480
+ const response = await this.httpClient.post(`/api/game-sessions/${sessionId}/progress`, progressData);
481
+ if (this.config.enableDebugMode) {
482
+ console.log('[Playe SDK] Progress updated:', {
483
+ sessionId,
484
+ score: progressData.currentScore,
485
+ rank: response.currentRank,
486
+ });
487
+ }
488
+ return response;
489
+ }
490
+ catch (error) {
491
+ throw this.handleError(error, 'Failed to update game progress');
492
+ }
493
+ }
494
+ async completeGame(sessionId, completionData) {
495
+ try {
496
+ ValidationUtils.validateSessionId(sessionId);
497
+ ValidationUtils.validateCompleteParams(completionData);
498
+ const response = await this.httpClient.post(`/api/game-sessions/${sessionId}/complete`, {
499
+ ...completionData,
500
+ completionTime: new Date().toISOString(),
501
+ });
502
+ // Stop heartbeat
503
+ this.stopHeartbeat();
504
+ if (this.config.enableDebugMode) {
505
+ console.log('[Playe SDK] Game completed:', {
506
+ sessionId,
507
+ isWinner: response.isWinner,
508
+ finalRank: response.finalRank,
509
+ });
510
+ }
511
+ return response;
512
+ }
513
+ catch (error) {
514
+ throw this.handleError(error, 'Failed to complete game');
515
+ }
516
+ }
517
+ async abandonGame(sessionId, reason, lastKnownScore) {
518
+ try {
519
+ ValidationUtils.validateSessionId(sessionId);
520
+ const response = await this.httpClient.post(`/api/game-sessions/${sessionId}/abandon`, {
521
+ reason,
522
+ lastKnownScore,
523
+ abandonedAt: new Date().toISOString(),
524
+ });
525
+ // Stop heartbeat
526
+ this.stopHeartbeat();
527
+ if (this.config.enableDebugMode) {
528
+ console.log('[Playe SDK] Game abandoned:', sessionId);
529
+ }
530
+ return response;
531
+ }
532
+ catch (error) {
533
+ throw this.handleError(error, 'Failed to abandon game');
534
+ }
535
+ }
536
+ // Campaign information
537
+ async getCampaignStatus(campaignId, playerId) {
538
+ try {
539
+ const params = playerId ? `?playerId=${encodeURIComponent(playerId)}` : '';
540
+ return await this.httpClient.get(`/api/campaigns/${campaignId}/status${params}`);
541
+ }
542
+ catch (error) {
543
+ throw this.handleError(error, 'Failed to get campaign status');
544
+ }
545
+ }
546
+ async getLeaderboard(campaignId, limit, playerId) {
547
+ try {
548
+ const params = new URLSearchParams();
549
+ if (limit)
550
+ params.append('limit', limit.toString());
551
+ if (playerId)
552
+ params.append('playerId', playerId);
553
+ const queryString = params.toString() ? `?${params.toString()}` : '';
554
+ return await this.httpClient.get(`/api/campaigns/${campaignId}/leaderboard${queryString}`);
555
+ }
556
+ catch (error) {
557
+ throw this.handleError(error, 'Failed to get leaderboard');
558
+ }
559
+ }
560
+ async getPlayerAttempts(campaignId, playerId) {
561
+ try {
562
+ return await this.httpClient.get(`/api/campaigns/${campaignId}/players/${playerId}/attempts`);
563
+ }
564
+ catch (error) {
565
+ throw this.handleError(error, 'Failed to get player attempts');
566
+ }
567
+ }
568
+ // Validation and anti-cheat
569
+ async validateGameSession(sessionId, validationData) {
570
+ try {
571
+ ValidationUtils.validateSessionId(sessionId);
572
+ return await this.httpClient.post(`/api/game-sessions/${sessionId}/validate`, {
573
+ validationData,
574
+ clientTimestamp: new Date().toISOString(),
575
+ });
576
+ }
577
+ catch (error) {
578
+ throw this.handleError(error, 'Failed to validate game session');
579
+ }
580
+ }
581
+ async sendHeartbeat(sessionId, currentScore) {
582
+ try {
583
+ ValidationUtils.validateSessionId(sessionId);
584
+ return await this.httpClient.post(`/api/game-sessions/${sessionId}/heartbeat`, {
585
+ currentScore,
586
+ clientTimestamp: new Date().toISOString(),
587
+ });
588
+ }
589
+ catch (error) {
590
+ throw this.handleError(error, 'Failed to send heartbeat');
591
+ }
592
+ }
593
+ // Developer tools
594
+ async createTestGameSession(testConfig) {
595
+ try {
596
+ this._isDemo = true;
597
+ return await this.httpClient.post('/api/test/game-sessions', {
598
+ testConfig,
599
+ createdAt: new Date().toISOString(),
600
+ });
601
+ }
602
+ catch (error) {
603
+ throw this.handleError(error, 'Failed to create test game session');
604
+ }
605
+ }
606
+ // Legacy compatibility methods
607
+ async getGameById(id) {
608
+ try {
609
+ return await this.httpClient.get(`/api/legacy/games/${id}`);
610
+ }
611
+ catch (error) {
612
+ if (error instanceof Error && error.message.includes('404')) {
613
+ return null;
614
+ }
615
+ throw this.handleError(error, 'Failed to get game by ID');
616
+ }
617
+ }
618
+ async getGameSession() {
619
+ if (this.currentSession) {
620
+ return this.currentSession;
621
+ }
622
+ throw new GameSessionError('No active game session');
623
+ }
624
+ async updateGameSession(gameSession) {
625
+ try {
626
+ const response = await this.httpClient.put(`/api/legacy/game-sessions/${gameSession.id}`, gameSession);
627
+ this.currentSession = response;
628
+ return response;
629
+ }
630
+ catch (error) {
631
+ throw this.handleError(error, 'Failed to update game session');
632
+ }
633
+ }
634
+ // Utility methods
635
+ test(text) {
636
+ const message = text || 'Playe Developer SDK is working!';
637
+ console.log(`[Playe SDK Test] ${message}`);
638
+ if (this.config.enableDebugMode) {
639
+ console.log('[Playe SDK Test] Config:', this.config);
640
+ console.log('[Playe SDK Test] Device Info:', DeviceUtils.getDeviceInfo());
641
+ console.log('[Playe SDK Test] Feature Support:', DeviceUtils.getFeatureSupport());
642
+ }
643
+ }
644
+ isDemo() {
645
+ return this._isDemo;
646
+ }
647
+ gameLoadingStart() {
648
+ if (this.config.enableDebugMode) {
649
+ console.log('[Playe SDK] Game loading started');
650
+ }
651
+ // Emit custom event for tracking
652
+ if (DeviceUtils.isBrowser()) {
653
+ window.dispatchEvent(new CustomEvent('playe:game-loading-start'));
654
+ }
655
+ }
656
+ gameLoadingFinished() {
657
+ if (this.config.enableDebugMode) {
658
+ console.log('[Playe SDK] Game loading finished');
659
+ }
660
+ // Emit custom event for tracking
661
+ if (DeviceUtils.isBrowser()) {
662
+ window.dispatchEvent(new CustomEvent('playe:game-loading-finished'));
663
+ }
664
+ }
665
+ gamePlayStop() {
666
+ if (this.config.enableDebugMode) {
667
+ console.log('[Playe SDK] Game play stopped');
668
+ }
669
+ this.stopHeartbeat();
670
+ // Emit custom event for tracking
671
+ if (DeviceUtils.isBrowser()) {
672
+ window.dispatchEvent(new CustomEvent('playe:game-play-stop'));
673
+ }
674
+ }
675
+ // Private helper methods
676
+ validateConfig(config) {
677
+ if (!config.apiBaseUrl) {
678
+ throw new ValidationError('apiBaseUrl is required in SDK configuration');
679
+ }
680
+ try {
681
+ new URL(config.apiBaseUrl);
682
+ }
683
+ catch {
684
+ throw new ValidationError('apiBaseUrl must be a valid URL');
685
+ }
686
+ }
687
+ getDefaultConfig() {
688
+ return {
689
+ enableDebugMode: false,
690
+ retryAttempts: 3,
691
+ timeoutMs: 30000,
692
+ enableOfflineMode: false,
693
+ };
694
+ }
695
+ startHeartbeat(sessionId) {
696
+ if (this.heartbeatInterval) {
697
+ clearInterval(this.heartbeatInterval);
698
+ }
699
+ this.heartbeatInterval = window.setInterval(async () => {
700
+ try {
701
+ await this.sendHeartbeat(sessionId);
702
+ }
703
+ catch (error) {
704
+ if (this.config.enableDebugMode) {
705
+ console.warn('[Playe SDK] Heartbeat failed:', error);
706
+ }
707
+ }
708
+ }, 30000); // Send heartbeat every 30 seconds
709
+ }
710
+ stopHeartbeat() {
711
+ if (this.heartbeatInterval) {
712
+ clearInterval(this.heartbeatInterval);
713
+ this.heartbeatInterval = null;
714
+ }
715
+ }
716
+ handleError(error, context) {
717
+ if (error instanceof PlayeSDKError) {
718
+ return error;
719
+ }
720
+ if (error instanceof Error) {
721
+ // Check for specific error types
722
+ if (error.message.includes('401') || error.message.includes('403')) {
723
+ return new AuthenticationError(`Authentication failed: ${error.message}`, context);
724
+ }
725
+ if (error.message.includes('network') || error.message.includes('fetch')) {
726
+ return new NetworkError(`Network error: ${error.message}`, error, context);
727
+ }
728
+ return PlayeSDKError.fromError(error, context);
729
+ }
730
+ return new PlayeSDKError(`Unknown error: ${String(error)}`, undefined, context);
731
+ }
732
+ // Cleanup method
733
+ destroy() {
734
+ this.stopHeartbeat();
735
+ this.currentSession = null;
736
+ if (this.config.enableDebugMode) {
737
+ console.log('[Playe SDK] SDK destroyed');
738
+ }
739
+ }
740
+ }
741
+
742
+ // Main SDK exports
743
+ function createPlayeSDK(config) {
744
+ return new PlayeDeveloperSDK(config);
745
+ }
746
+ // Version
747
+ const VERSION = '1.0.0';
748
+
749
+ exports.AuthenticationError = AuthenticationError;
750
+ exports.DeviceUtils = DeviceUtils;
751
+ exports.GameSessionError = GameSessionError;
752
+ exports.NetworkError = NetworkError;
753
+ exports.PlayeDeveloperSDK = PlayeDeveloperSDK;
754
+ exports.PlayeSDKError = PlayeSDKError;
755
+ exports.VERSION = VERSION;
756
+ exports.ValidationError = ValidationError;
757
+ exports.ValidationUtils = ValidationUtils;
758
+ exports.createPlayeSDK = createPlayeSDK;
759
+ //# sourceMappingURL=index.cjs.js.map