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.esm.js
CHANGED
|
@@ -5,58 +5,33 @@
|
|
|
5
5
|
|
|
6
6
|
/**
|
|
7
7
|
* Engine configuration class
|
|
8
|
-
* Analogous to EngineConfig in C++ / Java / Objective-C
|
|
8
|
+
* Analogous to EngineConfig in C++ / Java / Objective-C. Web only accepts licenseToken.
|
|
9
9
|
*/
|
|
10
10
|
class EngineConfig {
|
|
11
11
|
/**
|
|
12
12
|
* @param {Object} config
|
|
13
|
-
* @param {string}
|
|
13
|
+
* @param {string} config.licenseToken Compact JWS, or the raw `{success, token}`
|
|
14
14
|
* HTTP response body from your auth server.
|
|
15
|
-
* @param {string} [config.authProxyUrl] Production: forward the WASM auth
|
|
16
|
-
* challenge to your server, which holds app_id/app_key and calls Cloudflare.
|
|
17
|
-
* @param {Function} [config.fetchAuthResponse] Custom auth hook with signature
|
|
18
|
-
* `async (challenge) => string|object`. For local debugging you can use
|
|
19
|
-
* `createDirectAuthFetcher` to hit Cloudflare directly — never ship keys in
|
|
20
|
-
* a production frontend bundle.
|
|
21
15
|
* @param {boolean} [config.externalContext] Kept for cross-platform alignment;
|
|
22
|
-
* unused on Web
|
|
16
|
+
* unused on Web today.
|
|
23
17
|
*/
|
|
24
18
|
constructor(config = {}) {
|
|
25
19
|
this.licenseToken = config.licenseToken || null;
|
|
26
|
-
this.authProxyUrl = config.authProxyUrl || null;
|
|
27
|
-
this.fetchAuthResponse = config.fetchAuthResponse || null;
|
|
28
20
|
this.resourcePath = '/resource.fbd';
|
|
29
21
|
/**
|
|
30
22
|
* Whether to use an external GL context (native platforms only).
|
|
31
|
-
* On Web
|
|
23
|
+
* On Web this field is kept for config-shape alignment and has no effect.
|
|
32
24
|
*/
|
|
33
25
|
this.externalContext = !!config.externalContext;
|
|
34
26
|
}
|
|
35
27
|
|
|
36
28
|
isValid() {
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
return true;
|
|
40
|
-
}
|
|
41
|
-
if (typeof this.fetchAuthResponse === 'function') {
|
|
42
|
-
return true;
|
|
43
|
-
}
|
|
44
|
-
if (this.authProxyUrl && typeof this.authProxyUrl === 'string' &&
|
|
45
|
-
this.authProxyUrl.trim() !== '') {
|
|
46
|
-
return true;
|
|
47
|
-
}
|
|
48
|
-
return false;
|
|
29
|
+
return typeof this.licenseToken === 'string' &&
|
|
30
|
+
this.licenseToken.trim() !== '';
|
|
49
31
|
}
|
|
50
32
|
|
|
51
33
|
toString() {
|
|
52
|
-
|
|
53
|
-
? 'licenseToken'
|
|
54
|
-
: this.fetchAuthResponse
|
|
55
|
-
? 'fetchAuthResponse'
|
|
56
|
-
: this.authProxyUrl
|
|
57
|
-
? 'authProxyUrl'
|
|
58
|
-
: 'invalid';
|
|
59
|
-
return `EngineConfig{mode='${mode}'}`;
|
|
34
|
+
return `EngineConfig{mode='${this.isValid() ? 'licenseToken' : 'invalid'}'}`;
|
|
60
35
|
}
|
|
61
36
|
}
|
|
62
37
|
|
|
@@ -306,40 +281,6 @@ var constants = /*#__PURE__*/Object.freeze({
|
|
|
306
281
|
WhiteningStyle: WhiteningStyle$1
|
|
307
282
|
});
|
|
308
283
|
|
|
309
|
-
/**
|
|
310
|
-
* Browser-side console lines aligned with C++ Logger (src/base/logging.cc) Release pattern:
|
|
311
|
-
* [%Y-%m-%d %H:%M:%S.%e] [facebetter] [%l] [%t] - %v
|
|
312
|
-
* spdlog level names: info, warning, error (see spdlog SPDLOG_LEVEL_NAMES).
|
|
313
|
-
*/
|
|
314
|
-
|
|
315
|
-
function pad2(n) {
|
|
316
|
-
return String(n).padStart(2, '0');
|
|
317
|
-
}
|
|
318
|
-
|
|
319
|
-
/**
|
|
320
|
-
* @param {'info'|'warning'|'error'} levelTag — must match spdlog long level strings
|
|
321
|
-
* @param {string} message
|
|
322
|
-
* @returns {string}
|
|
323
|
-
*/
|
|
324
|
-
function formatLogLine(levelTag, message) {
|
|
325
|
-
const d = new Date();
|
|
326
|
-
const ms = String(d.getMilliseconds()).padStart(3, '0');
|
|
327
|
-
const ts = `${d.getFullYear()}-${pad2(d.getMonth() + 1)}-${pad2(d.getDate())} ${pad2(
|
|
328
|
-
d.getHours()
|
|
329
|
-
)}:${pad2(d.getMinutes())}:${pad2(d.getSeconds())}.${ms}`;
|
|
330
|
-
return `[${ts}] [facebetter] [${levelTag}] [0] - ${message}`;
|
|
331
|
-
}
|
|
332
|
-
|
|
333
|
-
/** @param {string} message */
|
|
334
|
-
function logInfo(message) {
|
|
335
|
-
console.info(formatLogLine('info', message));
|
|
336
|
-
}
|
|
337
|
-
|
|
338
|
-
/** @param {string} message */
|
|
339
|
-
function logWarn(message) {
|
|
340
|
-
console.warn(formatLogLine('warning', message));
|
|
341
|
-
}
|
|
342
|
-
|
|
343
284
|
/**
|
|
344
285
|
* Beauty Effect Engine Core
|
|
345
286
|
* Platform-agnostic engine implementation with dependency injection
|
|
@@ -404,8 +345,6 @@ class BeautyEffectEngine {
|
|
|
404
345
|
|
|
405
346
|
this.config = engineConfig;
|
|
406
347
|
this.licenseToken = engineConfig.licenseToken;
|
|
407
|
-
this.authProxyUrl = engineConfig.authProxyUrl;
|
|
408
|
-
this.fetchAuthResponse = engineConfig.fetchAuthResponse;
|
|
409
348
|
this.resourcePath = '/resource.fbd';
|
|
410
349
|
this.enginePtr = null;
|
|
411
350
|
this.initialized = false;
|
|
@@ -425,7 +364,8 @@ class BeautyEffectEngine {
|
|
|
425
364
|
this._toImageData = platformAPI.toImageData;
|
|
426
365
|
this._ensureFacebetterCanvas = platformAPI.ensureFacebetterCanvas;
|
|
427
366
|
this._cleanupFacebetterCanvas = platformAPI.cleanupFacebetterCanvas;
|
|
428
|
-
|
|
367
|
+
this._pendingLogConfig = null;
|
|
368
|
+
|
|
429
369
|
// Browser-specific state
|
|
430
370
|
this._facebetterCanvas = null;
|
|
431
371
|
this._createdFacebetterCanvas = false;
|
|
@@ -436,9 +376,8 @@ class BeautyEffectEngine {
|
|
|
436
376
|
if (this._ensureFacebetterCanvas) {
|
|
437
377
|
this._ensureFacebetterCanvas();
|
|
438
378
|
}
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
this._wasmLoadPromise = this._loadWasmModule();
|
|
379
|
+
|
|
380
|
+
this._wasmLoadPromise = null;
|
|
442
381
|
this._initPromise = null;
|
|
443
382
|
}
|
|
444
383
|
|
|
@@ -495,8 +434,12 @@ class BeautyEffectEngine {
|
|
|
495
434
|
/**
|
|
496
435
|
* Initializes the engine
|
|
497
436
|
* @param {Object} [options] - Initialization options
|
|
498
|
-
* @param {number} [options.timeout] - Timeout in milliseconds (default:
|
|
499
|
-
* @param {number} [options.authTimeout] -
|
|
437
|
+
* @param {number} [options.timeout] - Timeout in milliseconds (default: 120000)
|
|
438
|
+
* @param {number} [options.authTimeout] - Unused. Kept for compatibility.
|
|
439
|
+
* @param {function} [options.onProgress] - Download progress:
|
|
440
|
+
* `{ loaded, total, percent }` while fetching the runtime files.
|
|
441
|
+
* @param {string} [options.assetBaseUrl] - Directory that contains
|
|
442
|
+
* `facebetter-core.wasm` and `resource.fbd`. Optional when using npm.
|
|
500
443
|
* @returns {Promise<void>} Promise that resolves when initialization is complete
|
|
501
444
|
*/
|
|
502
445
|
async init(options = {}) {
|
|
@@ -513,9 +456,18 @@ class BeautyEffectEngine {
|
|
|
513
456
|
// Create initialization Promise
|
|
514
457
|
this._initPromise = (async () => {
|
|
515
458
|
try {
|
|
516
|
-
const wasmTimeout = options.timeout ||
|
|
459
|
+
const wasmTimeout = options.timeout || 120000;
|
|
460
|
+
|
|
461
|
+
if (!this._wasmLoadPromise) {
|
|
462
|
+
this._wasmLoadPromise = this._loadWasmModule({
|
|
463
|
+
onProgress: options.onProgress,
|
|
464
|
+
assetBaseUrl: options.assetBaseUrl,
|
|
465
|
+
wasmUrl: options.wasmUrl,
|
|
466
|
+
wasmAssetUrl: options.wasmAssetUrl,
|
|
467
|
+
resourceAssetUrl: options.resourceAssetUrl,
|
|
468
|
+
});
|
|
469
|
+
}
|
|
517
470
|
|
|
518
|
-
// Wait for WASM module loading (with timeout)
|
|
519
471
|
try {
|
|
520
472
|
await Promise.race([
|
|
521
473
|
this._wasmLoadPromise,
|
|
@@ -525,6 +477,11 @@ class BeautyEffectEngine {
|
|
|
525
477
|
throw this._enhanceError(error, 'Failed to load WASM module');
|
|
526
478
|
}
|
|
527
479
|
|
|
480
|
+
if (this._pendingLogConfig) {
|
|
481
|
+
await this._applyLogConfig(this._pendingLogConfig);
|
|
482
|
+
this._pendingLogConfig = null;
|
|
483
|
+
}
|
|
484
|
+
|
|
528
485
|
const Module = this._getWasmModule();
|
|
529
486
|
|
|
530
487
|
// Setup usage report proxy for WASM
|
|
@@ -550,66 +507,12 @@ class BeautyEffectEngine {
|
|
|
550
507
|
};
|
|
551
508
|
}
|
|
552
509
|
|
|
553
|
-
// Web
|
|
554
|
-
// - licenseToken: pre-fetched JWS or `{success, token}` body
|
|
555
|
-
// - authProxyUrl: production — forward the challenge to your server
|
|
556
|
-
// - fetchAuthResponse: custom hook; locally use createDirectAuthFetcher
|
|
510
|
+
// Web: fetch the token outside the engine, then pass licenseToken.
|
|
557
511
|
if (!this.licenseToken) {
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
const result = await this.fetchAuthResponse(challenge);
|
|
563
|
-
if (result && typeof result === 'object' && typeof result.response === 'string') {
|
|
564
|
-
return { response: result.response };
|
|
565
|
-
}
|
|
566
|
-
if (typeof result === 'string') {
|
|
567
|
-
return result;
|
|
568
|
-
}
|
|
569
|
-
if (result && typeof result === 'object') {
|
|
570
|
-
return JSON.stringify(result);
|
|
571
|
-
}
|
|
572
|
-
return '';
|
|
573
|
-
} catch (err) {
|
|
574
|
-
logWarn(
|
|
575
|
-
`Online license: fetchAuthResponse error${err?.message ? `: ${err.message}` : ''}`
|
|
576
|
-
);
|
|
577
|
-
return JSON.stringify({
|
|
578
|
-
success: false,
|
|
579
|
-
error: 'auth_fetch_failed',
|
|
580
|
-
message: err?.message || 'auth fetch failed',
|
|
581
|
-
});
|
|
582
|
-
}
|
|
583
|
-
};
|
|
584
|
-
} else if (this.authProxyUrl) {
|
|
585
|
-
Module.onOnlineAuth = async (payloadJson) => {
|
|
586
|
-
logInfo('Online license: proxying auth challenge');
|
|
587
|
-
try {
|
|
588
|
-
const response = await fetch(this.authProxyUrl, {
|
|
589
|
-
method: 'POST',
|
|
590
|
-
headers: { 'Content-Type': 'application/json' },
|
|
591
|
-
body: payloadJson,
|
|
592
|
-
});
|
|
593
|
-
if (!response.ok) {
|
|
594
|
-
logWarn(`Online license: proxy HTTP ${response.status}`);
|
|
595
|
-
return '';
|
|
596
|
-
}
|
|
597
|
-
const text = await response.text();
|
|
598
|
-
logInfo('Online license: proxy response ok');
|
|
599
|
-
return text;
|
|
600
|
-
} catch (err) {
|
|
601
|
-
logWarn(
|
|
602
|
-
`Online license: proxy error${err?.message ? `: ${err.message}` : ''}`
|
|
603
|
-
);
|
|
604
|
-
return '';
|
|
605
|
-
}
|
|
606
|
-
};
|
|
607
|
-
} else {
|
|
608
|
-
throw new FacebetterError(
|
|
609
|
-
'Web auth requires licenseToken, authProxyUrl, or fetchAuthResponse.',
|
|
610
|
-
'LICENSE_ERROR'
|
|
611
|
-
);
|
|
612
|
-
}
|
|
512
|
+
throw new FacebetterError(
|
|
513
|
+
'Web auth requires licenseToken. Fetch it from your server before init().',
|
|
514
|
+
'LICENSE_ERROR'
|
|
515
|
+
);
|
|
613
516
|
}
|
|
614
517
|
|
|
615
518
|
// C ABI still has app_id/app_key slots; leave them empty on Web.
|
|
@@ -653,7 +556,8 @@ class BeautyEffectEngine {
|
|
|
653
556
|
this.initialized = true;
|
|
654
557
|
this._initPromise = null; // Clear Promise cache, allow re-initialization (if needed)
|
|
655
558
|
} catch (error) {
|
|
656
|
-
this._initPromise = null;
|
|
559
|
+
this._initPromise = null;
|
|
560
|
+
this._wasmLoadPromise = null;
|
|
657
561
|
throw error;
|
|
658
562
|
}
|
|
659
563
|
})();
|
|
@@ -710,8 +614,9 @@ class BeautyEffectEngine {
|
|
|
710
614
|
}
|
|
711
615
|
|
|
712
616
|
/**
|
|
713
|
-
* Sets log configuration
|
|
714
|
-
*
|
|
617
|
+
* Sets log configuration.
|
|
618
|
+
* If called before `init()`, the values are applied after the runtime downloads
|
|
619
|
+
* and before the engine is created.
|
|
715
620
|
* @param {Object} config - Log configuration
|
|
716
621
|
* @param {boolean} config.consoleEnabled - Enable console logging
|
|
717
622
|
* @param {boolean} config.fileEnabled - Enable file logging
|
|
@@ -720,8 +625,15 @@ class BeautyEffectEngine {
|
|
|
720
625
|
* @returns {Promise<void>} Promise that resolves when log config is set
|
|
721
626
|
*/
|
|
722
627
|
async setLogConfig(config) {
|
|
723
|
-
|
|
628
|
+
if (!this._wasmLoadPromise) {
|
|
629
|
+
this._pendingLogConfig = config;
|
|
630
|
+
return;
|
|
631
|
+
}
|
|
724
632
|
await this._wasmLoadPromise;
|
|
633
|
+
await this._applyLogConfig(config);
|
|
634
|
+
}
|
|
635
|
+
|
|
636
|
+
async _applyLogConfig(config) {
|
|
725
637
|
const Module = this._getWasmModule();
|
|
726
638
|
|
|
727
639
|
const fileNamePtr = allocUtf8(Module, config.fileName);
|
|
@@ -1626,100 +1538,204 @@ class BeautyEffectEngine {
|
|
|
1626
1538
|
|
|
1627
1539
|
}
|
|
1628
1540
|
|
|
1629
|
-
|
|
1630
|
-
const
|
|
1631
|
-
|
|
1541
|
+
const WASM_FILE = 'facebetter-core.wasm';
|
|
1542
|
+
const RESOURCE_FILE = 'resource.fbd';
|
|
1543
|
+
const MEMFS_RESOURCE_PATH = '/resource.fbd';
|
|
1632
1544
|
|
|
1633
|
-
function
|
|
1634
|
-
|
|
1635
|
-
|
|
1636
|
-
|
|
1545
|
+
function emitProgress(onProgress, loaded, total) {
|
|
1546
|
+
if (typeof onProgress !== 'function') {
|
|
1547
|
+
return;
|
|
1548
|
+
}
|
|
1549
|
+
const safeTotal = total > 0 ? total : loaded;
|
|
1550
|
+
const percent =
|
|
1551
|
+
safeTotal > 0 ? Math.min(100, Math.round((loaded / safeTotal) * 100)) : 0;
|
|
1552
|
+
onProgress({ loaded, total: safeTotal, percent });
|
|
1637
1553
|
}
|
|
1638
1554
|
|
|
1639
|
-
async function
|
|
1640
|
-
const
|
|
1641
|
-
const
|
|
1642
|
-
|
|
1643
|
-
|
|
1644
|
-
|
|
1645
|
-
|
|
1646
|
-
['sign']
|
|
1647
|
-
);
|
|
1648
|
-
const signature = await crypto.subtle.sign(
|
|
1649
|
-
'HMAC',
|
|
1650
|
-
cryptoKey,
|
|
1651
|
-
encoder.encode(message)
|
|
1652
|
-
);
|
|
1653
|
-
return toHex(signature);
|
|
1555
|
+
async function readFileUrl(url, onChunk) {
|
|
1556
|
+
const { readFile } = await import('node:fs/promises');
|
|
1557
|
+
const { fileURLToPath } = await import('node:url');
|
|
1558
|
+
const buf = await readFile(fileURLToPath(url));
|
|
1559
|
+
const bytes = new Uint8Array(buf);
|
|
1560
|
+
onChunk(bytes.byteLength, bytes.byteLength);
|
|
1561
|
+
return bytes;
|
|
1654
1562
|
}
|
|
1655
1563
|
|
|
1656
1564
|
/**
|
|
1657
|
-
*
|
|
1658
|
-
*
|
|
1659
|
-
*
|
|
1660
|
-
* @
|
|
1661
|
-
* @param {string} options.appId
|
|
1662
|
-
* @param {string} options.appKey
|
|
1663
|
-
* @param {Object} options.challenge WASM challenge `{nonce, timestamp, platform, user_agent}`
|
|
1664
|
-
* @param {string} [options.authUrl]
|
|
1665
|
-
* @returns {Promise<string>} Raw HTTP body (`{success, token}` or an error envelope)
|
|
1565
|
+
* Fetch a binary with byte-level download progress.
|
|
1566
|
+
* @param {string} url
|
|
1567
|
+
* @param {function(number, number): void} onChunk loaded, total (0 if unknown)
|
|
1568
|
+
* @returns {Promise<Uint8Array>}
|
|
1666
1569
|
*/
|
|
1667
|
-
async function
|
|
1668
|
-
|
|
1669
|
-
|
|
1670
|
-
challenge,
|
|
1671
|
-
authUrl = FACEBETTER_AUTH_URL,
|
|
1672
|
-
}) {
|
|
1673
|
-
const id = typeof appId === 'string' ? appId.trim() : '';
|
|
1674
|
-
const key = typeof appKey === 'string' ? appKey.trim() : '';
|
|
1675
|
-
if (!id || !key) {
|
|
1676
|
-
throw new FacebetterError(
|
|
1677
|
-
'requestFacebetterAuth requires appId and appKey',
|
|
1678
|
-
'LICENSE_ERROR'
|
|
1679
|
-
);
|
|
1570
|
+
async function fetchBinary(url, onChunk) {
|
|
1571
|
+
if (typeof url === 'string' && url.startsWith('file:')) {
|
|
1572
|
+
return readFileUrl(url, onChunk);
|
|
1680
1573
|
}
|
|
1681
|
-
|
|
1574
|
+
|
|
1575
|
+
const response = await fetch(url);
|
|
1576
|
+
if (!response.ok) {
|
|
1682
1577
|
throw new FacebetterError(
|
|
1683
|
-
|
|
1684
|
-
'
|
|
1578
|
+
`Failed to download ${url} (${response.status})`,
|
|
1579
|
+
'WASM_LOAD_ERROR'
|
|
1685
1580
|
);
|
|
1686
1581
|
}
|
|
1687
1582
|
|
|
1688
|
-
const
|
|
1689
|
-
const
|
|
1690
|
-
|
|
1691
|
-
|
|
1692
|
-
|
|
1693
|
-
|
|
1694
|
-
|
|
1695
|
-
|
|
1696
|
-
|
|
1697
|
-
|
|
1698
|
-
|
|
1699
|
-
|
|
1700
|
-
|
|
1701
|
-
|
|
1702
|
-
|
|
1703
|
-
|
|
1704
|
-
|
|
1583
|
+
const totalHeader = Number(response.headers.get('content-length'));
|
|
1584
|
+
const total = Number.isFinite(totalHeader) && totalHeader > 0 ? totalHeader : 0;
|
|
1585
|
+
|
|
1586
|
+
if (!response.body || typeof response.body.getReader !== 'function') {
|
|
1587
|
+
const buffer = await response.arrayBuffer();
|
|
1588
|
+
const bytes = new Uint8Array(buffer);
|
|
1589
|
+
onChunk(bytes.byteLength, total || bytes.byteLength);
|
|
1590
|
+
return bytes;
|
|
1591
|
+
}
|
|
1592
|
+
|
|
1593
|
+
const reader = response.body.getReader();
|
|
1594
|
+
const chunks = [];
|
|
1595
|
+
let loaded = 0;
|
|
1596
|
+
while (true) {
|
|
1597
|
+
const { done, value } = await reader.read();
|
|
1598
|
+
if (done) {
|
|
1599
|
+
break;
|
|
1600
|
+
}
|
|
1601
|
+
chunks.push(value);
|
|
1602
|
+
loaded += value.byteLength;
|
|
1603
|
+
onChunk(loaded, total);
|
|
1604
|
+
}
|
|
1605
|
+
|
|
1606
|
+
const bytes = new Uint8Array(loaded);
|
|
1607
|
+
let offset = 0;
|
|
1608
|
+
for (const chunk of chunks) {
|
|
1609
|
+
bytes.set(chunk, offset);
|
|
1610
|
+
offset += chunk.byteLength;
|
|
1611
|
+
}
|
|
1612
|
+
onChunk(loaded, total || loaded);
|
|
1613
|
+
return bytes;
|
|
1614
|
+
}
|
|
1615
|
+
|
|
1616
|
+
async function fetchRuntimeAssets({ wasmUrl, resourceUrl, onProgress }) {
|
|
1617
|
+
let wasmLoaded = 0;
|
|
1618
|
+
let wasmTotal = 0;
|
|
1619
|
+
let resourceLoaded = 0;
|
|
1620
|
+
let resourceTotal = 0;
|
|
1621
|
+
|
|
1622
|
+
const emit = () => {
|
|
1623
|
+
emitProgress(
|
|
1624
|
+
onProgress,
|
|
1625
|
+
wasmLoaded + resourceLoaded,
|
|
1626
|
+
wasmTotal + resourceTotal
|
|
1627
|
+
);
|
|
1628
|
+
};
|
|
1629
|
+
|
|
1630
|
+
const [wasmBytes, resourceBytes] = await Promise.all([
|
|
1631
|
+
fetchBinary(wasmUrl, (loaded, total) => {
|
|
1632
|
+
wasmLoaded = loaded;
|
|
1633
|
+
wasmTotal = total || loaded;
|
|
1634
|
+
emit();
|
|
1705
1635
|
}),
|
|
1706
|
-
|
|
1707
|
-
|
|
1636
|
+
fetchBinary(resourceUrl, (loaded, total) => {
|
|
1637
|
+
resourceLoaded = loaded;
|
|
1638
|
+
resourceTotal = total || loaded;
|
|
1639
|
+
emit();
|
|
1640
|
+
}),
|
|
1641
|
+
]);
|
|
1642
|
+
|
|
1643
|
+
emitProgress(
|
|
1644
|
+
onProgress,
|
|
1645
|
+
wasmBytes.byteLength + resourceBytes.byteLength,
|
|
1646
|
+
wasmBytes.byteLength + resourceBytes.byteLength
|
|
1647
|
+
);
|
|
1648
|
+
return { wasmBytes, resourceBytes };
|
|
1649
|
+
}
|
|
1650
|
+
|
|
1651
|
+
function scriptDirectory() {
|
|
1652
|
+
if (typeof document === 'undefined') {
|
|
1653
|
+
return '';
|
|
1654
|
+
}
|
|
1655
|
+
if (document.currentScript?.src) {
|
|
1656
|
+
return document.currentScript.src.slice(
|
|
1657
|
+
0,
|
|
1658
|
+
document.currentScript.src.lastIndexOf('/') + 1
|
|
1659
|
+
);
|
|
1660
|
+
}
|
|
1661
|
+
const scripts = document.getElementsByTagName('script');
|
|
1662
|
+
for (let i = scripts.length - 1; i >= 0; i--) {
|
|
1663
|
+
const src = scripts[i].src;
|
|
1664
|
+
if (src && (src.includes('facebetter.js') || src.includes('facebetter'))) {
|
|
1665
|
+
return src.slice(0, src.lastIndexOf('/') + 1);
|
|
1666
|
+
}
|
|
1667
|
+
}
|
|
1668
|
+
if (typeof location !== 'undefined') {
|
|
1669
|
+
return new URL('.', location.href).href;
|
|
1670
|
+
}
|
|
1671
|
+
return './';
|
|
1708
1672
|
}
|
|
1709
1673
|
|
|
1710
1674
|
/**
|
|
1711
|
-
*
|
|
1712
|
-
*
|
|
1713
|
-
*
|
|
1714
|
-
* @param {Object} options
|
|
1715
|
-
* @param {string} options.appId
|
|
1716
|
-
* @param {string} options.appKey
|
|
1717
|
-
* @param {string} [options.authUrl]
|
|
1718
|
-
* @returns {(challenge: Object) => Promise<string>}
|
|
1675
|
+
* Resolve sidecar wasm / resource URLs.
|
|
1676
|
+
* Prefer `assetBaseUrl`, then `facebetter-core/assets`, then the script directory.
|
|
1719
1677
|
*/
|
|
1720
|
-
function
|
|
1721
|
-
|
|
1722
|
-
|
|
1678
|
+
async function resolveRuntimeAssetUrls(options = {}) {
|
|
1679
|
+
const joinBase = (base) => {
|
|
1680
|
+
const prefix = base.endsWith('/') ? base : `${base}/`;
|
|
1681
|
+
return {
|
|
1682
|
+
wasmUrl: options.wasmAssetUrl || `${prefix}${WASM_FILE}`,
|
|
1683
|
+
resourceUrl: options.resourceAssetUrl || `${prefix}${RESOURCE_FILE}`,
|
|
1684
|
+
};
|
|
1685
|
+
};
|
|
1686
|
+
|
|
1687
|
+
if (options.wasmAssetUrl && options.resourceAssetUrl) {
|
|
1688
|
+
return {
|
|
1689
|
+
wasmUrl: options.wasmAssetUrl,
|
|
1690
|
+
resourceUrl: options.resourceAssetUrl,
|
|
1691
|
+
};
|
|
1692
|
+
}
|
|
1693
|
+
if (options.assetBaseUrl) {
|
|
1694
|
+
return joinBase(options.assetBaseUrl);
|
|
1695
|
+
}
|
|
1696
|
+
if (typeof options.wasmUrl === 'string' && /^(https?:|file:|\/)/.test(options.wasmUrl)) {
|
|
1697
|
+
return joinBase(options.wasmUrl.slice(0, options.wasmUrl.lastIndexOf('/') + 1));
|
|
1698
|
+
}
|
|
1699
|
+
|
|
1700
|
+
try {
|
|
1701
|
+
const assets = await import('facebetter-core/assets');
|
|
1702
|
+
return {
|
|
1703
|
+
wasmUrl: options.wasmAssetUrl || assets.wasmUrl,
|
|
1704
|
+
resourceUrl: options.resourceAssetUrl || assets.resourceUrl,
|
|
1705
|
+
};
|
|
1706
|
+
} catch {
|
|
1707
|
+
return joinBase(scriptDirectory());
|
|
1708
|
+
}
|
|
1709
|
+
}
|
|
1710
|
+
|
|
1711
|
+
/**
|
|
1712
|
+
* Instantiate the Emscripten factory using already-downloaded bytes.
|
|
1713
|
+
*/
|
|
1714
|
+
async function instantiateRuntime(factory, wasmBytes, resourceBytes) {
|
|
1715
|
+
if (!factory) {
|
|
1716
|
+
throw new FacebetterError(
|
|
1717
|
+
'WASM module does not export createFaceBetterModule',
|
|
1718
|
+
'WASM_LOAD_ERROR'
|
|
1719
|
+
);
|
|
1720
|
+
}
|
|
1721
|
+
|
|
1722
|
+
const module = await factory({
|
|
1723
|
+
instantiateWasm(imports, receiveInstance) {
|
|
1724
|
+
return WebAssembly.instantiate(wasmBytes, imports).then((result) => {
|
|
1725
|
+
receiveInstance(result.instance, result.module);
|
|
1726
|
+
});
|
|
1727
|
+
},
|
|
1728
|
+
});
|
|
1729
|
+
|
|
1730
|
+
if (!module) {
|
|
1731
|
+
throw new FacebetterError('Failed to initialize WASM module', 'WASM_LOAD_ERROR');
|
|
1732
|
+
}
|
|
1733
|
+
if (!module.FS) {
|
|
1734
|
+
throw new FacebetterError('Runtime filesystem is not available', 'WASM_LOAD_ERROR');
|
|
1735
|
+
}
|
|
1736
|
+
module.FS.writeFile(MEMFS_RESOURCE_PATH, resourceBytes);
|
|
1737
|
+
module.ready = true;
|
|
1738
|
+
return module;
|
|
1723
1739
|
}
|
|
1724
1740
|
|
|
1725
1741
|
/**
|
|
@@ -1778,21 +1794,19 @@ function toImageData(source, width, height) {
|
|
|
1778
1794
|
*/
|
|
1779
1795
|
|
|
1780
1796
|
|
|
1781
|
-
// ESM-specific WASM loader
|
|
1782
1797
|
let wasmModuleInstance = null;
|
|
1783
1798
|
let wasmModulePromise = null;
|
|
1784
1799
|
|
|
1785
1800
|
/**
|
|
1786
|
-
*
|
|
1787
|
-
*
|
|
1788
|
-
*
|
|
1789
|
-
* @param {
|
|
1790
|
-
* @param {string} [options.wasmUrl]
|
|
1791
|
-
* @param {Function} [options.locateFile] - Custom locateFile function (usually not needed with SINGLE_FILE=1)
|
|
1792
|
-
* @returns {Promise<Object>} Promise that resolves with the WASM module
|
|
1801
|
+
* Load the runtime factory, then download .wasm and resource.fbd with progress.
|
|
1802
|
+
* @param {Object} [options]
|
|
1803
|
+
* @param {function} [options.onProgress]
|
|
1804
|
+
* @param {string} [options.assetBaseUrl]
|
|
1805
|
+
* @param {string} [options.wasmUrl] Glue JS URL (optional)
|
|
1793
1806
|
*/
|
|
1794
1807
|
async function loadWasmModule(options = {}) {
|
|
1795
1808
|
if (wasmModuleInstance) {
|
|
1809
|
+
options.onProgress?.({ loaded: 1, total: 1, percent: 100 });
|
|
1796
1810
|
return wasmModuleInstance;
|
|
1797
1811
|
}
|
|
1798
1812
|
|
|
@@ -1801,53 +1815,32 @@ async function loadWasmModule(options = {}) {
|
|
|
1801
1815
|
}
|
|
1802
1816
|
|
|
1803
1817
|
wasmModulePromise = (async () => {
|
|
1804
|
-
// Try to import the WASM module factory
|
|
1805
|
-
// facebetter-core.js is an Emscripten-generated ESM file (with MODULARIZE and EXPORT_ES6)
|
|
1806
|
-
// It's provided by the facebetter-core npm package
|
|
1807
|
-
let FaceBetterModuleFactory;
|
|
1808
|
-
|
|
1809
1818
|
try {
|
|
1810
|
-
|
|
1819
|
+
let factory;
|
|
1811
1820
|
if (options.wasmUrl) {
|
|
1812
|
-
|
|
1813
|
-
|
|
1814
|
-
FaceBetterModuleFactory = wasmModule.default || wasmModule.createFaceBetterModule;
|
|
1821
|
+
const wasmModule = await import(/* @vite-ignore */ options.wasmUrl);
|
|
1822
|
+
factory = wasmModule.default || wasmModule.createFaceBetterModule;
|
|
1815
1823
|
} else {
|
|
1816
|
-
// Import from facebetter-core npm package
|
|
1817
1824
|
const wasmModule = await import('facebetter-core');
|
|
1818
|
-
|
|
1819
|
-
// Get the factory function (default export or named export)
|
|
1820
|
-
FaceBetterModuleFactory = wasmModule.default || wasmModule.createFaceBetterModule;
|
|
1821
|
-
}
|
|
1822
|
-
|
|
1823
|
-
if (!FaceBetterModuleFactory) {
|
|
1824
|
-
throw new Error('WASM module does not export createFaceBetterModule function');
|
|
1825
|
+
factory = wasmModule.default || wasmModule.createFaceBetterModule;
|
|
1825
1826
|
}
|
|
1826
|
-
|
|
1827
|
-
|
|
1828
|
-
|
|
1829
|
-
|
|
1830
|
-
|
|
1831
|
-
|
|
1832
|
-
|
|
1833
|
-
|
|
1834
|
-
|
|
1835
|
-
|
|
1836
|
-
|
|
1837
|
-
|
|
1838
|
-
|
|
1839
|
-
|
|
1840
|
-
|
|
1841
|
-
|
|
1842
|
-
// Initialize the module (MODULARIZE returns a Promise)
|
|
1843
|
-
wasmModuleInstance = await FaceBetterModuleFactory(moduleOptions);
|
|
1844
|
-
|
|
1845
|
-
if (!wasmModuleInstance) {
|
|
1846
|
-
throw new FacebetterError('Failed to initialize WASM module');
|
|
1827
|
+
const urls = await resolveRuntimeAssetUrls(options);
|
|
1828
|
+
const { wasmBytes, resourceBytes } = await fetchRuntimeAssets({
|
|
1829
|
+
...urls,
|
|
1830
|
+
onProgress: options.onProgress,
|
|
1831
|
+
});
|
|
1832
|
+
wasmModuleInstance = await instantiateRuntime(
|
|
1833
|
+
factory,
|
|
1834
|
+
wasmBytes,
|
|
1835
|
+
resourceBytes
|
|
1836
|
+
);
|
|
1837
|
+
return wasmModuleInstance;
|
|
1838
|
+
} catch (error) {
|
|
1839
|
+
wasmModulePromise = null;
|
|
1840
|
+
throw error instanceof FacebetterError
|
|
1841
|
+
? error
|
|
1842
|
+
: new FacebetterError(`Failed to load WASM module: ${error.message}`, 'WASM_LOAD_ERROR');
|
|
1847
1843
|
}
|
|
1848
|
-
|
|
1849
|
-
wasmModuleInstance.ready = true;
|
|
1850
|
-
return wasmModuleInstance;
|
|
1851
1844
|
})();
|
|
1852
1845
|
|
|
1853
1846
|
return wasmModulePromise;
|
|
@@ -1908,7 +1901,7 @@ function cleanupFacebetterCanvas() {
|
|
|
1908
1901
|
|
|
1909
1902
|
// Create platform API
|
|
1910
1903
|
const platformAPI = {
|
|
1911
|
-
loadWasmModule: () => loadWasmModule(),
|
|
1904
|
+
loadWasmModule: (options) => loadWasmModule(options),
|
|
1912
1905
|
getWasmModule,
|
|
1913
1906
|
getWasmBuffer,
|
|
1914
1907
|
toImageData: toImageData,
|
|
@@ -1957,12 +1950,9 @@ var index = {
|
|
|
1957
1950
|
BeautyEffectEngine: ESMBeautyEffectEngine,
|
|
1958
1951
|
EngineConfig,
|
|
1959
1952
|
FacebetterError,
|
|
1960
|
-
FACEBETTER_AUTH_URL,
|
|
1961
|
-
createDirectAuthFetcher,
|
|
1962
|
-
requestFacebetterAuth,
|
|
1963
1953
|
...constants,
|
|
1964
1954
|
loadWasmModule
|
|
1965
1955
|
};
|
|
1966
1956
|
|
|
1967
|
-
export { ESMBeautyEffectEngine as BeautyEffectEngine, BlushColor, BlushStyle, ChromaKeyColor, ContourStyle, EngineConfig, EngineEventCode, EyeLinerColor, EyeLinerStyle, EyeShadowColor, EyeShadowStyle, EyebrowColor, EyebrowStyle, EyelashColor, EyelashStyle,
|
|
1957
|
+
export { ESMBeautyEffectEngine as BeautyEffectEngine, BlushColor, BlushStyle, ChromaKeyColor, ContourStyle, EngineConfig, EngineEventCode, EyeLinerColor, EyeLinerStyle, EyeShadowColor, EyeShadowStyle, EyebrowColor, EyebrowStyle, EyelashColor, EyelashStyle, FacebetterError, FrameType, LipstickColor, MirrorMode, PupilColor, Reshape, SmoothingStyle, WhiteningStyle, createBeautyEffectEngine, index as default, getWasmBuffer, getWasmModule, loadWasmModule };
|
|
1968
1958
|
//# sourceMappingURL=facebetter.esm.js.map
|