facebetter 1.4.0 → 1.4.2

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.
@@ -218,6 +218,158 @@
218
218
  VirtualBackgroundOptions: VirtualBackgroundOptions$1
219
219
  });
220
220
 
221
+ /**
222
+ * Browser localStorage cache for online license JSON (WASM path).
223
+ * Aligns with native: full server response body, TTL from response.timestamp (seconds).
224
+ */
225
+
226
+ /** @type {number} Same semantics as C++ kOnlineLicenseCacheTtlSecs */
227
+ const ONLINE_LICENSE_CACHE_TTL_SEC = 7 * 24 * 3600;
228
+
229
+ const STORAGE_PREFIX = 'facebetter.onlineLicense.v1:';
230
+
231
+ /**
232
+ * @returns {boolean}
233
+ */
234
+ function isLocalStorageAvailable() {
235
+ try {
236
+ if (typeof globalThis.localStorage === 'undefined') {
237
+ return false;
238
+ }
239
+ const k = '__fb_ls_test__';
240
+ globalThis.localStorage.setItem(k, '1');
241
+ globalThis.localStorage.removeItem(k);
242
+ return true;
243
+ } catch {
244
+ return false;
245
+ }
246
+ }
247
+
248
+ /**
249
+ * @param {string} appId
250
+ * @returns {string}
251
+ */
252
+ function storageKey(appId) {
253
+ return `${STORAGE_PREFIX}${appId}`;
254
+ }
255
+
256
+ /**
257
+ * @param {string} responseText
258
+ * @param {string} expectedAppId
259
+ * @returns {{ ok: boolean, ts: number } | null}
260
+ */
261
+ function parseAndValidateResponse(responseText, expectedAppId) {
262
+ try {
263
+ const o = JSON.parse(responseText);
264
+ if (!o || typeof o !== 'object') {
265
+ return null;
266
+ }
267
+ if (o.success !== true) {
268
+ return null;
269
+ }
270
+ if (typeof o.app_id !== 'string' || o.app_id !== expectedAppId) {
271
+ return null;
272
+ }
273
+ if (typeof o.timestamp !== 'number' || !Number.isFinite(o.timestamp)) {
274
+ return null;
275
+ }
276
+ return { ok: true, ts: o.timestamp };
277
+ } catch {
278
+ return null;
279
+ }
280
+ }
281
+
282
+ /**
283
+ * Returns cached raw response body if still within TTL, else null.
284
+ * @param {string} appId
285
+ * @returns {string | null}
286
+ */
287
+ function readOnlineLicenseCache(appId) {
288
+ if (!appId || !isLocalStorageAvailable()) {
289
+ return null;
290
+ }
291
+ let raw;
292
+ try {
293
+ raw = globalThis.localStorage.getItem(storageKey(appId));
294
+ } catch {
295
+ return null;
296
+ }
297
+ if (!raw || typeof raw !== 'string') {
298
+ return null;
299
+ }
300
+ const meta = parseAndValidateResponse(raw, appId);
301
+ if (!meta) {
302
+ return null;
303
+ }
304
+ const nowSec = Math.floor(Date.now() / 1000);
305
+ const ts = meta.ts;
306
+ const ageSec = ts <= nowSec ? nowSec - ts : 0;
307
+ if (ageSec > ONLINE_LICENSE_CACHE_TTL_SEC) {
308
+ return null;
309
+ }
310
+ return raw;
311
+ }
312
+
313
+ /**
314
+ * Persists successful auth response; no-op if storage unavailable or payload invalid.
315
+ * @param {string} appId
316
+ * @param {string} responseText
317
+ * @returns {boolean}
318
+ */
319
+ function writeOnlineLicenseCache(appId, responseText) {
320
+ if (!appId || !isLocalStorageAvailable() || typeof responseText !== 'string') {
321
+ return false;
322
+ }
323
+ if (!parseAndValidateResponse(responseText, appId)) {
324
+ return false;
325
+ }
326
+ try {
327
+ globalThis.localStorage.setItem(storageKey(appId), responseText);
328
+ return true;
329
+ } catch {
330
+ return false;
331
+ }
332
+ }
333
+
334
+ /**
335
+ * Browser-side console lines aligned with C++ Logger (src/base/logging.cc) Release pattern:
336
+ * [%Y-%m-%d %H:%M:%S.%e] [facebetter] [%l] [%t] - %v
337
+ * spdlog level names: info, warning, error (see spdlog SPDLOG_LEVEL_NAMES).
338
+ */
339
+
340
+ function pad2(n) {
341
+ return String(n).padStart(2, '0');
342
+ }
343
+
344
+ /**
345
+ * @param {'info'|'warning'|'error'} levelTag — must match spdlog long level strings
346
+ * @param {string} message
347
+ * @returns {string}
348
+ */
349
+ function formatLogLine(levelTag, message) {
350
+ const d = new Date();
351
+ const ms = String(d.getMilliseconds()).padStart(3, '0');
352
+ const ts = `${d.getFullYear()}-${pad2(d.getMonth() + 1)}-${pad2(d.getDate())} ${pad2(
353
+ d.getHours()
354
+ )}:${pad2(d.getMinutes())}:${pad2(d.getSeconds())}.${ms}`;
355
+ return `[${ts}] [facebetter] [${levelTag}] [0] - ${message}`;
356
+ }
357
+
358
+ /** @param {string} message */
359
+ function logInfo(message) {
360
+ console.info(formatLogLine('info', message));
361
+ }
362
+
363
+ /** @param {string} message */
364
+ function logWarn(message) {
365
+ console.warn(formatLogLine('warning', message));
366
+ }
367
+
368
+ /** @param {string} message */
369
+ function logError(message) {
370
+ console.error(formatLogLine('error', message));
371
+ }
372
+
221
373
  /**
222
374
  * Beauty Effect Engine Core
223
375
  * Platform-agnostic engine implementation with dependency injection
@@ -409,26 +561,71 @@
409
561
  };
410
562
  }
411
563
 
412
- // Setup online auth proxy for WASM
564
+ // Setup online auth proxy for WASM (localStorage cache + fetch, same TTL as native).
565
+ // 返回 { response, fromCache } 供 WASM 桥区分:仅 fromCache===true 时 C++ 跳过 5 分钟防重放。
413
566
  if (!Module.onOnlineAuth) {
414
567
  Module.onOnlineAuth = async (payloadJson) => {
568
+ let appId = '';
415
569
  try {
416
- const url = 'https://facebetter.pixpark.net/facebetter/v1/auth';
417
- const response = await fetch(url, {
570
+ const p = JSON.parse(payloadJson);
571
+ if (p && typeof p.app_id === 'string') {
572
+ appId = p.app_id;
573
+ }
574
+ } catch {
575
+ // ignore malformed payload
576
+ }
577
+
578
+ const authResult = (body, fromCache) =>
579
+ body ? { response: body, fromCache } : '';
580
+
581
+ const cached = appId ? readOnlineLicenseCache(appId) : null;
582
+ if (cached) {
583
+ // 与 C++ 中「disk cache」对应;WASM 侧日志仍显示 online,以这里为准区分是否发 HTTP
584
+ logInfo(
585
+ 'Online license: localStorage cache hit (no auth HTTP)'
586
+ );
587
+ return authResult(cached, true);
588
+ }
589
+
590
+ const authUrl = 'https://facebetter.pixpark.net/facebetter/v1/auth';
591
+ logInfo('Online license: requesting auth server');
592
+ try {
593
+ const response = await fetch(authUrl, {
418
594
  method: 'POST',
419
595
  headers: {
420
- 'Content-Type': 'application/json'
596
+ 'Content-Type': 'application/json',
421
597
  },
422
- body: payloadJson
598
+ body: payloadJson,
423
599
  });
424
600
  if (!response.ok) {
425
- console.warn('[JS Engine] Online auth fetch failed with status:', response.status);
426
- return "";
601
+ const fallback = appId ? readOnlineLicenseCache(appId) : null;
602
+ if (fallback) {
603
+ logInfo(
604
+ `Online license: using cache after HTTP error (status ${response.status})`
605
+ );
606
+ } else {
607
+ logWarn(
608
+ `Online license: HTTP ${response.status}, no cache`
609
+ );
610
+ }
611
+ return authResult(fallback, true);
427
612
  }
428
- return await response.text();
429
- } catch (error) {
430
- console.error('[JS Engine] Online auth fetch exception:', error);
431
- return "";
613
+ const text = await response.text();
614
+ if (appId && text) {
615
+ writeOnlineLicenseCache(appId, text);
616
+ }
617
+ logInfo(
618
+ 'Online license: server response ok, cache updated'
619
+ );
620
+ return authResult(text, false);
621
+ } catch {
622
+ const fallback = appId ? readOnlineLicenseCache(appId) : null;
623
+ if (fallback) {
624
+ logInfo(
625
+ 'Online license: using cache after network error'
626
+ );
627
+ }
628
+ return authResult(fallback, true);
432
629
  }
433
630
  };
434
631
  }
@@ -861,7 +1058,7 @@
861
1058
  try {
862
1059
  return JSON.parse(jsonStr || '[]');
863
1060
  } catch (e) {
864
- console.error('Failed to parse registered filters JSON:', e);
1061
+ logError(`Failed to parse registered filters JSON: ${e}`);
865
1062
  return [];
866
1063
  }
867
1064
  }
@@ -882,7 +1079,7 @@
882
1079
  try {
883
1080
  return JSON.parse(jsonStr || '[]');
884
1081
  } catch (e) {
885
- console.error('Failed to parse registered stickers JSON:', e);
1082
+ logError(`Failed to parse registered stickers JSON: ${e}`);
886
1083
  return [];
887
1084
  }
888
1085
  }