@looplay/sdk 0.1.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 ADDED
@@ -0,0 +1,3 @@
1
+ # Looplay
2
+
3
+ API Reference: https://docs.looplay.gg/build-on-loopplay/looplay-sdk
@@ -0,0 +1,638 @@
1
+ 'use strict';
2
+
3
+ // src/errors.ts
4
+ var LooplaySDKError = class extends Error {
5
+ constructor(message) {
6
+ super(message);
7
+ this.name = "LooplaySDKError";
8
+ }
9
+ };
10
+ var NotInitializedError = class extends LooplaySDKError {
11
+ constructor() {
12
+ super("LooplaySDK not initialized. Call init({ gameId }) first.");
13
+ this.name = "NotInitializedError";
14
+ }
15
+ };
16
+ var MissingBaseUrlError = class extends LooplaySDKError {
17
+ constructor() {
18
+ super("Missing baseUrl. Provide `baseUrl` to enable HTTP integration.");
19
+ this.name = "MissingBaseUrlError";
20
+ }
21
+ };
22
+ var MissingAuthError = class extends LooplaySDKError {
23
+ constructor() {
24
+ super("Missing auth. Provide `auth` options to enable user login/session.");
25
+ this.name = "MissingAuthError";
26
+ }
27
+ };
28
+ var NotAuthenticatedError = class extends LooplaySDKError {
29
+ constructor() {
30
+ super("Not authenticated. Login is required for this operation.");
31
+ this.name = "NotAuthenticatedError";
32
+ }
33
+ };
34
+
35
+ // src/auth/storage.ts
36
+ var MemoryAuthStorage = class {
37
+ map = /* @__PURE__ */ new Map();
38
+ async getItem(key) {
39
+ return this.map.has(key) ? this.map.get(key) : null;
40
+ }
41
+ async setItem(key, value) {
42
+ this.map.set(key, value);
43
+ }
44
+ async removeItem(key) {
45
+ this.map.delete(key);
46
+ }
47
+ };
48
+ var BrowserLocalStorageAuthStorage = class {
49
+ async getItem(key) {
50
+ if (typeof window === "undefined" || !window.localStorage) return null;
51
+ return window.localStorage.getItem(key);
52
+ }
53
+ async setItem(key, value) {
54
+ if (typeof window === "undefined" || !window.localStorage) return;
55
+ window.localStorage.setItem(key, value);
56
+ }
57
+ async removeItem(key) {
58
+ if (typeof window === "undefined" || !window.localStorage) return;
59
+ window.localStorage.removeItem(key);
60
+ }
61
+ };
62
+
63
+ // src/auth/looplay-auth.ts
64
+ var InvalidSessionError = class extends LooplaySDKError {
65
+ constructor() {
66
+ super("Invalid session returned by auth provider");
67
+ this.name = "InvalidSessionError";
68
+ }
69
+ };
70
+ var LooplayAuth = class {
71
+ provider;
72
+ storage;
73
+ key;
74
+ session = null;
75
+ listeners = /* @__PURE__ */ new Set();
76
+ constructor(options) {
77
+ this.provider = options.provider;
78
+ this.storage = options.storage ?? new MemoryAuthStorage();
79
+ this.key = `${options.storageKeyPrefix ?? "looplay"}:auth:session`;
80
+ }
81
+ async init() {
82
+ await this.provider.init?.();
83
+ await this.restoreFromStorage();
84
+ await this.provider.onSessionChanged?.(this.session);
85
+ }
86
+ getSession() {
87
+ return this.session;
88
+ }
89
+ async login(params) {
90
+ const result = await this.provider.login(params);
91
+ if (!result?.session?.user?.id) {
92
+ throw new InvalidSessionError();
93
+ }
94
+ await this.setSession(result.session, "login");
95
+ return result.session;
96
+ }
97
+ async logout() {
98
+ await this.provider.logout();
99
+ await this.setSession(null, "logout");
100
+ }
101
+ async getAccessToken() {
102
+ if (!this.session) return void 0;
103
+ const now = Date.now();
104
+ const isExpired = typeof this.session.expiresAt === "number" && this.session.expiresAt <= now;
105
+ if (!isExpired) return this.session.accessToken;
106
+ if (!this.provider.refresh) return this.session.accessToken;
107
+ const refreshed = await this.provider.refresh();
108
+ const next = { ...this.session, ...refreshed };
109
+ await this.setSession(next, "sessionChanged");
110
+ return next.accessToken;
111
+ }
112
+ on(eventName, listener) {
113
+ this.listeners.add(listener);
114
+ return () => this.listeners.delete(listener);
115
+ }
116
+ async restoreFromStorage() {
117
+ const raw = await this.storage.getItem(this.key);
118
+ if (!raw) return;
119
+ try {
120
+ const parsed = JSON.parse(raw);
121
+ if (parsed?.user?.id) {
122
+ this.session = parsed;
123
+ }
124
+ } catch {
125
+ await this.storage.removeItem(this.key);
126
+ }
127
+ }
128
+ async setSession(next, event) {
129
+ this.session = next;
130
+ await this.provider.onSessionChanged?.(this.session);
131
+ if (next) {
132
+ await this.storage.setItem(this.key, JSON.stringify(next));
133
+ } else {
134
+ await this.storage.removeItem(this.key);
135
+ }
136
+ for (const listener of this.listeners) {
137
+ listener(this.session);
138
+ }
139
+ }
140
+ };
141
+
142
+ // src/apps/http.ts
143
+ var HttpError = class extends Error {
144
+ status;
145
+ bodyText;
146
+ constructor(message, status, bodyText) {
147
+ super(message);
148
+ this.name = "HttpError";
149
+ this.status = status;
150
+ this.bodyText = bodyText;
151
+ }
152
+ };
153
+ var HttpClient = class {
154
+ baseUrl;
155
+ fetchFn;
156
+ defaultHeaders;
157
+ constructor(options) {
158
+ this.baseUrl = options.baseUrl.replace(/\/$/, "");
159
+ this.fetchFn = options.fetch ?? fetch;
160
+ this.defaultHeaders = options.defaultHeaders ?? {};
161
+ }
162
+ async request(method, path, options) {
163
+ const url = this.buildUrl(path, options?.query);
164
+ const headers = this.mergeHeaders(
165
+ this.defaultHeaders,
166
+ options?.headers
167
+ );
168
+ if (options?.bearerToken) {
169
+ headers.Authorization = `Bearer ${options.bearerToken}`;
170
+ }
171
+ let body;
172
+ if (options?.body !== void 0) {
173
+ headers["Content-Type"] = headers["Content-Type"] ?? "application/json";
174
+ body = JSON.stringify(options.body);
175
+ }
176
+ const res = await this.fetchFn(url, { method, headers, body });
177
+ if (!res.ok) {
178
+ const text2 = await this.safeText(res);
179
+ throw new HttpError(`HTTP ${res.status} ${res.statusText}`, res.status, text2);
180
+ }
181
+ const text = await this.safeText(res);
182
+ if (!text) return void 0;
183
+ return JSON.parse(text);
184
+ }
185
+ mergeHeaders(...sources) {
186
+ const result = {};
187
+ for (const src of sources) {
188
+ if (!src) continue;
189
+ for (const [key, value] of Object.entries(src)) {
190
+ if (value === void 0) continue;
191
+ result[key] = value;
192
+ }
193
+ }
194
+ return result;
195
+ }
196
+ buildUrl(path, query) {
197
+ const normalizedPath = path.startsWith("/") ? path : `/${path}`;
198
+ const url = new URL(`${this.baseUrl}${normalizedPath}`);
199
+ if (query) {
200
+ for (const [key, value] of Object.entries(query)) {
201
+ if (value === void 0) continue;
202
+ url.searchParams.set(key, String(value));
203
+ }
204
+ }
205
+ return url.toString();
206
+ }
207
+ async safeText(res) {
208
+ try {
209
+ return await res.text();
210
+ } catch {
211
+ return "";
212
+ }
213
+ }
214
+ };
215
+
216
+ // src/apps/service-client.ts
217
+ var ServiceClient = class {
218
+ http;
219
+ constructor(options) {
220
+ this.http = new HttpClient(options);
221
+ }
222
+ // ─────────────────────────────────────────────────────────────────────────────
223
+ // Auth
224
+ // ─────────────────────────────────────────────────────────────────────────────
225
+ async telegramLogin(telegramInitData) {
226
+ const body = { telegramInitData };
227
+ return this.http.request("POST", "/account/telegram-login", { body });
228
+ }
229
+ async refresh(refreshToken) {
230
+ const body = { refreshToken };
231
+ return this.http.request("POST", "/account/refresh", { body });
232
+ }
233
+ async logout(refreshToken) {
234
+ const body = { refreshToken };
235
+ return this.http.request("POST", "/account/logout", { body });
236
+ }
237
+ // ─────────────────────────────────────────────────────────────────────────────
238
+ // Games
239
+ // ─────────────────────────────────────────────────────────────────────────────
240
+ async listGames(query) {
241
+ return this.http.request("GET", "/games", { query });
242
+ }
243
+ async getGameDetail(gameId, bearerToken) {
244
+ return this.http.request("GET", `/games/${encodeURIComponent(gameId)}`, {
245
+ bearerToken
246
+ });
247
+ }
248
+ async listRecentPlayed(bearerToken, query) {
249
+ return this.http.request("GET", "/games/recent-play", { bearerToken, query });
250
+ }
251
+ async trackPlay(bearerToken, gameId, playTimeSeconds) {
252
+ const body = { playTimeSeconds };
253
+ await this.http.request("POST", `/games/${encodeURIComponent(gameId)}/play`, {
254
+ bearerToken,
255
+ body
256
+ });
257
+ return true;
258
+ }
259
+ async trackMatchEnd(bearerToken, gameId, body) {
260
+ await this.http.request("POST", `/games/${encodeURIComponent(gameId)}/end`, {
261
+ bearerToken,
262
+ body
263
+ });
264
+ return true;
265
+ }
266
+ async emitGameEvent(bearerToken, gameId, body) {
267
+ return this.http.request("POST", `/games/${encodeURIComponent(gameId)}/emit`, {
268
+ bearerToken,
269
+ body
270
+ });
271
+ }
272
+ // ─────────────────────────────────────────────────────────────────────────────
273
+ // Balance
274
+ // ─────────────────────────────────────────────────────────────────────────────
275
+ async getBalances(bearerToken, query) {
276
+ return this.http.request("GET", "/balance", { bearerToken, query });
277
+ }
278
+ async getBalanceHistory(bearerToken, query) {
279
+ return this.http.request("GET", "/balance/history", { bearerToken, query });
280
+ }
281
+ // ─────────────────────────────────────────────────────────────────────────────
282
+ // User
283
+ // ─────────────────────────────────────────────────────────────────────────────
284
+ async getMyProfile(bearerToken) {
285
+ return this.http.request("GET", "/user/profile", { bearerToken });
286
+ }
287
+ async getMyBalance(bearerToken) {
288
+ return this.http.request("GET", "/user/balance", { bearerToken });
289
+ }
290
+ // ─────────────────────────────────────────────────────────────────────────────
291
+ // Referrals
292
+ // ─────────────────────────────────────────────────────────────────────────────
293
+ async listReferrals(bearerToken, query) {
294
+ return this.http.request("GET", "/referrals", { bearerToken, query });
295
+ }
296
+ async setReferral(bearerToken, body) {
297
+ return this.http.request("POST", "/referrals/set", { bearerToken, body });
298
+ }
299
+ // ─────────────────────────────────────────────────────────────────────────────
300
+ // Tasks / Quests
301
+ // ─────────────────────────────────────────────────────────────────────────────
302
+ async listTasks(bearerToken) {
303
+ return this.http.request("GET", "/task", { bearerToken });
304
+ }
305
+ async listFinishedTasks(bearerToken) {
306
+ return this.http.request("GET", "/task/finished", { bearerToken });
307
+ }
308
+ async startTask(bearerToken, body) {
309
+ return this.http.request("POST", "/task/start", { bearerToken, body });
310
+ }
311
+ async claimTask(bearerToken, body) {
312
+ return this.http.request("POST", "/task/claim", { bearerToken, body });
313
+ }
314
+ async getPlatformQuests(bearerToken, query) {
315
+ return this.http.request("GET", "/quest/platform", { bearerToken, query });
316
+ }
317
+ async getCampaignQuests(bearerToken, query) {
318
+ return this.http.request("GET", "/quest/campaign", { bearerToken, query });
319
+ }
320
+ async claimQuest(bearerToken, questProgressId) {
321
+ return this.http.request("POST", `/quest/${encodeURIComponent(questProgressId)}/claim`, {
322
+ bearerToken
323
+ });
324
+ }
325
+ async claimMilestone(bearerToken, milestoneId, body) {
326
+ return this.http.request(
327
+ "POST",
328
+ `/quest/milestone/${encodeURIComponent(milestoneId)}/claim`,
329
+ {
330
+ bearerToken,
331
+ body
332
+ }
333
+ );
334
+ }
335
+ };
336
+
337
+ // src/apps/api-client.ts
338
+ var ApiClient = class {
339
+ raw;
340
+ getAccessToken;
341
+ constructor(options) {
342
+ if (!options.baseUrl) throw new MissingBaseUrlError();
343
+ this.raw = new ServiceClient({
344
+ baseUrl: options.baseUrl,
345
+ fetch: options.fetch,
346
+ defaultHeaders: options.defaultHeaders
347
+ });
348
+ this.getAccessToken = options.getAccessToken;
349
+ }
350
+ /** Expose the underlying route-level client (requires manual bearerToken passing). */
351
+ unsafeRaw() {
352
+ return this.raw;
353
+ }
354
+ // Public endpoints
355
+ async listGames(query) {
356
+ return this.raw.listGames(query);
357
+ }
358
+ // Authenticated endpoints
359
+ async requireToken() {
360
+ const token = await this.getAccessToken?.();
361
+ if (!token) throw new NotAuthenticatedError();
362
+ return token;
363
+ }
364
+ async getGameDetail(gameId) {
365
+ const token = await this.requireToken();
366
+ return this.raw.getGameDetail(gameId, token);
367
+ }
368
+ async listRecentPlayed(query) {
369
+ const token = await this.requireToken();
370
+ return this.raw.listRecentPlayed(token, query);
371
+ }
372
+ async trackPlay(gameId, playTimeSeconds) {
373
+ const token = await this.requireToken();
374
+ return this.raw.trackPlay(token, gameId, playTimeSeconds);
375
+ }
376
+ async trackMatch(gameId, body) {
377
+ const token = await this.requireToken();
378
+ return this.raw.trackMatchEnd(token, gameId, body);
379
+ }
380
+ async emit(gameId, actionCode, opts) {
381
+ const token = await this.requireToken();
382
+ const body = {
383
+ actionCode,
384
+ value: opts?.value,
385
+ refId: opts?.refId,
386
+ payload: opts?.payload
387
+ };
388
+ return this.raw.emitGameEvent(token, gameId, body);
389
+ }
390
+ async getMyProfile() {
391
+ const token = await this.requireToken();
392
+ return this.raw.getMyProfile(token);
393
+ }
394
+ async getMyBalance() {
395
+ const token = await this.requireToken();
396
+ return this.raw.getMyBalance(token);
397
+ }
398
+ async getBalances(query) {
399
+ const token = await this.requireToken();
400
+ return this.raw.getBalances(token, query);
401
+ }
402
+ async getBalanceHistory(query) {
403
+ const token = await this.requireToken();
404
+ return this.raw.getBalanceHistory(token, query);
405
+ }
406
+ async listReferrals(query) {
407
+ const token = await this.requireToken();
408
+ return this.raw.listReferrals(token, query);
409
+ }
410
+ async setReferral(body) {
411
+ const token = await this.requireToken();
412
+ return this.raw.setReferral(token, body);
413
+ }
414
+ async listTasks() {
415
+ const token = await this.requireToken();
416
+ return this.raw.listTasks(token);
417
+ }
418
+ async listFinishedTasks() {
419
+ const token = await this.requireToken();
420
+ return this.raw.listFinishedTasks(token);
421
+ }
422
+ async startTask(body) {
423
+ const token = await this.requireToken();
424
+ return this.raw.startTask(token, body);
425
+ }
426
+ async claimTask(body) {
427
+ const token = await this.requireToken();
428
+ return this.raw.claimTask(token, body);
429
+ }
430
+ async getPlatformQuests(query) {
431
+ const token = await this.requireToken();
432
+ return this.raw.getPlatformQuests(token, query);
433
+ }
434
+ async getCampaignQuests(query) {
435
+ const token = await this.requireToken();
436
+ return this.raw.getCampaignQuests(token, query);
437
+ }
438
+ async claimQuest(questProgressId) {
439
+ const token = await this.requireToken();
440
+ return this.raw.claimQuest(token, questProgressId);
441
+ }
442
+ async claimMilestone(milestoneId, body) {
443
+ const token = await this.requireToken();
444
+ return this.raw.claimMilestone(token, milestoneId, body);
445
+ }
446
+ };
447
+
448
+ // src/looplay-sdk.ts
449
+ var LooplaySDK = class {
450
+ auth;
451
+ api;
452
+ gameId;
453
+ initialized = false;
454
+ constructor(options = {}) {
455
+ const { auth, baseUrl, fetch: fetch2, defaultHeaders } = options;
456
+ if (auth) {
457
+ this.auth = new LooplayAuth(auth);
458
+ }
459
+ if (baseUrl) {
460
+ this.api = new ApiClient({
461
+ baseUrl,
462
+ fetch: fetch2,
463
+ defaultHeaders,
464
+ getAccessToken: async () => this.auth?.getAccessToken()
465
+ });
466
+ }
467
+ }
468
+ async init(params) {
469
+ this.gameId = params.gameId;
470
+ await this.auth?.init();
471
+ await this.verifyGameId(params);
472
+ this.initialized = true;
473
+ }
474
+ async getAccessToken() {
475
+ return this.auth?.getAccessToken();
476
+ }
477
+ getGameId() {
478
+ return this.gameId;
479
+ }
480
+ async getMyProfile() {
481
+ this.assertInitialized();
482
+ if (!this.api) throw new MissingBaseUrlError();
483
+ return this.api.getMyProfile();
484
+ }
485
+ async trackPlay(playTimeSeconds) {
486
+ this.assertInitialized();
487
+ if (!this.api) throw new MissingBaseUrlError();
488
+ if (!this.gameId) throw new NotInitializedError();
489
+ return this.api.trackPlay(this.gameId, playTimeSeconds);
490
+ }
491
+ /**
492
+ * Report that a match/round ended.
493
+ * Maps to PLAY_MATCH (and WIN_MATCH when isWin=true).
494
+ * matchId is the idempotency anchor — safe to call twice with the same matchId.
495
+ */
496
+ async trackMatch(matchId, opts) {
497
+ this.assertInitialized();
498
+ if (!this.api) throw new MissingBaseUrlError();
499
+ if (!this.gameId) throw new NotInitializedError();
500
+ return this.api.trackMatch(this.gameId, {
501
+ matchId,
502
+ matchDurationSeconds: opts.durationSeconds,
503
+ isCompleted: opts.isCompleted,
504
+ isWin: opts.isWin
505
+ });
506
+ }
507
+ /**
508
+ * Emit a custom action event (CUSTOM_ACTION metric).
509
+ * Use GameEventKey for platform-defined codes; custom strings for game-specific quests.
510
+ * Pass refId to make the call idempotent — same refId = same event, dedup-safe.
511
+ */
512
+ async emit(actionCode, opts) {
513
+ this.assertInitialized();
514
+ if (!this.api) throw new MissingBaseUrlError();
515
+ if (!this.gameId) throw new NotInitializedError();
516
+ return this.api.emit(this.gameId, actionCode, opts);
517
+ }
518
+ assertInitialized() {
519
+ if (!this.initialized) throw new NotInitializedError();
520
+ }
521
+ async verifyGameId(params) {
522
+ if ((params.verifyMode ?? "none") === "none") return;
523
+ if (!this.auth) throw new MissingAuthError();
524
+ if (!this.api) throw new MissingBaseUrlError();
525
+ await this.api.getGameDetail(params.gameId);
526
+ }
527
+ };
528
+
529
+ // src/auth/providers/telegram-auth-provider.ts
530
+ var MissingTelegramInitDataError = class extends LooplaySDKError {
531
+ constructor() {
532
+ super("Missing Telegram init data");
533
+ this.name = "MissingTelegramInitDataError";
534
+ }
535
+ };
536
+ var MissingRefreshTokenError = class extends LooplaySDKError {
537
+ constructor() {
538
+ super("Missing refresh token");
539
+ this.name = "MissingRefreshTokenError";
540
+ }
541
+ };
542
+ function decodeJwtExpMs(token) {
543
+ if (!token) return void 0;
544
+ const parts = token.split(".");
545
+ if (parts.length < 2) return void 0;
546
+ try {
547
+ const payload = parts[1].replace(/-/g, "+").replace(/_/g, "/");
548
+ const padded = payload + "===".slice((payload.length + 3) % 4);
549
+ if (typeof globalThis.atob !== "function") return void 0;
550
+ const json = globalThis.atob(padded);
551
+ const parsed = JSON.parse(json);
552
+ if (typeof parsed.exp !== "number") return void 0;
553
+ return parsed.exp * 1e3;
554
+ } catch {
555
+ return void 0;
556
+ }
557
+ }
558
+ var TelegramAuthProvider = class {
559
+ id = "telegram";
560
+ client;
561
+ getTelegramInitData;
562
+ mapUser;
563
+ session = null;
564
+ constructor(options) {
565
+ this.client = new ServiceClient(options);
566
+ this.getTelegramInitData = options.getTelegramInitData;
567
+ this.mapUser = options.mapUser;
568
+ }
569
+ onSessionChanged = (session) => {
570
+ this.session = session;
571
+ };
572
+ async login(params) {
573
+ const initData = params?.telegramInitData ?? this.getTelegramInitData?.() ?? globalThis?.Telegram?.WebApp?.initData;
574
+ if (!initData) throw new MissingTelegramInitDataError();
575
+ const result = await this.client.telegramLogin(initData);
576
+ const rawUser = result.user ?? {};
577
+ const user = this.mapUser ? this.mapUser(rawUser) : this.defaultMapUser(rawUser);
578
+ const session = {
579
+ user,
580
+ accessToken: result.accessToken,
581
+ refreshToken: result.refreshToken,
582
+ expiresAt: decodeJwtExpMs(result.accessToken)
583
+ };
584
+ this.session = session;
585
+ return { session };
586
+ }
587
+ async refresh() {
588
+ const currentSession = this.session;
589
+ const refreshToken = currentSession?.refreshToken;
590
+ if (!currentSession || !refreshToken) throw new MissingRefreshTokenError();
591
+ const result = await this.client.refresh(refreshToken);
592
+ const next = {
593
+ accessToken: result.accessToken,
594
+ refreshToken: result.refreshToken,
595
+ expiresAt: decodeJwtExpMs(result.accessToken)
596
+ };
597
+ this.session = { ...currentSession, ...next };
598
+ return next;
599
+ }
600
+ async logout() {
601
+ const refreshToken = this.session?.refreshToken;
602
+ if (refreshToken) {
603
+ try {
604
+ await this.client.logout(refreshToken);
605
+ } catch {
606
+ }
607
+ }
608
+ this.session = null;
609
+ }
610
+ defaultMapUser(raw) {
611
+ const id = raw.id ?? raw._id ?? raw.userId ?? raw.telegramId;
612
+ if (id === void 0 || id === null || String(id).trim() === "") {
613
+ throw new LooplaySDKError("Missing user id from auth response");
614
+ }
615
+ return {
616
+ id: String(id),
617
+ displayName: raw.displayName ?? raw.username ?? raw.name,
618
+ avatarUrl: raw.avatarUrl ?? raw.photoUrl ?? raw.avatar
619
+ };
620
+ }
621
+ };
622
+
623
+ exports.ApiClient = ApiClient;
624
+ exports.BrowserLocalStorageAuthStorage = BrowserLocalStorageAuthStorage;
625
+ exports.HttpClient = HttpClient;
626
+ exports.HttpError = HttpError;
627
+ exports.LooplayAuth = LooplayAuth;
628
+ exports.LooplaySDK = LooplaySDK;
629
+ exports.LooplaySDKError = LooplaySDKError;
630
+ exports.MemoryAuthStorage = MemoryAuthStorage;
631
+ exports.MissingAuthError = MissingAuthError;
632
+ exports.MissingBaseUrlError = MissingBaseUrlError;
633
+ exports.NotAuthenticatedError = NotAuthenticatedError;
634
+ exports.NotInitializedError = NotInitializedError;
635
+ exports.ServiceClient = ServiceClient;
636
+ exports.TelegramAuthProvider = TelegramAuthProvider;
637
+ //# sourceMappingURL=looplay-sdk.cjs.js.map
638
+ //# sourceMappingURL=looplay-sdk.cjs.js.map