@looplay/sdk 0.1.0 → 0.2.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 +86 -1
- package/dist/looplay-sdk.cjs.js +330 -32
- package/dist/looplay-sdk.cjs.js.map +1 -1
- package/dist/looplay-sdk.esm.js +330 -33
- package/dist/looplay-sdk.esm.js.map +1 -1
- package/dist/looplay-sdk.min.js +3 -1
- package/dist/looplay-sdk.min.js.map +1 -1
- package/dist/types/apps/api-client.d.ts +13 -0
- package/dist/types/apps/http.d.ts +2 -0
- package/dist/types/apps/service-client.d.ts +14 -4
- package/dist/types/auth/jwt.d.ts +2 -0
- package/dist/types/iframe/iframe-auth.d.ts +58 -0
- package/dist/types/iframe/index.d.ts +2 -0
- package/dist/types/iframe/types.d.ts +48 -0
- package/dist/types/index.d.ts +3 -1
- package/package.json +1 -1
package/dist/looplay-sdk.esm.js
CHANGED
|
@@ -138,6 +138,15 @@ var LooplayAuth = class {
|
|
|
138
138
|
};
|
|
139
139
|
|
|
140
140
|
// src/apps/http.ts
|
|
141
|
+
function getFetchFn(fetchOverride) {
|
|
142
|
+
if (fetchOverride) {
|
|
143
|
+
if (typeof window !== "undefined" && fetchOverride === window.fetch) {
|
|
144
|
+
return window.fetch.bind(window);
|
|
145
|
+
}
|
|
146
|
+
return fetchOverride;
|
|
147
|
+
}
|
|
148
|
+
return globalThis.fetch.bind(globalThis);
|
|
149
|
+
}
|
|
141
150
|
var HttpError = class extends Error {
|
|
142
151
|
status;
|
|
143
152
|
bodyText;
|
|
@@ -154,7 +163,7 @@ var HttpClient = class {
|
|
|
154
163
|
defaultHeaders;
|
|
155
164
|
constructor(options) {
|
|
156
165
|
this.baseUrl = options.baseUrl.replace(/\/$/, "");
|
|
157
|
-
this.fetchFn = options.fetch
|
|
166
|
+
this.fetchFn = getFetchFn(options.fetch);
|
|
158
167
|
this.defaultHeaders = options.defaultHeaders ?? {};
|
|
159
168
|
}
|
|
160
169
|
async request(method, path, options) {
|
|
@@ -165,6 +174,8 @@ var HttpClient = class {
|
|
|
165
174
|
);
|
|
166
175
|
if (options?.bearerToken) {
|
|
167
176
|
headers.Authorization = `Bearer ${options.bearerToken}`;
|
|
177
|
+
} else if (options?.anonId) {
|
|
178
|
+
headers["x-looplay-anon-id"] = options.anonId;
|
|
168
179
|
}
|
|
169
180
|
let body;
|
|
170
181
|
if (options?.body !== void 0) {
|
|
@@ -246,24 +257,24 @@ var ServiceClient = class {
|
|
|
246
257
|
async listRecentPlayed(bearerToken, query) {
|
|
247
258
|
return this.http.request("GET", "/games/recent-play", { bearerToken, query });
|
|
248
259
|
}
|
|
249
|
-
async trackPlay(
|
|
260
|
+
async trackPlay(auth, gameId, playTimeSeconds) {
|
|
250
261
|
const body = { playTimeSeconds };
|
|
251
262
|
await this.http.request("POST", `/games/${encodeURIComponent(gameId)}/play`, {
|
|
252
|
-
|
|
263
|
+
...auth,
|
|
253
264
|
body
|
|
254
265
|
});
|
|
255
266
|
return true;
|
|
256
267
|
}
|
|
257
|
-
async trackMatchEnd(
|
|
268
|
+
async trackMatchEnd(auth, gameId, body) {
|
|
258
269
|
await this.http.request("POST", `/games/${encodeURIComponent(gameId)}/end`, {
|
|
259
|
-
|
|
270
|
+
...auth,
|
|
260
271
|
body
|
|
261
272
|
});
|
|
262
273
|
return true;
|
|
263
274
|
}
|
|
264
|
-
async emitGameEvent(
|
|
275
|
+
async emitGameEvent(auth, gameId, body) {
|
|
265
276
|
return this.http.request("POST", `/games/${encodeURIComponent(gameId)}/emit`, {
|
|
266
|
-
|
|
277
|
+
...auth,
|
|
267
278
|
body
|
|
268
279
|
});
|
|
269
280
|
}
|
|
@@ -336,6 +347,7 @@ var ServiceClient = class {
|
|
|
336
347
|
var ApiClient = class {
|
|
337
348
|
raw;
|
|
338
349
|
getAccessToken;
|
|
350
|
+
getAnonymousId;
|
|
339
351
|
constructor(options) {
|
|
340
352
|
if (!options.baseUrl) throw new MissingBaseUrlError();
|
|
341
353
|
this.raw = new ServiceClient({
|
|
@@ -344,6 +356,7 @@ var ApiClient = class {
|
|
|
344
356
|
defaultHeaders: options.defaultHeaders
|
|
345
357
|
});
|
|
346
358
|
this.getAccessToken = options.getAccessToken;
|
|
359
|
+
this.getAnonymousId = options.getAnonymousId;
|
|
347
360
|
}
|
|
348
361
|
/** Expose the underlying route-level client (requires manual bearerToken passing). */
|
|
349
362
|
unsafeRaw() {
|
|
@@ -359,8 +372,20 @@ var ApiClient = class {
|
|
|
359
372
|
if (!token) throw new NotAuthenticatedError();
|
|
360
373
|
return token;
|
|
361
374
|
}
|
|
375
|
+
/**
|
|
376
|
+
* Resolves auth for the tracking endpoints: a real bearer token if logged
|
|
377
|
+
* in, otherwise an anonymous device id. Throws only when neither is
|
|
378
|
+
* available — anonymous tracking still requires *some* identity.
|
|
379
|
+
*/
|
|
380
|
+
async resolveTrackingAuth() {
|
|
381
|
+
const bearerToken = await this.getAccessToken?.();
|
|
382
|
+
if (bearerToken) return { bearerToken };
|
|
383
|
+
const anonId = this.getAnonymousId?.();
|
|
384
|
+
if (anonId) return { anonId };
|
|
385
|
+
throw new NotAuthenticatedError();
|
|
386
|
+
}
|
|
362
387
|
async getGameDetail(gameId) {
|
|
363
|
-
const token = await this.
|
|
388
|
+
const token = await this.getAccessToken?.();
|
|
364
389
|
return this.raw.getGameDetail(gameId, token);
|
|
365
390
|
}
|
|
366
391
|
async listRecentPlayed(query) {
|
|
@@ -368,22 +393,22 @@ var ApiClient = class {
|
|
|
368
393
|
return this.raw.listRecentPlayed(token, query);
|
|
369
394
|
}
|
|
370
395
|
async trackPlay(gameId, playTimeSeconds) {
|
|
371
|
-
const
|
|
372
|
-
return this.raw.trackPlay(
|
|
396
|
+
const auth = await this.resolveTrackingAuth();
|
|
397
|
+
return this.raw.trackPlay(auth, gameId, playTimeSeconds);
|
|
373
398
|
}
|
|
374
399
|
async trackMatch(gameId, body) {
|
|
375
|
-
const
|
|
376
|
-
return this.raw.trackMatchEnd(
|
|
400
|
+
const auth = await this.resolveTrackingAuth();
|
|
401
|
+
return this.raw.trackMatchEnd(auth, gameId, body);
|
|
377
402
|
}
|
|
378
403
|
async emit(gameId, actionCode, opts) {
|
|
379
|
-
const
|
|
404
|
+
const auth = await this.resolveTrackingAuth();
|
|
380
405
|
const body = {
|
|
381
406
|
actionCode,
|
|
382
407
|
value: opts?.value,
|
|
383
408
|
refId: opts?.refId,
|
|
384
409
|
payload: opts?.payload
|
|
385
410
|
};
|
|
386
|
-
return this.raw.emitGameEvent(
|
|
411
|
+
return this.raw.emitGameEvent(auth, gameId, body);
|
|
387
412
|
}
|
|
388
413
|
async getMyProfile() {
|
|
389
414
|
const token = await this.requireToken();
|
|
@@ -450,14 +475,14 @@ var LooplaySDK = class {
|
|
|
450
475
|
gameId;
|
|
451
476
|
initialized = false;
|
|
452
477
|
constructor(options = {}) {
|
|
453
|
-
const { auth, baseUrl, fetch
|
|
478
|
+
const { auth, baseUrl, fetch, defaultHeaders } = options;
|
|
454
479
|
if (auth) {
|
|
455
480
|
this.auth = new LooplayAuth(auth);
|
|
456
481
|
}
|
|
457
482
|
if (baseUrl) {
|
|
458
483
|
this.api = new ApiClient({
|
|
459
484
|
baseUrl,
|
|
460
|
-
fetch
|
|
485
|
+
fetch,
|
|
461
486
|
defaultHeaders,
|
|
462
487
|
getAccessToken: async () => this.auth?.getAccessToken()
|
|
463
488
|
});
|
|
@@ -524,6 +549,26 @@ var LooplaySDK = class {
|
|
|
524
549
|
}
|
|
525
550
|
};
|
|
526
551
|
|
|
552
|
+
// src/auth/jwt.ts
|
|
553
|
+
function decodeJwtClaims(token) {
|
|
554
|
+
if (!token) return void 0;
|
|
555
|
+
const parts = token.split(".");
|
|
556
|
+
if (parts.length < 2) return void 0;
|
|
557
|
+
try {
|
|
558
|
+
const payload = parts[1].replace(/-/g, "+").replace(/_/g, "/");
|
|
559
|
+
const padded = payload + "===".slice((payload.length + 3) % 4);
|
|
560
|
+
if (typeof globalThis.atob !== "function") return void 0;
|
|
561
|
+
const json = globalThis.atob(padded);
|
|
562
|
+
return JSON.parse(json);
|
|
563
|
+
} catch {
|
|
564
|
+
return void 0;
|
|
565
|
+
}
|
|
566
|
+
}
|
|
567
|
+
function decodeJwtExpMs(token) {
|
|
568
|
+
const exp = decodeJwtClaims(token)?.exp;
|
|
569
|
+
return typeof exp === "number" ? exp * 1e3 : void 0;
|
|
570
|
+
}
|
|
571
|
+
|
|
527
572
|
// src/auth/providers/telegram-auth-provider.ts
|
|
528
573
|
var MissingTelegramInitDataError = class extends LooplaySDKError {
|
|
529
574
|
constructor() {
|
|
@@ -537,22 +582,6 @@ var MissingRefreshTokenError = class extends LooplaySDKError {
|
|
|
537
582
|
this.name = "MissingRefreshTokenError";
|
|
538
583
|
}
|
|
539
584
|
};
|
|
540
|
-
function decodeJwtExpMs(token) {
|
|
541
|
-
if (!token) return void 0;
|
|
542
|
-
const parts = token.split(".");
|
|
543
|
-
if (parts.length < 2) return void 0;
|
|
544
|
-
try {
|
|
545
|
-
const payload = parts[1].replace(/-/g, "+").replace(/_/g, "/");
|
|
546
|
-
const padded = payload + "===".slice((payload.length + 3) % 4);
|
|
547
|
-
if (typeof globalThis.atob !== "function") return void 0;
|
|
548
|
-
const json = globalThis.atob(padded);
|
|
549
|
-
const parsed = JSON.parse(json);
|
|
550
|
-
if (typeof parsed.exp !== "number") return void 0;
|
|
551
|
-
return parsed.exp * 1e3;
|
|
552
|
-
} catch {
|
|
553
|
-
return void 0;
|
|
554
|
-
}
|
|
555
|
-
}
|
|
556
585
|
var TelegramAuthProvider = class {
|
|
557
586
|
id = "telegram";
|
|
558
587
|
client;
|
|
@@ -618,6 +647,274 @@ var TelegramAuthProvider = class {
|
|
|
618
647
|
}
|
|
619
648
|
};
|
|
620
649
|
|
|
621
|
-
|
|
650
|
+
// src/iframe/iframe-auth.ts
|
|
651
|
+
var DEFAULT_ANON_ID_STORAGE_KEY = "looplay:anon-id";
|
|
652
|
+
var DEFAULT_ANONYMOUS_FALLBACK_TIMEOUT_MS = 4e3;
|
|
653
|
+
var INITIAL_STATE = {
|
|
654
|
+
jwt: null,
|
|
655
|
+
gameId: null,
|
|
656
|
+
apiUrl: null,
|
|
657
|
+
anonymousId: null,
|
|
658
|
+
origin: null,
|
|
659
|
+
receivedAt: null
|
|
660
|
+
};
|
|
661
|
+
var isRecord = (value) => typeof value === "object" && value !== null;
|
|
662
|
+
var toOptionalString = (value) => {
|
|
663
|
+
if (typeof value !== "string") return null;
|
|
664
|
+
const normalized = value.trim();
|
|
665
|
+
return normalized ? normalized : null;
|
|
666
|
+
};
|
|
667
|
+
function generateAnonymousId() {
|
|
668
|
+
if (typeof crypto !== "undefined" && typeof crypto.randomUUID === "function") {
|
|
669
|
+
return crypto.randomUUID();
|
|
670
|
+
}
|
|
671
|
+
return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, (c) => {
|
|
672
|
+
const r = Math.random() * 16 | 0;
|
|
673
|
+
const v = c === "x" ? r : r & 3 | 8;
|
|
674
|
+
return v.toString(16);
|
|
675
|
+
});
|
|
676
|
+
}
|
|
677
|
+
function readStoredAnonymousId(key) {
|
|
678
|
+
if (typeof window === "undefined" || !window.localStorage) return null;
|
|
679
|
+
try {
|
|
680
|
+
return window.localStorage.getItem(key);
|
|
681
|
+
} catch {
|
|
682
|
+
return null;
|
|
683
|
+
}
|
|
684
|
+
}
|
|
685
|
+
function writeStoredAnonymousId(key, value) {
|
|
686
|
+
if (typeof window === "undefined" || !window.localStorage) return;
|
|
687
|
+
try {
|
|
688
|
+
window.localStorage.setItem(key, value);
|
|
689
|
+
} catch {
|
|
690
|
+
}
|
|
691
|
+
}
|
|
692
|
+
var LooplayIframeAuth = class {
|
|
693
|
+
state = INITIAL_STATE;
|
|
694
|
+
initialized = false;
|
|
695
|
+
lifecycleTrackingInitialized = false;
|
|
696
|
+
lastGameStartedKey = null;
|
|
697
|
+
client = null;
|
|
698
|
+
listeners = /* @__PURE__ */ new Set();
|
|
699
|
+
options;
|
|
700
|
+
messageListener = null;
|
|
701
|
+
anonymousFallbackTimer = null;
|
|
702
|
+
constructor(options = {}) {
|
|
703
|
+
this.options = options;
|
|
704
|
+
}
|
|
705
|
+
getState() {
|
|
706
|
+
return this.state;
|
|
707
|
+
}
|
|
708
|
+
getJwt() {
|
|
709
|
+
return this.state.jwt;
|
|
710
|
+
}
|
|
711
|
+
getApiUrl() {
|
|
712
|
+
return this.options.apiUrl ?? this.state.apiUrl;
|
|
713
|
+
}
|
|
714
|
+
/** Resolves to the configured `appId`, falling back to whatever the parent pushed as `gameId`. */
|
|
715
|
+
getGameId() {
|
|
716
|
+
return this.options.appId ?? this.state.gameId;
|
|
717
|
+
}
|
|
718
|
+
getAnonymousId() {
|
|
719
|
+
return this.state.anonymousId;
|
|
720
|
+
}
|
|
721
|
+
/** True once authenticated (bearer) or anonymous tracking is ready, given a configured `apiUrl`/`appId`. */
|
|
722
|
+
canTrack() {
|
|
723
|
+
if (!this.getApiUrl() || !this.getGameId()) return false;
|
|
724
|
+
return Boolean(this.state.jwt || this.state.anonymousId);
|
|
725
|
+
}
|
|
726
|
+
subscribe(listener) {
|
|
727
|
+
this.listeners.add(listener);
|
|
728
|
+
listener(this.state);
|
|
729
|
+
return () => this.listeners.delete(listener);
|
|
730
|
+
}
|
|
731
|
+
requestAuth() {
|
|
732
|
+
if (typeof window === "undefined" || window.parent === window) {
|
|
733
|
+
this.debug("standalone mode detected; auth request skipped");
|
|
734
|
+
return;
|
|
735
|
+
}
|
|
736
|
+
this.debug("requesting auth from parent", { targetOrigin: this.resolveParentOrigin() ?? "*" });
|
|
737
|
+
window.parent.postMessage({ type: "LOOPLAY_AUTH_REQUEST" }, this.resolveParentOrigin() ?? "*");
|
|
738
|
+
}
|
|
739
|
+
/** Idempotent; attaches the postMessage listener (if embedded) and arms the anonymous fallback. */
|
|
740
|
+
init() {
|
|
741
|
+
if (this.initialized || typeof window === "undefined") return;
|
|
742
|
+
this.initialized = true;
|
|
743
|
+
const isEmbedded = window.parent !== window;
|
|
744
|
+
this.debug("auth listener initialized", {
|
|
745
|
+
isIframe: isEmbedded,
|
|
746
|
+
parentOrigin: this.resolveParentOrigin()
|
|
747
|
+
});
|
|
748
|
+
if (!isEmbedded) {
|
|
749
|
+
this.activateAnonymousMode();
|
|
750
|
+
return;
|
|
751
|
+
}
|
|
752
|
+
const parentOrigin = this.resolveParentOrigin();
|
|
753
|
+
if (!parentOrigin) {
|
|
754
|
+
console.warn(
|
|
755
|
+
"[LooplaySDK] LooplayIframeAuth could not resolve a parent origin to verify postMessage against (document.referrer is empty). Auth messages from window.parent will be accepted regardless of origin. Pass `parentOrigin` explicitly to harden this."
|
|
756
|
+
);
|
|
757
|
+
}
|
|
758
|
+
this.messageListener = (event) => {
|
|
759
|
+
if (event.source !== window.parent) return;
|
|
760
|
+
if (parentOrigin && event.origin !== parentOrigin) {
|
|
761
|
+
this.debug("ignored auth message from unexpected origin", {
|
|
762
|
+
expectedOrigin: parentOrigin,
|
|
763
|
+
receivedOrigin: event.origin
|
|
764
|
+
});
|
|
765
|
+
return;
|
|
766
|
+
}
|
|
767
|
+
if (!isRecord(event.data) || event.data.type !== "LOOPLAY_AUTH") return;
|
|
768
|
+
this.handleAuthMessage(event.data, event.origin);
|
|
769
|
+
};
|
|
770
|
+
window.addEventListener("message", this.messageListener);
|
|
771
|
+
this.anonymousFallbackTimer = setTimeout(() => {
|
|
772
|
+
if (this.state.jwt) return;
|
|
773
|
+
this.activateAnonymousMode();
|
|
774
|
+
}, this.options.anonymousFallbackTimeoutMs ?? DEFAULT_ANONYMOUS_FALLBACK_TIMEOUT_MS);
|
|
775
|
+
this.requestAuth();
|
|
776
|
+
}
|
|
777
|
+
/** Removes the postMessage listener, cancels timers, and clears subscribers; safe to call multiple times. */
|
|
778
|
+
dispose() {
|
|
779
|
+
if (typeof window !== "undefined" && this.messageListener) {
|
|
780
|
+
window.removeEventListener("message", this.messageListener);
|
|
781
|
+
}
|
|
782
|
+
if (this.anonymousFallbackTimer !== null) {
|
|
783
|
+
clearTimeout(this.anonymousFallbackTimer);
|
|
784
|
+
}
|
|
785
|
+
this.messageListener = null;
|
|
786
|
+
this.anonymousFallbackTimer = null;
|
|
787
|
+
this.initialized = false;
|
|
788
|
+
this.lifecycleTrackingInitialized = false;
|
|
789
|
+
this.listeners.clear();
|
|
790
|
+
}
|
|
791
|
+
/** Lazily builds (and rebuilds on auth change) the `ApiClient` bound to the current auth mode. */
|
|
792
|
+
getClient() {
|
|
793
|
+
if (this.client) return this.client;
|
|
794
|
+
const baseUrl = this.getApiUrl();
|
|
795
|
+
this.debug("creating sdk ApiClient", { baseUrl });
|
|
796
|
+
this.client = new ApiClient({
|
|
797
|
+
baseUrl: baseUrl ?? void 0,
|
|
798
|
+
getAccessToken: async () => this.getJwt() ?? void 0,
|
|
799
|
+
getAnonymousId: () => this.getAnonymousId() ?? void 0
|
|
800
|
+
});
|
|
801
|
+
return this.client;
|
|
802
|
+
}
|
|
803
|
+
trackPlay(playTimeSeconds) {
|
|
804
|
+
const gameId = this.getGameId();
|
|
805
|
+
if (!this.canTrack() || !gameId) {
|
|
806
|
+
this.debug("trackPlay skipped; auth is not ready", { hasGameId: Boolean(gameId) });
|
|
807
|
+
return Promise.resolve(false);
|
|
808
|
+
}
|
|
809
|
+
this.debug("trackPlay dispatched", { gameId, playTimeSeconds });
|
|
810
|
+
return this.getClient().trackPlay(gameId, playTimeSeconds);
|
|
811
|
+
}
|
|
812
|
+
trackMatch(matchId, opts) {
|
|
813
|
+
const gameId = this.getGameId();
|
|
814
|
+
if (!this.canTrack() || !gameId) {
|
|
815
|
+
this.debug("trackMatch skipped; auth is not ready", { matchId });
|
|
816
|
+
return Promise.resolve(false);
|
|
817
|
+
}
|
|
818
|
+
this.debug("trackMatch dispatched", { gameId, matchId, ...opts });
|
|
819
|
+
return this.getClient().trackMatch(gameId, {
|
|
820
|
+
matchId,
|
|
821
|
+
matchDurationSeconds: opts.durationSeconds,
|
|
822
|
+
isCompleted: opts.isCompleted,
|
|
823
|
+
isWin: opts.isWin
|
|
824
|
+
});
|
|
825
|
+
}
|
|
826
|
+
emitGameEvent(actionCode, opts) {
|
|
827
|
+
const gameId = this.getGameId();
|
|
828
|
+
if (!this.canTrack() || !gameId) {
|
|
829
|
+
this.debug("emitGameEvent skipped; auth is not ready", { actionCode });
|
|
830
|
+
return Promise.resolve(null);
|
|
831
|
+
}
|
|
832
|
+
this.debug("emitGameEvent dispatched", { gameId, actionCode, ...opts });
|
|
833
|
+
return this.getClient().emit(gameId, actionCode, opts);
|
|
834
|
+
}
|
|
835
|
+
/** Idempotent; auto-emits GAME_STARTED exactly once per distinct auth session (bearer or anonymous). */
|
|
836
|
+
initLifecycleTracking() {
|
|
837
|
+
if (this.lifecycleTrackingInitialized || typeof window === "undefined") return;
|
|
838
|
+
this.lifecycleTrackingInitialized = true;
|
|
839
|
+
this.debug("lifecycle tracking initialized");
|
|
840
|
+
this.subscribe(() => {
|
|
841
|
+
const key = this.getTrackingKey();
|
|
842
|
+
if (!key || this.lastGameStartedKey === key) return;
|
|
843
|
+
this.lastGameStartedKey = key;
|
|
844
|
+
this.debug("auto GAME_STARTED dispatch");
|
|
845
|
+
void this.emitGameEvent("GAME_STARTED");
|
|
846
|
+
});
|
|
847
|
+
}
|
|
848
|
+
getTrackingKey() {
|
|
849
|
+
const gameId = this.getGameId();
|
|
850
|
+
const apiUrl = this.getApiUrl();
|
|
851
|
+
const identity = this.state.jwt ?? this.state.anonymousId;
|
|
852
|
+
if (!gameId || !apiUrl || !identity) return null;
|
|
853
|
+
return `${apiUrl}
|
|
854
|
+
${gameId}
|
|
855
|
+
${identity}`;
|
|
856
|
+
}
|
|
857
|
+
/** Generates (or restores) a persisted anonymous device id and activates anonymous tracking. */
|
|
858
|
+
activateAnonymousMode() {
|
|
859
|
+
if (this.state.anonymousId) return;
|
|
860
|
+
const key = this.options.anonymousIdStorageKey ?? DEFAULT_ANON_ID_STORAGE_KEY;
|
|
861
|
+
const anonymousId = readStoredAnonymousId(key) ?? generateAnonymousId();
|
|
862
|
+
writeStoredAnonymousId(key, anonymousId);
|
|
863
|
+
this.lastGameStartedKey = null;
|
|
864
|
+
this.setState({ ...this.state, anonymousId, receivedAt: Date.now() });
|
|
865
|
+
this.debug("anonymous tracking mode active", { anonymousId });
|
|
866
|
+
}
|
|
867
|
+
handleAuthMessage(data, origin) {
|
|
868
|
+
if (this.anonymousFallbackTimer !== null) {
|
|
869
|
+
clearTimeout(this.anonymousFallbackTimer);
|
|
870
|
+
this.anonymousFallbackTimer = null;
|
|
871
|
+
}
|
|
872
|
+
const jwt = toOptionalString(data.jwt);
|
|
873
|
+
const gameId = toOptionalString(data.gameId);
|
|
874
|
+
const apiUrl = toOptionalString(data.apiUrl);
|
|
875
|
+
if (!jwt || !gameId || !apiUrl) {
|
|
876
|
+
this.lastGameStartedKey = null;
|
|
877
|
+
this.setState({ ...this.state, jwt: null, gameId: null, apiUrl: null, origin, receivedAt: Date.now() });
|
|
878
|
+
this.debug("auth cleared by parent; falling back to anonymous tracking");
|
|
879
|
+
this.activateAnonymousMode();
|
|
880
|
+
return;
|
|
881
|
+
}
|
|
882
|
+
this.lastGameStartedKey = null;
|
|
883
|
+
this.setState({
|
|
884
|
+
jwt,
|
|
885
|
+
gameId,
|
|
886
|
+
apiUrl,
|
|
887
|
+
anonymousId: null,
|
|
888
|
+
origin,
|
|
889
|
+
receivedAt: Date.now()
|
|
890
|
+
});
|
|
891
|
+
}
|
|
892
|
+
setState(next) {
|
|
893
|
+
this.state = next;
|
|
894
|
+
this.client = null;
|
|
895
|
+
this.debug("auth state updated", {
|
|
896
|
+
gameId: this.getGameId(),
|
|
897
|
+
apiUrl: this.getApiUrl(),
|
|
898
|
+
origin: next.origin,
|
|
899
|
+
hasJwt: Boolean(next.jwt),
|
|
900
|
+
hasAnonymousId: Boolean(next.anonymousId)
|
|
901
|
+
});
|
|
902
|
+
this.listeners.forEach((listener) => listener(next));
|
|
903
|
+
}
|
|
904
|
+
resolveParentOrigin() {
|
|
905
|
+
if (this.options.parentOrigin) return this.options.parentOrigin;
|
|
906
|
+
if (typeof document === "undefined" || !document.referrer) return null;
|
|
907
|
+
try {
|
|
908
|
+
return new URL(document.referrer).origin;
|
|
909
|
+
} catch {
|
|
910
|
+
return null;
|
|
911
|
+
}
|
|
912
|
+
}
|
|
913
|
+
debug(message, details) {
|
|
914
|
+
this.options.onDebug?.(message, details);
|
|
915
|
+
}
|
|
916
|
+
};
|
|
917
|
+
|
|
918
|
+
export { ApiClient, BrowserLocalStorageAuthStorage, HttpClient, HttpError, LooplayAuth, LooplayIframeAuth, LooplaySDK, LooplaySDKError, MemoryAuthStorage, MissingAuthError, MissingBaseUrlError, NotAuthenticatedError, NotInitializedError, ServiceClient, TelegramAuthProvider };
|
|
622
919
|
//# sourceMappingURL=looplay-sdk.esm.js.map
|
|
623
920
|
//# sourceMappingURL=looplay-sdk.esm.js.map
|