@seaverse/dataservice 1.8.3 → 1.8.5
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 +3 -0
- package/dist/index.d.mts +58 -2
- package/dist/index.d.ts +58 -2
- package/dist/index.js +108 -8
- package/dist/index.mjs +107 -8
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -471,6 +471,9 @@ collection.insert(data: T): Promise<DataRecord<T>>
|
|
|
471
471
|
|
|
472
472
|
// Insert with custom UUID
|
|
473
473
|
collection.insert(data: T, id: string): Promise<DataRecord<T>>
|
|
474
|
+
|
|
475
|
+
// Upsert with deterministic per-user UUID derived from user_id + app_id + collection name
|
|
476
|
+
collection.upsert(data: T): Promise<DataRecord<T>>
|
|
474
477
|
```
|
|
475
478
|
|
|
476
479
|
#### Read
|
package/dist/index.d.mts
CHANGED
|
@@ -183,6 +183,13 @@ interface QueryBuilder<T = any> {
|
|
|
183
183
|
interface Collection<T = any> {
|
|
184
184
|
/** Insert a new record (optionally provide UUID) */
|
|
185
185
|
insert(data: T, id?: string): Promise<DataRecord<T>>;
|
|
186
|
+
/**
|
|
187
|
+
* Upsert a record using a stable per-user key.
|
|
188
|
+
*
|
|
189
|
+
* The SDK derives a deterministic UUID from the authenticated user_id,
|
|
190
|
+
* app_id, and collection name, then uses PostgREST on_conflict=id.
|
|
191
|
+
*/
|
|
192
|
+
upsert(data: T): Promise<DataRecord<T>>;
|
|
186
193
|
/** Get a single record by ID (alias for selectById) */
|
|
187
194
|
get(id: string): Promise<DataRecord<T> | null>;
|
|
188
195
|
/** Select records with optional filters */
|
|
@@ -239,6 +246,8 @@ interface DataTable {
|
|
|
239
246
|
interface DataServiceClient {
|
|
240
247
|
/** Automatically extracted application ID from current URL */
|
|
241
248
|
readonly appId: string;
|
|
249
|
+
/** User ID decoded from the JWT token when available */
|
|
250
|
+
readonly userId: string | null;
|
|
242
251
|
/** Access user private data table (user_data) */
|
|
243
252
|
readonly userData: DataTable;
|
|
244
253
|
/** Get user data statistics (RPC call) */
|
|
@@ -302,6 +311,53 @@ declare function debugSetToken(token: string): void;
|
|
|
302
311
|
* ```
|
|
303
312
|
*/
|
|
304
313
|
declare function setAppId(appId: string): void;
|
|
314
|
+
/**
|
|
315
|
+
* Create a new Data Service client
|
|
316
|
+
*
|
|
317
|
+
* The SDK automatically obtains the service host from the parent page via PostMessage (500ms timeout).
|
|
318
|
+
* If the fetch fails, it falls back to the default host: https://dataservice-api.seaverse.ai
|
|
319
|
+
*
|
|
320
|
+
* AI-friendly: Single entry point with clear configuration
|
|
321
|
+
*
|
|
322
|
+
* @param config - Client configuration (optional)
|
|
323
|
+
* @returns DataServiceClient instance
|
|
324
|
+
*
|
|
325
|
+
* @example
|
|
326
|
+
* ```typescript
|
|
327
|
+
* // Auto-fetch token and serviceHost from parent page (iframe)
|
|
328
|
+
* // ServiceHost fetch: 500ms timeout, falls back to https://dataservice-api.seaverse.ai
|
|
329
|
+
* const client = await createClient({});
|
|
330
|
+
*
|
|
331
|
+
* // For development/testing, use debugSetToken
|
|
332
|
+
* import { debugSetToken, createClient } from '@seaverse/dataservice';
|
|
333
|
+
* debugSetToken('your-test-token');
|
|
334
|
+
* const client = await createClient({});
|
|
335
|
+
*
|
|
336
|
+
* // appId is automatically extracted from current URL
|
|
337
|
+
* const order = await client.userData.collection('orders').insert({ ... });
|
|
338
|
+
* const orders = await client.userData.collection('orders').select().execute();
|
|
339
|
+
* ```
|
|
340
|
+
*/
|
|
341
|
+
/**
|
|
342
|
+
* Notify the parent (SeaVerse App or iframe) of the current game score.
|
|
343
|
+
*
|
|
344
|
+
* Fire-and-forget — no response is expected.
|
|
345
|
+
*
|
|
346
|
+
* Protocol:
|
|
347
|
+
* - RN WebView: window.ReactNativeWebView.postMessage({ type: 'seaverse:game_score', score: number })
|
|
348
|
+
* - iframe: window.parent.postMessage({ type: 'seaverse:game_score', score: number }, '*')
|
|
349
|
+
*
|
|
350
|
+
* @param score Current game score (must be a non-negative number)
|
|
351
|
+
*
|
|
352
|
+
* @example
|
|
353
|
+
* ```typescript
|
|
354
|
+
* import { notifyGameScore } from '@seaverse/dataservice';
|
|
355
|
+
*
|
|
356
|
+
* // Call whenever the score updates
|
|
357
|
+
* notifyGameScore(1234);
|
|
358
|
+
* ```
|
|
359
|
+
*/
|
|
360
|
+
declare function notifyGameScore(score: number): void;
|
|
305
361
|
declare function createClient(config?: ClientConfig): Promise<DataServiceClient>;
|
|
306
362
|
|
|
307
363
|
/**
|
|
@@ -350,6 +406,6 @@ declare function createClient(config?: ClientConfig): Promise<DataServiceClient>
|
|
|
350
406
|
* ```
|
|
351
407
|
*/
|
|
352
408
|
|
|
353
|
-
declare const VERSION = "1.8.
|
|
409
|
+
declare const VERSION = "1.8.4";
|
|
354
410
|
|
|
355
|
-
export { type APIError, type ClientConfig, type Collection, type DataRecord, type DataServiceClient, DataServiceError, type DataTable, type OrderOptions, type QueryBuilder, type QueryFilter, type UserDataStats, VERSION, createClient, debugSetToken, setAppId };
|
|
411
|
+
export { type APIError, type ClientConfig, type Collection, type DataRecord, type DataServiceClient, DataServiceError, type DataTable, type OrderOptions, type QueryBuilder, type QueryFilter, type UserDataStats, VERSION, createClient, debugSetToken, notifyGameScore, setAppId };
|
package/dist/index.d.ts
CHANGED
|
@@ -183,6 +183,13 @@ interface QueryBuilder<T = any> {
|
|
|
183
183
|
interface Collection<T = any> {
|
|
184
184
|
/** Insert a new record (optionally provide UUID) */
|
|
185
185
|
insert(data: T, id?: string): Promise<DataRecord<T>>;
|
|
186
|
+
/**
|
|
187
|
+
* Upsert a record using a stable per-user key.
|
|
188
|
+
*
|
|
189
|
+
* The SDK derives a deterministic UUID from the authenticated user_id,
|
|
190
|
+
* app_id, and collection name, then uses PostgREST on_conflict=id.
|
|
191
|
+
*/
|
|
192
|
+
upsert(data: T): Promise<DataRecord<T>>;
|
|
186
193
|
/** Get a single record by ID (alias for selectById) */
|
|
187
194
|
get(id: string): Promise<DataRecord<T> | null>;
|
|
188
195
|
/** Select records with optional filters */
|
|
@@ -239,6 +246,8 @@ interface DataTable {
|
|
|
239
246
|
interface DataServiceClient {
|
|
240
247
|
/** Automatically extracted application ID from current URL */
|
|
241
248
|
readonly appId: string;
|
|
249
|
+
/** User ID decoded from the JWT token when available */
|
|
250
|
+
readonly userId: string | null;
|
|
242
251
|
/** Access user private data table (user_data) */
|
|
243
252
|
readonly userData: DataTable;
|
|
244
253
|
/** Get user data statistics (RPC call) */
|
|
@@ -302,6 +311,53 @@ declare function debugSetToken(token: string): void;
|
|
|
302
311
|
* ```
|
|
303
312
|
*/
|
|
304
313
|
declare function setAppId(appId: string): void;
|
|
314
|
+
/**
|
|
315
|
+
* Create a new Data Service client
|
|
316
|
+
*
|
|
317
|
+
* The SDK automatically obtains the service host from the parent page via PostMessage (500ms timeout).
|
|
318
|
+
* If the fetch fails, it falls back to the default host: https://dataservice-api.seaverse.ai
|
|
319
|
+
*
|
|
320
|
+
* AI-friendly: Single entry point with clear configuration
|
|
321
|
+
*
|
|
322
|
+
* @param config - Client configuration (optional)
|
|
323
|
+
* @returns DataServiceClient instance
|
|
324
|
+
*
|
|
325
|
+
* @example
|
|
326
|
+
* ```typescript
|
|
327
|
+
* // Auto-fetch token and serviceHost from parent page (iframe)
|
|
328
|
+
* // ServiceHost fetch: 500ms timeout, falls back to https://dataservice-api.seaverse.ai
|
|
329
|
+
* const client = await createClient({});
|
|
330
|
+
*
|
|
331
|
+
* // For development/testing, use debugSetToken
|
|
332
|
+
* import { debugSetToken, createClient } from '@seaverse/dataservice';
|
|
333
|
+
* debugSetToken('your-test-token');
|
|
334
|
+
* const client = await createClient({});
|
|
335
|
+
*
|
|
336
|
+
* // appId is automatically extracted from current URL
|
|
337
|
+
* const order = await client.userData.collection('orders').insert({ ... });
|
|
338
|
+
* const orders = await client.userData.collection('orders').select().execute();
|
|
339
|
+
* ```
|
|
340
|
+
*/
|
|
341
|
+
/**
|
|
342
|
+
* Notify the parent (SeaVerse App or iframe) of the current game score.
|
|
343
|
+
*
|
|
344
|
+
* Fire-and-forget — no response is expected.
|
|
345
|
+
*
|
|
346
|
+
* Protocol:
|
|
347
|
+
* - RN WebView: window.ReactNativeWebView.postMessage({ type: 'seaverse:game_score', score: number })
|
|
348
|
+
* - iframe: window.parent.postMessage({ type: 'seaverse:game_score', score: number }, '*')
|
|
349
|
+
*
|
|
350
|
+
* @param score Current game score (must be a non-negative number)
|
|
351
|
+
*
|
|
352
|
+
* @example
|
|
353
|
+
* ```typescript
|
|
354
|
+
* import { notifyGameScore } from '@seaverse/dataservice';
|
|
355
|
+
*
|
|
356
|
+
* // Call whenever the score updates
|
|
357
|
+
* notifyGameScore(1234);
|
|
358
|
+
* ```
|
|
359
|
+
*/
|
|
360
|
+
declare function notifyGameScore(score: number): void;
|
|
305
361
|
declare function createClient(config?: ClientConfig): Promise<DataServiceClient>;
|
|
306
362
|
|
|
307
363
|
/**
|
|
@@ -350,6 +406,6 @@ declare function createClient(config?: ClientConfig): Promise<DataServiceClient>
|
|
|
350
406
|
* ```
|
|
351
407
|
*/
|
|
352
408
|
|
|
353
|
-
declare const VERSION = "1.8.
|
|
409
|
+
declare const VERSION = "1.8.4";
|
|
354
410
|
|
|
355
|
-
export { type APIError, type ClientConfig, type Collection, type DataRecord, type DataServiceClient, DataServiceError, type DataTable, type OrderOptions, type QueryBuilder, type QueryFilter, type UserDataStats, VERSION, createClient, debugSetToken, setAppId };
|
|
411
|
+
export { type APIError, type ClientConfig, type Collection, type DataRecord, type DataServiceClient, DataServiceError, type DataTable, type OrderOptions, type QueryBuilder, type QueryFilter, type UserDataStats, VERSION, createClient, debugSetToken, notifyGameScore, setAppId };
|
package/dist/index.js
CHANGED
|
@@ -24,6 +24,7 @@ __export(index_exports, {
|
|
|
24
24
|
VERSION: () => VERSION,
|
|
25
25
|
createClient: () => createClient,
|
|
26
26
|
debugSetToken: () => debugSetToken,
|
|
27
|
+
notifyGameScore: () => notifyGameScore,
|
|
27
28
|
setAppId: () => setAppId
|
|
28
29
|
});
|
|
29
30
|
module.exports = __toCommonJS(index_exports);
|
|
@@ -43,6 +44,64 @@ var DataServiceError = class extends Error {
|
|
|
43
44
|
// src/client.ts
|
|
44
45
|
var debugToken = null;
|
|
45
46
|
var manualAppId = null;
|
|
47
|
+
function decodeBase64Url(value) {
|
|
48
|
+
const normalized = value.replace(/-/g, "+").replace(/_/g, "/");
|
|
49
|
+
const padded = normalized.padEnd(Math.ceil(normalized.length / 4) * 4, "=");
|
|
50
|
+
const buffer = globalThis.Buffer;
|
|
51
|
+
if (buffer) {
|
|
52
|
+
return buffer.from(padded, "base64").toString("utf8");
|
|
53
|
+
}
|
|
54
|
+
const atobFn = globalThis.atob;
|
|
55
|
+
if (typeof atobFn === "function") {
|
|
56
|
+
const binary = atobFn(padded);
|
|
57
|
+
const bytes = Uint8Array.from(Array.from(binary, (char) => char.charCodeAt(0)));
|
|
58
|
+
const textDecoder = globalThis.TextDecoder;
|
|
59
|
+
if (typeof textDecoder === "function") {
|
|
60
|
+
return new textDecoder().decode(bytes);
|
|
61
|
+
}
|
|
62
|
+
return binary;
|
|
63
|
+
}
|
|
64
|
+
throw new DataServiceError("Unable to decode JWT payload", "JWT_DECODE_UNSUPPORTED");
|
|
65
|
+
}
|
|
66
|
+
function extractUserIdFromToken(token) {
|
|
67
|
+
if (!token) return null;
|
|
68
|
+
const rawToken = token.startsWith("Bearer ") ? token.slice("Bearer ".length) : token;
|
|
69
|
+
const parts = rawToken.split(".");
|
|
70
|
+
if (parts.length < 2) return null;
|
|
71
|
+
try {
|
|
72
|
+
const payload = JSON.parse(decodeBase64Url(parts[1]));
|
|
73
|
+
return payload?.payload?.user_id || payload?.user_id || payload?.sub || null;
|
|
74
|
+
} catch {
|
|
75
|
+
return null;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
function stableUuidFromParts(...parts) {
|
|
79
|
+
const input = parts.join("");
|
|
80
|
+
let h1 = 3735928559;
|
|
81
|
+
let h2 = 1103547991;
|
|
82
|
+
let h3 = 3235826430;
|
|
83
|
+
let h4 = 305419896;
|
|
84
|
+
for (let i = 0; i < input.length; i++) {
|
|
85
|
+
const ch = input.charCodeAt(i);
|
|
86
|
+
h1 = Math.imul(h1 ^ ch, 2654435761);
|
|
87
|
+
h2 = Math.imul(h2 ^ ch, 1597334677);
|
|
88
|
+
h3 = Math.imul(h3 ^ ch, 2246822507);
|
|
89
|
+
h4 = Math.imul(h4 ^ ch, 3266489909);
|
|
90
|
+
}
|
|
91
|
+
h1 = Math.imul(h1 ^ h1 >>> 16, 2246822507) ^ Math.imul(h2 ^ h2 >>> 13, 3266489909);
|
|
92
|
+
h2 = Math.imul(h2 ^ h2 >>> 16, 2246822507) ^ Math.imul(h3 ^ h3 >>> 13, 3266489909);
|
|
93
|
+
h3 = Math.imul(h3 ^ h3 >>> 16, 2246822507) ^ Math.imul(h4 ^ h4 >>> 13, 3266489909);
|
|
94
|
+
h4 = Math.imul(h4 ^ h4 >>> 16, 2246822507) ^ Math.imul(h1 ^ h1 >>> 13, 3266489909);
|
|
95
|
+
const bytes = new Uint8Array(16);
|
|
96
|
+
new DataView(bytes.buffer).setUint32(0, h1 >>> 0);
|
|
97
|
+
new DataView(bytes.buffer).setUint32(4, h2 >>> 0);
|
|
98
|
+
new DataView(bytes.buffer).setUint32(8, h3 >>> 0);
|
|
99
|
+
new DataView(bytes.buffer).setUint32(12, h4 >>> 0);
|
|
100
|
+
bytes[6] = bytes[6] & 15 | 80;
|
|
101
|
+
bytes[8] = bytes[8] & 63 | 128;
|
|
102
|
+
const hex = Array.from(bytes, (byte) => byte.toString(16).padStart(2, "0")).join("");
|
|
103
|
+
return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`;
|
|
104
|
+
}
|
|
46
105
|
function debugSetToken(token) {
|
|
47
106
|
debugToken = token;
|
|
48
107
|
}
|
|
@@ -399,11 +458,12 @@ var QueryBuilderImpl = class {
|
|
|
399
458
|
}
|
|
400
459
|
};
|
|
401
460
|
var CollectionImpl = class {
|
|
402
|
-
constructor(client, tablePath, appId, collectionName) {
|
|
461
|
+
constructor(client, tablePath, appId, collectionName, userId) {
|
|
403
462
|
this.client = client;
|
|
404
463
|
this.tablePath = tablePath;
|
|
405
464
|
this.appId = appId;
|
|
406
465
|
this.collectionName = collectionName;
|
|
466
|
+
this.userId = userId;
|
|
407
467
|
}
|
|
408
468
|
async insert(data, id) {
|
|
409
469
|
const payload = {
|
|
@@ -417,6 +477,27 @@ var CollectionImpl = class {
|
|
|
417
477
|
const response = await this.client.post(this.tablePath, payload);
|
|
418
478
|
return Array.isArray(response) ? response[0] : response;
|
|
419
479
|
}
|
|
480
|
+
async upsert(data) {
|
|
481
|
+
if (!this.userId) {
|
|
482
|
+
throw new DataServiceError(
|
|
483
|
+
"Unable to derive user_id from token for deterministic upsert",
|
|
484
|
+
"MISSING_USER_ID"
|
|
485
|
+
);
|
|
486
|
+
}
|
|
487
|
+
const id = stableUuidFromParts(this.userId, this.appId, this.collectionName);
|
|
488
|
+
const payload = {
|
|
489
|
+
id,
|
|
490
|
+
app_id: this.appId,
|
|
491
|
+
collection_name: this.collectionName,
|
|
492
|
+
data
|
|
493
|
+
};
|
|
494
|
+
const response = await this.client.post(
|
|
495
|
+
`${this.tablePath}?on_conflict=id`,
|
|
496
|
+
payload,
|
|
497
|
+
{ Prefer: "resolution=merge-duplicates,return=representation" }
|
|
498
|
+
);
|
|
499
|
+
return Array.isArray(response) ? response[0] : response;
|
|
500
|
+
}
|
|
420
501
|
async get(id) {
|
|
421
502
|
return this.selectById(id);
|
|
422
503
|
}
|
|
@@ -472,13 +553,14 @@ var CollectionImpl = class {
|
|
|
472
553
|
}
|
|
473
554
|
};
|
|
474
555
|
var DataTableImpl = class {
|
|
475
|
-
constructor(client, tablePath, appId) {
|
|
556
|
+
constructor(client, tablePath, appId, userId) {
|
|
476
557
|
this.client = client;
|
|
477
558
|
this.tablePath = tablePath;
|
|
478
559
|
this.appId = appId;
|
|
560
|
+
this.userId = userId;
|
|
479
561
|
}
|
|
480
562
|
collection(name) {
|
|
481
|
-
return new CollectionImpl(this.client, this.tablePath, this.appId, name);
|
|
563
|
+
return new CollectionImpl(this.client, this.tablePath, this.appId, name, this.userId);
|
|
482
564
|
}
|
|
483
565
|
async batchInsert(baseCollectionName, records) {
|
|
484
566
|
const results = [];
|
|
@@ -488,7 +570,8 @@ var DataTableImpl = class {
|
|
|
488
570
|
this.client,
|
|
489
571
|
this.tablePath,
|
|
490
572
|
this.appId,
|
|
491
|
-
uniqueCollectionName
|
|
573
|
+
uniqueCollectionName,
|
|
574
|
+
this.userId
|
|
492
575
|
);
|
|
493
576
|
const result = await collection.insert(records[i]);
|
|
494
577
|
results.push(result);
|
|
@@ -503,10 +586,12 @@ var DataServiceClientImpl = class {
|
|
|
503
586
|
client;
|
|
504
587
|
userData;
|
|
505
588
|
appId;
|
|
506
|
-
|
|
589
|
+
userId;
|
|
590
|
+
constructor(client, appId, userId) {
|
|
507
591
|
this.client = client;
|
|
508
592
|
this.appId = appId;
|
|
509
|
-
this.
|
|
593
|
+
this.userId = userId;
|
|
594
|
+
this.userData = new DataTableImpl(this.client, "/user_data", this.appId, userId);
|
|
510
595
|
}
|
|
511
596
|
async getStats() {
|
|
512
597
|
const response = await this.client.post("/rpc/get_user_data_stats");
|
|
@@ -516,6 +601,19 @@ var DataServiceClientImpl = class {
|
|
|
516
601
|
return this.client.post("/rpc/health");
|
|
517
602
|
}
|
|
518
603
|
};
|
|
604
|
+
function notifyGameScore(score) {
|
|
605
|
+
if (!isInIframe() && !isInSeaVerseApp()) return;
|
|
606
|
+
const payload = JSON.stringify({ type: "seaverse:game_score", score });
|
|
607
|
+
try {
|
|
608
|
+
if (isInSeaVerseApp()) {
|
|
609
|
+
globalThis.window.ReactNativeWebView.postMessage(payload);
|
|
610
|
+
} else {
|
|
611
|
+
globalThis.window.parent.postMessage({ type: "seaverse:game_score", score }, "*");
|
|
612
|
+
}
|
|
613
|
+
} catch (e) {
|
|
614
|
+
console.warn("[SeaVerse DataService SDK] Failed to notify game score:", e);
|
|
615
|
+
}
|
|
616
|
+
}
|
|
519
617
|
async function createClient(config = {}) {
|
|
520
618
|
let token = null;
|
|
521
619
|
let serviceHost = null;
|
|
@@ -547,16 +645,18 @@ async function createClient(config = {}) {
|
|
|
547
645
|
}
|
|
548
646
|
}
|
|
549
647
|
const appId = manualAppId || parentAppId || extractAppId();
|
|
550
|
-
|
|
648
|
+
const userId = extractUserIdFromToken(token);
|
|
649
|
+
return new DataServiceClientImpl(httpClient, appId, userId);
|
|
551
650
|
}
|
|
552
651
|
|
|
553
652
|
// src/index.ts
|
|
554
|
-
var VERSION = "1.8.
|
|
653
|
+
var VERSION = "1.8.4";
|
|
555
654
|
// Annotate the CommonJS export names for ESM import in node:
|
|
556
655
|
0 && (module.exports = {
|
|
557
656
|
DataServiceError,
|
|
558
657
|
VERSION,
|
|
559
658
|
createClient,
|
|
560
659
|
debugSetToken,
|
|
660
|
+
notifyGameScore,
|
|
561
661
|
setAppId
|
|
562
662
|
});
|
package/dist/index.mjs
CHANGED
|
@@ -13,6 +13,64 @@ var DataServiceError = class extends Error {
|
|
|
13
13
|
// src/client.ts
|
|
14
14
|
var debugToken = null;
|
|
15
15
|
var manualAppId = null;
|
|
16
|
+
function decodeBase64Url(value) {
|
|
17
|
+
const normalized = value.replace(/-/g, "+").replace(/_/g, "/");
|
|
18
|
+
const padded = normalized.padEnd(Math.ceil(normalized.length / 4) * 4, "=");
|
|
19
|
+
const buffer = globalThis.Buffer;
|
|
20
|
+
if (buffer) {
|
|
21
|
+
return buffer.from(padded, "base64").toString("utf8");
|
|
22
|
+
}
|
|
23
|
+
const atobFn = globalThis.atob;
|
|
24
|
+
if (typeof atobFn === "function") {
|
|
25
|
+
const binary = atobFn(padded);
|
|
26
|
+
const bytes = Uint8Array.from(Array.from(binary, (char) => char.charCodeAt(0)));
|
|
27
|
+
const textDecoder = globalThis.TextDecoder;
|
|
28
|
+
if (typeof textDecoder === "function") {
|
|
29
|
+
return new textDecoder().decode(bytes);
|
|
30
|
+
}
|
|
31
|
+
return binary;
|
|
32
|
+
}
|
|
33
|
+
throw new DataServiceError("Unable to decode JWT payload", "JWT_DECODE_UNSUPPORTED");
|
|
34
|
+
}
|
|
35
|
+
function extractUserIdFromToken(token) {
|
|
36
|
+
if (!token) return null;
|
|
37
|
+
const rawToken = token.startsWith("Bearer ") ? token.slice("Bearer ".length) : token;
|
|
38
|
+
const parts = rawToken.split(".");
|
|
39
|
+
if (parts.length < 2) return null;
|
|
40
|
+
try {
|
|
41
|
+
const payload = JSON.parse(decodeBase64Url(parts[1]));
|
|
42
|
+
return payload?.payload?.user_id || payload?.user_id || payload?.sub || null;
|
|
43
|
+
} catch {
|
|
44
|
+
return null;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
function stableUuidFromParts(...parts) {
|
|
48
|
+
const input = parts.join("");
|
|
49
|
+
let h1 = 3735928559;
|
|
50
|
+
let h2 = 1103547991;
|
|
51
|
+
let h3 = 3235826430;
|
|
52
|
+
let h4 = 305419896;
|
|
53
|
+
for (let i = 0; i < input.length; i++) {
|
|
54
|
+
const ch = input.charCodeAt(i);
|
|
55
|
+
h1 = Math.imul(h1 ^ ch, 2654435761);
|
|
56
|
+
h2 = Math.imul(h2 ^ ch, 1597334677);
|
|
57
|
+
h3 = Math.imul(h3 ^ ch, 2246822507);
|
|
58
|
+
h4 = Math.imul(h4 ^ ch, 3266489909);
|
|
59
|
+
}
|
|
60
|
+
h1 = Math.imul(h1 ^ h1 >>> 16, 2246822507) ^ Math.imul(h2 ^ h2 >>> 13, 3266489909);
|
|
61
|
+
h2 = Math.imul(h2 ^ h2 >>> 16, 2246822507) ^ Math.imul(h3 ^ h3 >>> 13, 3266489909);
|
|
62
|
+
h3 = Math.imul(h3 ^ h3 >>> 16, 2246822507) ^ Math.imul(h4 ^ h4 >>> 13, 3266489909);
|
|
63
|
+
h4 = Math.imul(h4 ^ h4 >>> 16, 2246822507) ^ Math.imul(h1 ^ h1 >>> 13, 3266489909);
|
|
64
|
+
const bytes = new Uint8Array(16);
|
|
65
|
+
new DataView(bytes.buffer).setUint32(0, h1 >>> 0);
|
|
66
|
+
new DataView(bytes.buffer).setUint32(4, h2 >>> 0);
|
|
67
|
+
new DataView(bytes.buffer).setUint32(8, h3 >>> 0);
|
|
68
|
+
new DataView(bytes.buffer).setUint32(12, h4 >>> 0);
|
|
69
|
+
bytes[6] = bytes[6] & 15 | 80;
|
|
70
|
+
bytes[8] = bytes[8] & 63 | 128;
|
|
71
|
+
const hex = Array.from(bytes, (byte) => byte.toString(16).padStart(2, "0")).join("");
|
|
72
|
+
return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`;
|
|
73
|
+
}
|
|
16
74
|
function debugSetToken(token) {
|
|
17
75
|
debugToken = token;
|
|
18
76
|
}
|
|
@@ -369,11 +427,12 @@ var QueryBuilderImpl = class {
|
|
|
369
427
|
}
|
|
370
428
|
};
|
|
371
429
|
var CollectionImpl = class {
|
|
372
|
-
constructor(client, tablePath, appId, collectionName) {
|
|
430
|
+
constructor(client, tablePath, appId, collectionName, userId) {
|
|
373
431
|
this.client = client;
|
|
374
432
|
this.tablePath = tablePath;
|
|
375
433
|
this.appId = appId;
|
|
376
434
|
this.collectionName = collectionName;
|
|
435
|
+
this.userId = userId;
|
|
377
436
|
}
|
|
378
437
|
async insert(data, id) {
|
|
379
438
|
const payload = {
|
|
@@ -387,6 +446,27 @@ var CollectionImpl = class {
|
|
|
387
446
|
const response = await this.client.post(this.tablePath, payload);
|
|
388
447
|
return Array.isArray(response) ? response[0] : response;
|
|
389
448
|
}
|
|
449
|
+
async upsert(data) {
|
|
450
|
+
if (!this.userId) {
|
|
451
|
+
throw new DataServiceError(
|
|
452
|
+
"Unable to derive user_id from token for deterministic upsert",
|
|
453
|
+
"MISSING_USER_ID"
|
|
454
|
+
);
|
|
455
|
+
}
|
|
456
|
+
const id = stableUuidFromParts(this.userId, this.appId, this.collectionName);
|
|
457
|
+
const payload = {
|
|
458
|
+
id,
|
|
459
|
+
app_id: this.appId,
|
|
460
|
+
collection_name: this.collectionName,
|
|
461
|
+
data
|
|
462
|
+
};
|
|
463
|
+
const response = await this.client.post(
|
|
464
|
+
`${this.tablePath}?on_conflict=id`,
|
|
465
|
+
payload,
|
|
466
|
+
{ Prefer: "resolution=merge-duplicates,return=representation" }
|
|
467
|
+
);
|
|
468
|
+
return Array.isArray(response) ? response[0] : response;
|
|
469
|
+
}
|
|
390
470
|
async get(id) {
|
|
391
471
|
return this.selectById(id);
|
|
392
472
|
}
|
|
@@ -442,13 +522,14 @@ var CollectionImpl = class {
|
|
|
442
522
|
}
|
|
443
523
|
};
|
|
444
524
|
var DataTableImpl = class {
|
|
445
|
-
constructor(client, tablePath, appId) {
|
|
525
|
+
constructor(client, tablePath, appId, userId) {
|
|
446
526
|
this.client = client;
|
|
447
527
|
this.tablePath = tablePath;
|
|
448
528
|
this.appId = appId;
|
|
529
|
+
this.userId = userId;
|
|
449
530
|
}
|
|
450
531
|
collection(name) {
|
|
451
|
-
return new CollectionImpl(this.client, this.tablePath, this.appId, name);
|
|
532
|
+
return new CollectionImpl(this.client, this.tablePath, this.appId, name, this.userId);
|
|
452
533
|
}
|
|
453
534
|
async batchInsert(baseCollectionName, records) {
|
|
454
535
|
const results = [];
|
|
@@ -458,7 +539,8 @@ var DataTableImpl = class {
|
|
|
458
539
|
this.client,
|
|
459
540
|
this.tablePath,
|
|
460
541
|
this.appId,
|
|
461
|
-
uniqueCollectionName
|
|
542
|
+
uniqueCollectionName,
|
|
543
|
+
this.userId
|
|
462
544
|
);
|
|
463
545
|
const result = await collection.insert(records[i]);
|
|
464
546
|
results.push(result);
|
|
@@ -473,10 +555,12 @@ var DataServiceClientImpl = class {
|
|
|
473
555
|
client;
|
|
474
556
|
userData;
|
|
475
557
|
appId;
|
|
476
|
-
|
|
558
|
+
userId;
|
|
559
|
+
constructor(client, appId, userId) {
|
|
477
560
|
this.client = client;
|
|
478
561
|
this.appId = appId;
|
|
479
|
-
this.
|
|
562
|
+
this.userId = userId;
|
|
563
|
+
this.userData = new DataTableImpl(this.client, "/user_data", this.appId, userId);
|
|
480
564
|
}
|
|
481
565
|
async getStats() {
|
|
482
566
|
const response = await this.client.post("/rpc/get_user_data_stats");
|
|
@@ -486,6 +570,19 @@ var DataServiceClientImpl = class {
|
|
|
486
570
|
return this.client.post("/rpc/health");
|
|
487
571
|
}
|
|
488
572
|
};
|
|
573
|
+
function notifyGameScore(score) {
|
|
574
|
+
if (!isInIframe() && !isInSeaVerseApp()) return;
|
|
575
|
+
const payload = JSON.stringify({ type: "seaverse:game_score", score });
|
|
576
|
+
try {
|
|
577
|
+
if (isInSeaVerseApp()) {
|
|
578
|
+
globalThis.window.ReactNativeWebView.postMessage(payload);
|
|
579
|
+
} else {
|
|
580
|
+
globalThis.window.parent.postMessage({ type: "seaverse:game_score", score }, "*");
|
|
581
|
+
}
|
|
582
|
+
} catch (e) {
|
|
583
|
+
console.warn("[SeaVerse DataService SDK] Failed to notify game score:", e);
|
|
584
|
+
}
|
|
585
|
+
}
|
|
489
586
|
async function createClient(config = {}) {
|
|
490
587
|
let token = null;
|
|
491
588
|
let serviceHost = null;
|
|
@@ -517,15 +614,17 @@ async function createClient(config = {}) {
|
|
|
517
614
|
}
|
|
518
615
|
}
|
|
519
616
|
const appId = manualAppId || parentAppId || extractAppId();
|
|
520
|
-
|
|
617
|
+
const userId = extractUserIdFromToken(token);
|
|
618
|
+
return new DataServiceClientImpl(httpClient, appId, userId);
|
|
521
619
|
}
|
|
522
620
|
|
|
523
621
|
// src/index.ts
|
|
524
|
-
var VERSION = "1.8.
|
|
622
|
+
var VERSION = "1.8.4";
|
|
525
623
|
export {
|
|
526
624
|
DataServiceError,
|
|
527
625
|
VERSION,
|
|
528
626
|
createClient,
|
|
529
627
|
debugSetToken,
|
|
628
|
+
notifyGameScore,
|
|
530
629
|
setAppId
|
|
531
630
|
};
|