facebetter 2.0.0 → 2.0.1
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 +33 -26
- package/dist/facebetter.esm.js +260 -270
- package/dist/facebetter.esm.js.map +1 -1
- package/dist/facebetter.js +260 -261
- package/dist/facebetter.js.map +1 -1
- package/package.json +2 -2
package/dist/facebetter.js
CHANGED
|
@@ -11,58 +11,33 @@
|
|
|
11
11
|
|
|
12
12
|
/**
|
|
13
13
|
* Engine configuration class
|
|
14
|
-
* Analogous to EngineConfig in C++ / Java / Objective-C
|
|
14
|
+
* Analogous to EngineConfig in C++ / Java / Objective-C. Web only accepts licenseToken.
|
|
15
15
|
*/
|
|
16
16
|
class EngineConfig {
|
|
17
17
|
/**
|
|
18
18
|
* @param {Object} config
|
|
19
|
-
* @param {string}
|
|
19
|
+
* @param {string} config.licenseToken Compact JWS, or the raw `{success, token}`
|
|
20
20
|
* HTTP response body from your auth server.
|
|
21
|
-
* @param {string} [config.authProxyUrl] Production: forward the WASM auth
|
|
22
|
-
* challenge to your server, which holds app_id/app_key and calls Cloudflare.
|
|
23
|
-
* @param {Function} [config.fetchAuthResponse] Custom auth hook with signature
|
|
24
|
-
* `async (challenge) => string|object`. For local debugging you can use
|
|
25
|
-
* `createDirectAuthFetcher` to hit Cloudflare directly — never ship keys in
|
|
26
|
-
* a production frontend bundle.
|
|
27
21
|
* @param {boolean} [config.externalContext] Kept for cross-platform alignment;
|
|
28
|
-
* unused on Web
|
|
22
|
+
* unused on Web today.
|
|
29
23
|
*/
|
|
30
24
|
constructor(config = {}) {
|
|
31
25
|
this.licenseToken = config.licenseToken || null;
|
|
32
|
-
this.authProxyUrl = config.authProxyUrl || null;
|
|
33
|
-
this.fetchAuthResponse = config.fetchAuthResponse || null;
|
|
34
26
|
this.resourcePath = '/resource.fbd';
|
|
35
27
|
/**
|
|
36
28
|
* Whether to use an external GL context (native platforms only).
|
|
37
|
-
* On Web
|
|
29
|
+
* On Web this field is kept for config-shape alignment and has no effect.
|
|
38
30
|
*/
|
|
39
31
|
this.externalContext = !!config.externalContext;
|
|
40
32
|
}
|
|
41
33
|
|
|
42
34
|
isValid() {
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
return true;
|
|
46
|
-
}
|
|
47
|
-
if (typeof this.fetchAuthResponse === 'function') {
|
|
48
|
-
return true;
|
|
49
|
-
}
|
|
50
|
-
if (this.authProxyUrl && typeof this.authProxyUrl === 'string' &&
|
|
51
|
-
this.authProxyUrl.trim() !== '') {
|
|
52
|
-
return true;
|
|
53
|
-
}
|
|
54
|
-
return false;
|
|
35
|
+
return typeof this.licenseToken === 'string' &&
|
|
36
|
+
this.licenseToken.trim() !== '';
|
|
55
37
|
}
|
|
56
38
|
|
|
57
39
|
toString() {
|
|
58
|
-
|
|
59
|
-
? 'licenseToken'
|
|
60
|
-
: this.fetchAuthResponse
|
|
61
|
-
? 'fetchAuthResponse'
|
|
62
|
-
: this.authProxyUrl
|
|
63
|
-
? 'authProxyUrl'
|
|
64
|
-
: 'invalid';
|
|
65
|
-
return `EngineConfig{mode='${mode}'}`;
|
|
40
|
+
return `EngineConfig{mode='${this.isValid() ? 'licenseToken' : 'invalid'}'}`;
|
|
66
41
|
}
|
|
67
42
|
}
|
|
68
43
|
|
|
@@ -312,40 +287,6 @@
|
|
|
312
287
|
WhiteningStyle: WhiteningStyle$1
|
|
313
288
|
});
|
|
314
289
|
|
|
315
|
-
/**
|
|
316
|
-
* Browser-side console lines aligned with C++ Logger (src/base/logging.cc) Release pattern:
|
|
317
|
-
* [%Y-%m-%d %H:%M:%S.%e] [facebetter] [%l] [%t] - %v
|
|
318
|
-
* spdlog level names: info, warning, error (see spdlog SPDLOG_LEVEL_NAMES).
|
|
319
|
-
*/
|
|
320
|
-
|
|
321
|
-
function pad2(n) {
|
|
322
|
-
return String(n).padStart(2, '0');
|
|
323
|
-
}
|
|
324
|
-
|
|
325
|
-
/**
|
|
326
|
-
* @param {'info'|'warning'|'error'} levelTag — must match spdlog long level strings
|
|
327
|
-
* @param {string} message
|
|
328
|
-
* @returns {string}
|
|
329
|
-
*/
|
|
330
|
-
function formatLogLine(levelTag, message) {
|
|
331
|
-
const d = new Date();
|
|
332
|
-
const ms = String(d.getMilliseconds()).padStart(3, '0');
|
|
333
|
-
const ts = `${d.getFullYear()}-${pad2(d.getMonth() + 1)}-${pad2(d.getDate())} ${pad2(
|
|
334
|
-
d.getHours()
|
|
335
|
-
)}:${pad2(d.getMinutes())}:${pad2(d.getSeconds())}.${ms}`;
|
|
336
|
-
return `[${ts}] [facebetter] [${levelTag}] [0] - ${message}`;
|
|
337
|
-
}
|
|
338
|
-
|
|
339
|
-
/** @param {string} message */
|
|
340
|
-
function logInfo(message) {
|
|
341
|
-
console.info(formatLogLine('info', message));
|
|
342
|
-
}
|
|
343
|
-
|
|
344
|
-
/** @param {string} message */
|
|
345
|
-
function logWarn(message) {
|
|
346
|
-
console.warn(formatLogLine('warning', message));
|
|
347
|
-
}
|
|
348
|
-
|
|
349
290
|
/**
|
|
350
291
|
* Beauty Effect Engine Core
|
|
351
292
|
* Platform-agnostic engine implementation with dependency injection
|
|
@@ -410,8 +351,6 @@
|
|
|
410
351
|
|
|
411
352
|
this.config = engineConfig;
|
|
412
353
|
this.licenseToken = engineConfig.licenseToken;
|
|
413
|
-
this.authProxyUrl = engineConfig.authProxyUrl;
|
|
414
|
-
this.fetchAuthResponse = engineConfig.fetchAuthResponse;
|
|
415
354
|
this.resourcePath = '/resource.fbd';
|
|
416
355
|
this.enginePtr = null;
|
|
417
356
|
this.initialized = false;
|
|
@@ -431,7 +370,8 @@
|
|
|
431
370
|
this._toImageData = platformAPI.toImageData;
|
|
432
371
|
this._ensureFacebetterCanvas = platformAPI.ensureFacebetterCanvas;
|
|
433
372
|
this._cleanupFacebetterCanvas = platformAPI.cleanupFacebetterCanvas;
|
|
434
|
-
|
|
373
|
+
this._pendingLogConfig = null;
|
|
374
|
+
|
|
435
375
|
// Browser-specific state
|
|
436
376
|
this._facebetterCanvas = null;
|
|
437
377
|
this._createdFacebetterCanvas = false;
|
|
@@ -442,9 +382,8 @@
|
|
|
442
382
|
if (this._ensureFacebetterCanvas) {
|
|
443
383
|
this._ensureFacebetterCanvas();
|
|
444
384
|
}
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
this._wasmLoadPromise = this._loadWasmModule();
|
|
385
|
+
|
|
386
|
+
this._wasmLoadPromise = null;
|
|
448
387
|
this._initPromise = null;
|
|
449
388
|
}
|
|
450
389
|
|
|
@@ -501,8 +440,12 @@
|
|
|
501
440
|
/**
|
|
502
441
|
* Initializes the engine
|
|
503
442
|
* @param {Object} [options] - Initialization options
|
|
504
|
-
* @param {number} [options.timeout] - Timeout in milliseconds (default:
|
|
505
|
-
* @param {number} [options.authTimeout] -
|
|
443
|
+
* @param {number} [options.timeout] - Timeout in milliseconds (default: 120000)
|
|
444
|
+
* @param {number} [options.authTimeout] - Unused. Kept for compatibility.
|
|
445
|
+
* @param {function} [options.onProgress] - Download progress:
|
|
446
|
+
* `{ loaded, total, percent }` while fetching the runtime files.
|
|
447
|
+
* @param {string} [options.assetBaseUrl] - Directory that contains
|
|
448
|
+
* `facebetter-core.wasm` and `resource.fbd`. Optional when using npm.
|
|
506
449
|
* @returns {Promise<void>} Promise that resolves when initialization is complete
|
|
507
450
|
*/
|
|
508
451
|
async init(options = {}) {
|
|
@@ -519,9 +462,18 @@
|
|
|
519
462
|
// Create initialization Promise
|
|
520
463
|
this._initPromise = (async () => {
|
|
521
464
|
try {
|
|
522
|
-
const wasmTimeout = options.timeout ||
|
|
465
|
+
const wasmTimeout = options.timeout || 120000;
|
|
466
|
+
|
|
467
|
+
if (!this._wasmLoadPromise) {
|
|
468
|
+
this._wasmLoadPromise = this._loadWasmModule({
|
|
469
|
+
onProgress: options.onProgress,
|
|
470
|
+
assetBaseUrl: options.assetBaseUrl,
|
|
471
|
+
wasmUrl: options.wasmUrl,
|
|
472
|
+
wasmAssetUrl: options.wasmAssetUrl,
|
|
473
|
+
resourceAssetUrl: options.resourceAssetUrl,
|
|
474
|
+
});
|
|
475
|
+
}
|
|
523
476
|
|
|
524
|
-
// Wait for WASM module loading (with timeout)
|
|
525
477
|
try {
|
|
526
478
|
await Promise.race([
|
|
527
479
|
this._wasmLoadPromise,
|
|
@@ -531,6 +483,11 @@
|
|
|
531
483
|
throw this._enhanceError(error, 'Failed to load WASM module');
|
|
532
484
|
}
|
|
533
485
|
|
|
486
|
+
if (this._pendingLogConfig) {
|
|
487
|
+
await this._applyLogConfig(this._pendingLogConfig);
|
|
488
|
+
this._pendingLogConfig = null;
|
|
489
|
+
}
|
|
490
|
+
|
|
534
491
|
const Module = this._getWasmModule();
|
|
535
492
|
|
|
536
493
|
// Setup usage report proxy for WASM
|
|
@@ -556,66 +513,12 @@
|
|
|
556
513
|
};
|
|
557
514
|
}
|
|
558
515
|
|
|
559
|
-
// Web
|
|
560
|
-
// - licenseToken: pre-fetched JWS or `{success, token}` body
|
|
561
|
-
// - authProxyUrl: production — forward the challenge to your server
|
|
562
|
-
// - fetchAuthResponse: custom hook; locally use createDirectAuthFetcher
|
|
516
|
+
// Web: fetch the token outside the engine, then pass licenseToken.
|
|
563
517
|
if (!this.licenseToken) {
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
const result = await this.fetchAuthResponse(challenge);
|
|
569
|
-
if (result && typeof result === 'object' && typeof result.response === 'string') {
|
|
570
|
-
return { response: result.response };
|
|
571
|
-
}
|
|
572
|
-
if (typeof result === 'string') {
|
|
573
|
-
return result;
|
|
574
|
-
}
|
|
575
|
-
if (result && typeof result === 'object') {
|
|
576
|
-
return JSON.stringify(result);
|
|
577
|
-
}
|
|
578
|
-
return '';
|
|
579
|
-
} catch (err) {
|
|
580
|
-
logWarn(
|
|
581
|
-
`Online license: fetchAuthResponse error${err?.message ? `: ${err.message}` : ''}`
|
|
582
|
-
);
|
|
583
|
-
return JSON.stringify({
|
|
584
|
-
success: false,
|
|
585
|
-
error: 'auth_fetch_failed',
|
|
586
|
-
message: err?.message || 'auth fetch failed',
|
|
587
|
-
});
|
|
588
|
-
}
|
|
589
|
-
};
|
|
590
|
-
} else if (this.authProxyUrl) {
|
|
591
|
-
Module.onOnlineAuth = async (payloadJson) => {
|
|
592
|
-
logInfo('Online license: proxying auth challenge');
|
|
593
|
-
try {
|
|
594
|
-
const response = await fetch(this.authProxyUrl, {
|
|
595
|
-
method: 'POST',
|
|
596
|
-
headers: { 'Content-Type': 'application/json' },
|
|
597
|
-
body: payloadJson,
|
|
598
|
-
});
|
|
599
|
-
if (!response.ok) {
|
|
600
|
-
logWarn(`Online license: proxy HTTP ${response.status}`);
|
|
601
|
-
return '';
|
|
602
|
-
}
|
|
603
|
-
const text = await response.text();
|
|
604
|
-
logInfo('Online license: proxy response ok');
|
|
605
|
-
return text;
|
|
606
|
-
} catch (err) {
|
|
607
|
-
logWarn(
|
|
608
|
-
`Online license: proxy error${err?.message ? `: ${err.message}` : ''}`
|
|
609
|
-
);
|
|
610
|
-
return '';
|
|
611
|
-
}
|
|
612
|
-
};
|
|
613
|
-
} else {
|
|
614
|
-
throw new FacebetterError(
|
|
615
|
-
'Web auth requires licenseToken, authProxyUrl, or fetchAuthResponse.',
|
|
616
|
-
'LICENSE_ERROR'
|
|
617
|
-
);
|
|
618
|
-
}
|
|
518
|
+
throw new FacebetterError(
|
|
519
|
+
'Web auth requires licenseToken. Fetch it from your server before init().',
|
|
520
|
+
'LICENSE_ERROR'
|
|
521
|
+
);
|
|
619
522
|
}
|
|
620
523
|
|
|
621
524
|
// C ABI still has app_id/app_key slots; leave them empty on Web.
|
|
@@ -659,7 +562,8 @@
|
|
|
659
562
|
this.initialized = true;
|
|
660
563
|
this._initPromise = null; // Clear Promise cache, allow re-initialization (if needed)
|
|
661
564
|
} catch (error) {
|
|
662
|
-
this._initPromise = null;
|
|
565
|
+
this._initPromise = null;
|
|
566
|
+
this._wasmLoadPromise = null;
|
|
663
567
|
throw error;
|
|
664
568
|
}
|
|
665
569
|
})();
|
|
@@ -716,8 +620,9 @@
|
|
|
716
620
|
}
|
|
717
621
|
|
|
718
622
|
/**
|
|
719
|
-
* Sets log configuration
|
|
720
|
-
*
|
|
623
|
+
* Sets log configuration.
|
|
624
|
+
* If called before `init()`, the values are applied after the runtime downloads
|
|
625
|
+
* and before the engine is created.
|
|
721
626
|
* @param {Object} config - Log configuration
|
|
722
627
|
* @param {boolean} config.consoleEnabled - Enable console logging
|
|
723
628
|
* @param {boolean} config.fileEnabled - Enable file logging
|
|
@@ -726,8 +631,15 @@
|
|
|
726
631
|
* @returns {Promise<void>} Promise that resolves when log config is set
|
|
727
632
|
*/
|
|
728
633
|
async setLogConfig(config) {
|
|
729
|
-
|
|
634
|
+
if (!this._wasmLoadPromise) {
|
|
635
|
+
this._pendingLogConfig = config;
|
|
636
|
+
return;
|
|
637
|
+
}
|
|
730
638
|
await this._wasmLoadPromise;
|
|
639
|
+
await this._applyLogConfig(config);
|
|
640
|
+
}
|
|
641
|
+
|
|
642
|
+
async _applyLogConfig(config) {
|
|
731
643
|
const Module = this._getWasmModule();
|
|
732
644
|
|
|
733
645
|
const fileNamePtr = allocUtf8(Module, config.fileName);
|
|
@@ -1632,100 +1544,204 @@
|
|
|
1632
1544
|
|
|
1633
1545
|
}
|
|
1634
1546
|
|
|
1635
|
-
|
|
1636
|
-
const
|
|
1637
|
-
|
|
1547
|
+
const WASM_FILE = 'facebetter-core.wasm';
|
|
1548
|
+
const RESOURCE_FILE = 'resource.fbd';
|
|
1549
|
+
const MEMFS_RESOURCE_PATH = '/resource.fbd';
|
|
1638
1550
|
|
|
1639
|
-
function
|
|
1640
|
-
|
|
1641
|
-
|
|
1642
|
-
|
|
1551
|
+
function emitProgress(onProgress, loaded, total) {
|
|
1552
|
+
if (typeof onProgress !== 'function') {
|
|
1553
|
+
return;
|
|
1554
|
+
}
|
|
1555
|
+
const safeTotal = total > 0 ? total : loaded;
|
|
1556
|
+
const percent =
|
|
1557
|
+
safeTotal > 0 ? Math.min(100, Math.round((loaded / safeTotal) * 100)) : 0;
|
|
1558
|
+
onProgress({ loaded, total: safeTotal, percent });
|
|
1643
1559
|
}
|
|
1644
1560
|
|
|
1645
|
-
async function
|
|
1646
|
-
const
|
|
1647
|
-
const
|
|
1648
|
-
|
|
1649
|
-
|
|
1650
|
-
|
|
1651
|
-
|
|
1652
|
-
['sign']
|
|
1653
|
-
);
|
|
1654
|
-
const signature = await crypto.subtle.sign(
|
|
1655
|
-
'HMAC',
|
|
1656
|
-
cryptoKey,
|
|
1657
|
-
encoder.encode(message)
|
|
1658
|
-
);
|
|
1659
|
-
return toHex(signature);
|
|
1561
|
+
async function readFileUrl(url, onChunk) {
|
|
1562
|
+
const { readFile } = await import('node:fs/promises');
|
|
1563
|
+
const { fileURLToPath } = await import('node:url');
|
|
1564
|
+
const buf = await readFile(fileURLToPath(url));
|
|
1565
|
+
const bytes = new Uint8Array(buf);
|
|
1566
|
+
onChunk(bytes.byteLength, bytes.byteLength);
|
|
1567
|
+
return bytes;
|
|
1660
1568
|
}
|
|
1661
1569
|
|
|
1662
1570
|
/**
|
|
1663
|
-
*
|
|
1664
|
-
*
|
|
1665
|
-
*
|
|
1666
|
-
* @
|
|
1667
|
-
* @param {string} options.appId
|
|
1668
|
-
* @param {string} options.appKey
|
|
1669
|
-
* @param {Object} options.challenge WASM challenge `{nonce, timestamp, platform, user_agent}`
|
|
1670
|
-
* @param {string} [options.authUrl]
|
|
1671
|
-
* @returns {Promise<string>} Raw HTTP body (`{success, token}` or an error envelope)
|
|
1571
|
+
* Fetch a binary with byte-level download progress.
|
|
1572
|
+
* @param {string} url
|
|
1573
|
+
* @param {function(number, number): void} onChunk loaded, total (0 if unknown)
|
|
1574
|
+
* @returns {Promise<Uint8Array>}
|
|
1672
1575
|
*/
|
|
1673
|
-
async function
|
|
1674
|
-
|
|
1675
|
-
|
|
1676
|
-
challenge,
|
|
1677
|
-
authUrl = FACEBETTER_AUTH_URL,
|
|
1678
|
-
}) {
|
|
1679
|
-
const id = typeof appId === 'string' ? appId.trim() : '';
|
|
1680
|
-
const key = typeof appKey === 'string' ? appKey.trim() : '';
|
|
1681
|
-
if (!id || !key) {
|
|
1682
|
-
throw new FacebetterError(
|
|
1683
|
-
'requestFacebetterAuth requires appId and appKey',
|
|
1684
|
-
'LICENSE_ERROR'
|
|
1685
|
-
);
|
|
1576
|
+
async function fetchBinary(url, onChunk) {
|
|
1577
|
+
if (typeof url === 'string' && url.startsWith('file:')) {
|
|
1578
|
+
return readFileUrl(url, onChunk);
|
|
1686
1579
|
}
|
|
1687
|
-
|
|
1580
|
+
|
|
1581
|
+
const response = await fetch(url);
|
|
1582
|
+
if (!response.ok) {
|
|
1688
1583
|
throw new FacebetterError(
|
|
1689
|
-
|
|
1690
|
-
'
|
|
1584
|
+
`Failed to download ${url} (${response.status})`,
|
|
1585
|
+
'WASM_LOAD_ERROR'
|
|
1691
1586
|
);
|
|
1692
1587
|
}
|
|
1693
1588
|
|
|
1694
|
-
const
|
|
1695
|
-
const
|
|
1696
|
-
|
|
1697
|
-
|
|
1698
|
-
|
|
1699
|
-
|
|
1700
|
-
|
|
1701
|
-
|
|
1702
|
-
|
|
1703
|
-
|
|
1704
|
-
|
|
1705
|
-
|
|
1706
|
-
|
|
1707
|
-
|
|
1708
|
-
|
|
1709
|
-
|
|
1710
|
-
|
|
1589
|
+
const totalHeader = Number(response.headers.get('content-length'));
|
|
1590
|
+
const total = Number.isFinite(totalHeader) && totalHeader > 0 ? totalHeader : 0;
|
|
1591
|
+
|
|
1592
|
+
if (!response.body || typeof response.body.getReader !== 'function') {
|
|
1593
|
+
const buffer = await response.arrayBuffer();
|
|
1594
|
+
const bytes = new Uint8Array(buffer);
|
|
1595
|
+
onChunk(bytes.byteLength, total || bytes.byteLength);
|
|
1596
|
+
return bytes;
|
|
1597
|
+
}
|
|
1598
|
+
|
|
1599
|
+
const reader = response.body.getReader();
|
|
1600
|
+
const chunks = [];
|
|
1601
|
+
let loaded = 0;
|
|
1602
|
+
while (true) {
|
|
1603
|
+
const { done, value } = await reader.read();
|
|
1604
|
+
if (done) {
|
|
1605
|
+
break;
|
|
1606
|
+
}
|
|
1607
|
+
chunks.push(value);
|
|
1608
|
+
loaded += value.byteLength;
|
|
1609
|
+
onChunk(loaded, total);
|
|
1610
|
+
}
|
|
1611
|
+
|
|
1612
|
+
const bytes = new Uint8Array(loaded);
|
|
1613
|
+
let offset = 0;
|
|
1614
|
+
for (const chunk of chunks) {
|
|
1615
|
+
bytes.set(chunk, offset);
|
|
1616
|
+
offset += chunk.byteLength;
|
|
1617
|
+
}
|
|
1618
|
+
onChunk(loaded, total || loaded);
|
|
1619
|
+
return bytes;
|
|
1620
|
+
}
|
|
1621
|
+
|
|
1622
|
+
async function fetchRuntimeAssets({ wasmUrl, resourceUrl, onProgress }) {
|
|
1623
|
+
let wasmLoaded = 0;
|
|
1624
|
+
let wasmTotal = 0;
|
|
1625
|
+
let resourceLoaded = 0;
|
|
1626
|
+
let resourceTotal = 0;
|
|
1627
|
+
|
|
1628
|
+
const emit = () => {
|
|
1629
|
+
emitProgress(
|
|
1630
|
+
onProgress,
|
|
1631
|
+
wasmLoaded + resourceLoaded,
|
|
1632
|
+
wasmTotal + resourceTotal
|
|
1633
|
+
);
|
|
1634
|
+
};
|
|
1635
|
+
|
|
1636
|
+
const [wasmBytes, resourceBytes] = await Promise.all([
|
|
1637
|
+
fetchBinary(wasmUrl, (loaded, total) => {
|
|
1638
|
+
wasmLoaded = loaded;
|
|
1639
|
+
wasmTotal = total || loaded;
|
|
1640
|
+
emit();
|
|
1711
1641
|
}),
|
|
1712
|
-
|
|
1713
|
-
|
|
1642
|
+
fetchBinary(resourceUrl, (loaded, total) => {
|
|
1643
|
+
resourceLoaded = loaded;
|
|
1644
|
+
resourceTotal = total || loaded;
|
|
1645
|
+
emit();
|
|
1646
|
+
}),
|
|
1647
|
+
]);
|
|
1648
|
+
|
|
1649
|
+
emitProgress(
|
|
1650
|
+
onProgress,
|
|
1651
|
+
wasmBytes.byteLength + resourceBytes.byteLength,
|
|
1652
|
+
wasmBytes.byteLength + resourceBytes.byteLength
|
|
1653
|
+
);
|
|
1654
|
+
return { wasmBytes, resourceBytes };
|
|
1655
|
+
}
|
|
1656
|
+
|
|
1657
|
+
function scriptDirectory() {
|
|
1658
|
+
if (typeof document === 'undefined') {
|
|
1659
|
+
return '';
|
|
1660
|
+
}
|
|
1661
|
+
if (document.currentScript?.src) {
|
|
1662
|
+
return document.currentScript.src.slice(
|
|
1663
|
+
0,
|
|
1664
|
+
document.currentScript.src.lastIndexOf('/') + 1
|
|
1665
|
+
);
|
|
1666
|
+
}
|
|
1667
|
+
const scripts = document.getElementsByTagName('script');
|
|
1668
|
+
for (let i = scripts.length - 1; i >= 0; i--) {
|
|
1669
|
+
const src = scripts[i].src;
|
|
1670
|
+
if (src && (src.includes('facebetter.js') || src.includes('facebetter'))) {
|
|
1671
|
+
return src.slice(0, src.lastIndexOf('/') + 1);
|
|
1672
|
+
}
|
|
1673
|
+
}
|
|
1674
|
+
if (typeof location !== 'undefined') {
|
|
1675
|
+
return new URL('.', location.href).href;
|
|
1676
|
+
}
|
|
1677
|
+
return './';
|
|
1714
1678
|
}
|
|
1715
1679
|
|
|
1716
1680
|
/**
|
|
1717
|
-
*
|
|
1718
|
-
*
|
|
1719
|
-
*
|
|
1720
|
-
* @param {Object} options
|
|
1721
|
-
* @param {string} options.appId
|
|
1722
|
-
* @param {string} options.appKey
|
|
1723
|
-
* @param {string} [options.authUrl]
|
|
1724
|
-
* @returns {(challenge: Object) => Promise<string>}
|
|
1681
|
+
* Resolve sidecar wasm / resource URLs.
|
|
1682
|
+
* Prefer `assetBaseUrl`, then `facebetter-core/assets`, then the script directory.
|
|
1725
1683
|
*/
|
|
1726
|
-
function
|
|
1727
|
-
|
|
1728
|
-
|
|
1684
|
+
async function resolveRuntimeAssetUrls(options = {}) {
|
|
1685
|
+
const joinBase = (base) => {
|
|
1686
|
+
const prefix = base.endsWith('/') ? base : `${base}/`;
|
|
1687
|
+
return {
|
|
1688
|
+
wasmUrl: options.wasmAssetUrl || `${prefix}${WASM_FILE}`,
|
|
1689
|
+
resourceUrl: options.resourceAssetUrl || `${prefix}${RESOURCE_FILE}`,
|
|
1690
|
+
};
|
|
1691
|
+
};
|
|
1692
|
+
|
|
1693
|
+
if (options.wasmAssetUrl && options.resourceAssetUrl) {
|
|
1694
|
+
return {
|
|
1695
|
+
wasmUrl: options.wasmAssetUrl,
|
|
1696
|
+
resourceUrl: options.resourceAssetUrl,
|
|
1697
|
+
};
|
|
1698
|
+
}
|
|
1699
|
+
if (options.assetBaseUrl) {
|
|
1700
|
+
return joinBase(options.assetBaseUrl);
|
|
1701
|
+
}
|
|
1702
|
+
if (typeof options.wasmUrl === 'string' && /^(https?:|file:|\/)/.test(options.wasmUrl)) {
|
|
1703
|
+
return joinBase(options.wasmUrl.slice(0, options.wasmUrl.lastIndexOf('/') + 1));
|
|
1704
|
+
}
|
|
1705
|
+
|
|
1706
|
+
try {
|
|
1707
|
+
const assets = await import('facebetter-core/assets');
|
|
1708
|
+
return {
|
|
1709
|
+
wasmUrl: options.wasmAssetUrl || assets.wasmUrl,
|
|
1710
|
+
resourceUrl: options.resourceAssetUrl || assets.resourceUrl,
|
|
1711
|
+
};
|
|
1712
|
+
} catch {
|
|
1713
|
+
return joinBase(scriptDirectory());
|
|
1714
|
+
}
|
|
1715
|
+
}
|
|
1716
|
+
|
|
1717
|
+
/**
|
|
1718
|
+
* Instantiate the Emscripten factory using already-downloaded bytes.
|
|
1719
|
+
*/
|
|
1720
|
+
async function instantiateRuntime(factory, wasmBytes, resourceBytes) {
|
|
1721
|
+
if (!factory) {
|
|
1722
|
+
throw new FacebetterError(
|
|
1723
|
+
'WASM module does not export createFaceBetterModule',
|
|
1724
|
+
'WASM_LOAD_ERROR'
|
|
1725
|
+
);
|
|
1726
|
+
}
|
|
1727
|
+
|
|
1728
|
+
const module = await factory({
|
|
1729
|
+
instantiateWasm(imports, receiveInstance) {
|
|
1730
|
+
return WebAssembly.instantiate(wasmBytes, imports).then((result) => {
|
|
1731
|
+
receiveInstance(result.instance, result.module);
|
|
1732
|
+
});
|
|
1733
|
+
},
|
|
1734
|
+
});
|
|
1735
|
+
|
|
1736
|
+
if (!module) {
|
|
1737
|
+
throw new FacebetterError('Failed to initialize WASM module', 'WASM_LOAD_ERROR');
|
|
1738
|
+
}
|
|
1739
|
+
if (!module.FS) {
|
|
1740
|
+
throw new FacebetterError('Runtime filesystem is not available', 'WASM_LOAD_ERROR');
|
|
1741
|
+
}
|
|
1742
|
+
module.FS.writeFile(MEMFS_RESOURCE_PATH, resourceBytes);
|
|
1743
|
+
module.ready = true;
|
|
1744
|
+
return module;
|
|
1729
1745
|
}
|
|
1730
1746
|
|
|
1731
1747
|
/**
|
|
@@ -1734,6 +1750,7 @@
|
|
|
1734
1750
|
* Uses document.currentScript for automatic path detection in UMD builds
|
|
1735
1751
|
*/
|
|
1736
1752
|
|
|
1753
|
+
|
|
1737
1754
|
/**
|
|
1738
1755
|
* Gets the base path of the current script
|
|
1739
1756
|
* Similar to Node.js __dirname
|
|
@@ -1839,6 +1856,7 @@
|
|
|
1839
1856
|
*/
|
|
1840
1857
|
async function loadWasmModule(options = {}) {
|
|
1841
1858
|
if (wasmModuleInstance) {
|
|
1859
|
+
options.onProgress?.({ loaded: 1, total: 1, percent: 100 });
|
|
1842
1860
|
return wasmModuleInstance;
|
|
1843
1861
|
}
|
|
1844
1862
|
|
|
@@ -1848,57 +1866,44 @@
|
|
|
1848
1866
|
|
|
1849
1867
|
wasmModulePromise = (async () => {
|
|
1850
1868
|
try {
|
|
1851
|
-
let
|
|
1852
|
-
|
|
1869
|
+
let factory;
|
|
1870
|
+
|
|
1853
1871
|
if (options.wasmUrl) {
|
|
1854
|
-
|
|
1855
|
-
|
|
1856
|
-
FaceBetterModuleFactory = wasmModule.default || wasmModule.createFaceBetterModule;
|
|
1872
|
+
const wasmModule = await import(/* @vite-ignore */ options.wasmUrl);
|
|
1873
|
+
factory = wasmModule.default || wasmModule.createFaceBetterModule;
|
|
1857
1874
|
} else {
|
|
1858
|
-
// Try to import from facebetter-core npm package
|
|
1859
|
-
// Note: In UMD builds, this may not work if facebetter-core is not available
|
|
1860
|
-
// In that case, fallback to relative path detection
|
|
1861
1875
|
try {
|
|
1862
1876
|
const wasmModule = await import('facebetter-core');
|
|
1863
|
-
|
|
1864
|
-
} catch
|
|
1865
|
-
// Fallback: try to find facebetter-core.js relative to current script
|
|
1866
|
-
// This handles UMD builds where npm packages may not be available
|
|
1877
|
+
factory = wasmModule.default || wasmModule.createFaceBetterModule;
|
|
1878
|
+
} catch {
|
|
1867
1879
|
const basePath = getScriptBasePath();
|
|
1868
|
-
const
|
|
1869
|
-
const wasmModule = await import(
|
|
1870
|
-
|
|
1880
|
+
const gluePath = resolvePath('facebetter-core.js', basePath);
|
|
1881
|
+
const wasmModule = await import(gluePath);
|
|
1882
|
+
factory = wasmModule.default || wasmModule.createFaceBetterModule;
|
|
1883
|
+
if (!options.assetBaseUrl) {
|
|
1884
|
+
options = { ...options, assetBaseUrl: basePath };
|
|
1885
|
+
}
|
|
1871
1886
|
}
|
|
1872
1887
|
}
|
|
1873
|
-
|
|
1874
|
-
if (!FaceBetterModuleFactory) {
|
|
1875
|
-
throw new Error('WASM module does not export createFaceBetterModule function');
|
|
1876
|
-
}
|
|
1877
|
-
|
|
1878
|
-
// Configure module options
|
|
1879
|
-
// With SINGLE_FILE=1, WASM binary and data files are embedded in the JS file
|
|
1880
|
-
// No need to handle .wasm and .data file paths separately
|
|
1881
|
-
const moduleOptions = {
|
|
1882
|
-
locateFile: options.locateFile || function(path, prefix) {
|
|
1883
|
-
// With SINGLE_FILE=1, Emscripten handles embedded files automatically
|
|
1884
|
-
// Custom locateFile is only needed if user provides one
|
|
1885
|
-
return prefix + path;
|
|
1886
|
-
},
|
|
1887
|
-
...options
|
|
1888
|
-
};
|
|
1889
1888
|
|
|
1890
|
-
|
|
1891
|
-
|
|
1892
|
-
|
|
1893
|
-
if (!wasmModuleInstance) {
|
|
1894
|
-
throw new Error('Failed to initialize WASM module');
|
|
1889
|
+
if (!factory) {
|
|
1890
|
+
throw new Error('WASM module does not export createFaceBetterModule function');
|
|
1895
1891
|
}
|
|
1896
1892
|
|
|
1897
|
-
|
|
1893
|
+
const urls = await resolveRuntimeAssetUrls(options);
|
|
1894
|
+
const { wasmBytes, resourceBytes } = await fetchRuntimeAssets({
|
|
1895
|
+
...urls,
|
|
1896
|
+
onProgress: options.onProgress,
|
|
1897
|
+
});
|
|
1898
|
+
wasmModuleInstance = await instantiateRuntime(
|
|
1899
|
+
factory,
|
|
1900
|
+
wasmBytes,
|
|
1901
|
+
resourceBytes
|
|
1902
|
+
);
|
|
1898
1903
|
return wasmModuleInstance;
|
|
1899
1904
|
} catch (e) {
|
|
1900
1905
|
wasmModulePromise = null;
|
|
1901
|
-
throw new
|
|
1906
|
+
throw new FacebetterError(`Failed to load WASM module: ${e.message}`, 'WASM_LOAD_ERROR');
|
|
1902
1907
|
}
|
|
1903
1908
|
})();
|
|
1904
1909
|
|
|
@@ -2016,7 +2021,7 @@
|
|
|
2016
2021
|
|
|
2017
2022
|
// Create platform API
|
|
2018
2023
|
const platformAPI = {
|
|
2019
|
-
loadWasmModule: () => loadWasmModule(),
|
|
2024
|
+
loadWasmModule: (options) => loadWasmModule(options),
|
|
2020
2025
|
getWasmModule: getWasmModule,
|
|
2021
2026
|
getWasmBuffer: getWasmBuffer,
|
|
2022
2027
|
toImageData: toImageData,
|
|
@@ -2065,9 +2070,6 @@
|
|
|
2065
2070
|
BeautyEffectEngine: BrowserBeautyEffectEngine,
|
|
2066
2071
|
EngineConfig,
|
|
2067
2072
|
FacebetterError,
|
|
2068
|
-
FACEBETTER_AUTH_URL,
|
|
2069
|
-
createDirectAuthFetcher,
|
|
2070
|
-
requestFacebetterAuth,
|
|
2071
2073
|
...constants,
|
|
2072
2074
|
loadWasmModule: loadWasmModule
|
|
2073
2075
|
};
|
|
@@ -2087,7 +2089,6 @@
|
|
|
2087
2089
|
exports.EyebrowStyle = EyebrowStyle;
|
|
2088
2090
|
exports.EyelashColor = EyelashColor;
|
|
2089
2091
|
exports.EyelashStyle = EyelashStyle;
|
|
2090
|
-
exports.FACEBETTER_AUTH_URL = FACEBETTER_AUTH_URL;
|
|
2091
2092
|
exports.FacebetterError = FacebetterError;
|
|
2092
2093
|
exports.FrameType = FrameType;
|
|
2093
2094
|
exports.LipstickColor = LipstickColor;
|
|
@@ -2097,10 +2098,8 @@
|
|
|
2097
2098
|
exports.SmoothingStyle = SmoothingStyle;
|
|
2098
2099
|
exports.WhiteningStyle = WhiteningStyle;
|
|
2099
2100
|
exports.createBeautyEffectEngine = createBeautyEffectEngine;
|
|
2100
|
-
exports.createDirectAuthFetcher = createDirectAuthFetcher;
|
|
2101
2101
|
exports.default = index;
|
|
2102
2102
|
exports.loadWasmModule = loadWasmModule;
|
|
2103
|
-
exports.requestFacebetterAuth = requestFacebetterAuth;
|
|
2104
2103
|
|
|
2105
2104
|
Object.defineProperty(exports, '__esModule', { value: true });
|
|
2106
2105
|
|