facebetter 1.0.8 → 1.0.10
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/dist/facebetter.esm.js +154 -153
- package/dist/facebetter.esm.js.map +1 -1
- package/dist/facebetter.js +165 -59
- package/dist/facebetter.js.map +1 -1
- package/lib/index.js +23 -27
- package/package.json +5 -4
- package/README.md +0 -167
- package/dist/facebetter_wasm.data +0 -0
- package/dist/facebetter_wasm.js +0 -14
- package/dist/facebetter_wasm.wasm +0 -0
package/dist/facebetter.esm.js
CHANGED
|
@@ -230,60 +230,157 @@ class BeautyEffectEngine {
|
|
|
230
230
|
|
|
231
231
|
// Start loading WASM module immediately in constructor
|
|
232
232
|
this._wasmLoadPromise = this._loadWasmModule();
|
|
233
|
+
this._initPromise = null;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
/**
|
|
237
|
+
* Creates a timeout promise
|
|
238
|
+
* @private
|
|
239
|
+
* @param {number} timeout - Timeout in milliseconds
|
|
240
|
+
* @param {string} operation - Operation name for error message
|
|
241
|
+
* @returns {Promise} Promise that rejects after timeout
|
|
242
|
+
*/
|
|
243
|
+
_createTimeout(timeout, operation) {
|
|
244
|
+
return new Promise((_, reject) => {
|
|
245
|
+
setTimeout(() => {
|
|
246
|
+
reject(new FacebetterError(
|
|
247
|
+
`${operation} timed out after ${timeout}ms. Please check your network connection or try increasing the timeout.`,
|
|
248
|
+
'TIMEOUT'
|
|
249
|
+
));
|
|
250
|
+
}, timeout);
|
|
251
|
+
});
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
/**
|
|
255
|
+
* Enhances error with context information
|
|
256
|
+
* @private
|
|
257
|
+
* @param {Error} error - Original error
|
|
258
|
+
* @param {string} context - Context information
|
|
259
|
+
* @returns {FacebetterError} Enhanced error
|
|
260
|
+
*/
|
|
261
|
+
_enhanceError(error, context) {
|
|
262
|
+
if (error instanceof FacebetterError) {
|
|
263
|
+
return error;
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
let errorCode = 'UNKNOWN_ERROR';
|
|
267
|
+
let message = error.message || 'Unknown error occurred';
|
|
268
|
+
|
|
269
|
+
// Categorize errors
|
|
270
|
+
if (error.message?.includes('timeout') || error.message?.includes('TIMEOUT')) {
|
|
271
|
+
errorCode = 'TIMEOUT';
|
|
272
|
+
} else if (error.message?.includes('network') || error.message?.includes('fetch')) {
|
|
273
|
+
errorCode = 'NETWORK_ERROR';
|
|
274
|
+
} else if (error.message?.includes('WASM') || error.message?.includes('wasm')) {
|
|
275
|
+
errorCode = 'WASM_LOAD_ERROR';
|
|
276
|
+
} else if (error.message?.includes('license') || error.message?.includes('auth')) {
|
|
277
|
+
errorCode = 'LICENSE_ERROR';
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
return new FacebetterError(
|
|
281
|
+
context ? `${context}: ${message}` : message,
|
|
282
|
+
errorCode
|
|
283
|
+
);
|
|
233
284
|
}
|
|
234
285
|
|
|
235
286
|
/**
|
|
236
287
|
* Initializes the engine
|
|
288
|
+
* @param {Object} [options] - Initialization options
|
|
289
|
+
* @param {number} [options.timeout] - Timeout in milliseconds (default: 30000 for WASM, 10000 for auth)
|
|
290
|
+
* @param {number} [options.authTimeout] - Timeout for online authentication in milliseconds (default: 10000)
|
|
237
291
|
* @returns {Promise<void>} Promise that resolves when initialization is complete
|
|
238
292
|
*/
|
|
239
|
-
async init() {
|
|
293
|
+
async init(options = {}) {
|
|
294
|
+
// 并发控制:如果已经初始化,直接返回
|
|
240
295
|
if (this.initialized) {
|
|
241
296
|
return;
|
|
242
297
|
}
|
|
243
298
|
|
|
244
|
-
//
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
let licenseJsonToUse = this.licenseJson;
|
|
299
|
+
// 并发控制:如果正在初始化,返回同一个 Promise
|
|
300
|
+
if (this._initPromise) {
|
|
301
|
+
return this._initPromise;
|
|
302
|
+
}
|
|
249
303
|
|
|
250
|
-
//
|
|
251
|
-
|
|
304
|
+
// 创建初始化 Promise
|
|
305
|
+
this._initPromise = (async () => {
|
|
252
306
|
try {
|
|
253
|
-
|
|
254
|
-
const
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
307
|
+
const wasmTimeout = options.timeout || 30000;
|
|
308
|
+
const authTimeout = options.authTimeout || 10000;
|
|
309
|
+
|
|
310
|
+
// 等待 WASM 模块加载(带超时)
|
|
311
|
+
try {
|
|
312
|
+
await Promise.race([
|
|
313
|
+
this._wasmLoadPromise,
|
|
314
|
+
this._createTimeout(wasmTimeout, 'WASM module loading')
|
|
315
|
+
]);
|
|
316
|
+
} catch (error) {
|
|
317
|
+
throw this._enhanceError(error, 'Failed to load WASM module');
|
|
258
318
|
}
|
|
259
319
|
|
|
260
|
-
|
|
261
|
-
licenseJsonToUse = JSON.stringify(authResponse);
|
|
262
|
-
console.log('Online auth response received, using as licenseJson');
|
|
263
|
-
} catch (error) {
|
|
264
|
-
throw new FacebetterError(`Failed to verify app key online: ${error.message}`);
|
|
265
|
-
}
|
|
266
|
-
}
|
|
320
|
+
const Module = this._getWasmModule();
|
|
267
321
|
|
|
268
|
-
|
|
269
|
-
// 如果通过 appId/appKey 获取到了响应,则使用响应作为 licenseJson
|
|
270
|
-
// WASM 层只需要验证 licenseJson,不需要发送 HTTP 请求
|
|
271
|
-
const enginePtr = Module.ccall(
|
|
272
|
-
'CreateBeautyEffectEngine',
|
|
273
|
-
'number',
|
|
274
|
-
['string', 'string'],
|
|
275
|
-
[
|
|
276
|
-
this.resourcePath,
|
|
277
|
-
licenseJsonToUse || '' // 使用 licenseJson 验证
|
|
278
|
-
]
|
|
279
|
-
);
|
|
322
|
+
let licenseJsonToUse = this.licenseJson;
|
|
280
323
|
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
324
|
+
// 如果用户提供了 appId 和 appKey(但没有提供 licenseJson),则在 JS 层发送 HTTP 请求
|
|
325
|
+
if (this.appId && this.appKey && !this.licenseJson && this._verifyAppKeyOnline) {
|
|
326
|
+
try {
|
|
327
|
+
// 在线认证(带超时)
|
|
328
|
+
const authResponse = await Promise.race([
|
|
329
|
+
this._verifyAppKeyOnline(this.appId, this.appKey),
|
|
330
|
+
this._createTimeout(authTimeout, 'Online authentication')
|
|
331
|
+
]);
|
|
332
|
+
|
|
333
|
+
if (!authResponse) {
|
|
334
|
+
throw new FacebetterError(
|
|
335
|
+
'Failed to get server response from online_auth API. The server returned an empty response.',
|
|
336
|
+
'AUTH_EMPTY_RESPONSE'
|
|
337
|
+
);
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
// 将服务器响应转换为 JSON 字符串,作为 licenseJson 传递给 WASM 层
|
|
341
|
+
licenseJsonToUse = JSON.stringify(authResponse);
|
|
342
|
+
console.log('Online auth response received, using as licenseJson');
|
|
343
|
+
} catch (error) {
|
|
344
|
+
if (error instanceof FacebetterError && error.code === 'TIMEOUT') {
|
|
345
|
+
throw new FacebetterError(
|
|
346
|
+
`Online authentication timed out after ${authTimeout}ms. Please check your network connection or provide licenseJson directly.`,
|
|
347
|
+
'AUTH_TIMEOUT'
|
|
348
|
+
);
|
|
349
|
+
}
|
|
350
|
+
throw this._enhanceError(error, 'Failed to verify app key online');
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
// 如果用户直接提供了 licenseJson,则直接使用
|
|
355
|
+
// 如果通过 appId/appKey 获取到了响应,则使用响应作为 licenseJson
|
|
356
|
+
// WASM 层只需要验证 licenseJson,不需要发送 HTTP 请求
|
|
357
|
+
const enginePtr = Module.ccall(
|
|
358
|
+
'CreateBeautyEffectEngine',
|
|
359
|
+
'number',
|
|
360
|
+
['string', 'string'],
|
|
361
|
+
[
|
|
362
|
+
this.resourcePath,
|
|
363
|
+
licenseJsonToUse || '' // 使用 licenseJson 验证
|
|
364
|
+
]
|
|
365
|
+
);
|
|
366
|
+
|
|
367
|
+
if (!enginePtr) {
|
|
368
|
+
throw new FacebetterError(
|
|
369
|
+
'Failed to create BeautyEffect engine. This may be due to invalid license or resource path issues.',
|
|
370
|
+
'ENGINE_CREATE_FAILED'
|
|
371
|
+
);
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
this.enginePtr = enginePtr;
|
|
375
|
+
this.initialized = true;
|
|
376
|
+
this._initPromise = null; // 清除 Promise 缓存,允许重新初始化(如果需要)
|
|
377
|
+
} catch (error) {
|
|
378
|
+
this._initPromise = null; // 清除 Promise 缓存,允许重试
|
|
379
|
+
throw error;
|
|
380
|
+
}
|
|
381
|
+
})();
|
|
284
382
|
|
|
285
|
-
this.
|
|
286
|
-
this.initialized = true;
|
|
383
|
+
return this._initPromise;
|
|
287
384
|
}
|
|
288
385
|
|
|
289
386
|
/**
|
|
@@ -310,6 +407,7 @@ class BeautyEffectEngine {
|
|
|
310
407
|
this.enginePtr = null;
|
|
311
408
|
this.initialized = false;
|
|
312
409
|
this.bufferSize = 0;
|
|
410
|
+
this._initPromise = null; // 清除初始化 Promise
|
|
313
411
|
|
|
314
412
|
// Clean up offscreen canvas
|
|
315
413
|
if (this._offscreenCanvas) {
|
|
@@ -790,7 +888,7 @@ async function verifyAppKeyOnline(appId, appKey) {
|
|
|
790
888
|
/**
|
|
791
889
|
* ESM Entry Point
|
|
792
890
|
* For Vue/React/Vite/Webpack (ES Module)
|
|
793
|
-
* Uses
|
|
891
|
+
* Uses facebetter-core npm package for WASM module loading
|
|
794
892
|
*/
|
|
795
893
|
|
|
796
894
|
|
|
@@ -798,49 +896,13 @@ async function verifyAppKeyOnline(appId, appKey) {
|
|
|
798
896
|
let wasmModuleInstance = null;
|
|
799
897
|
let wasmModulePromise = null;
|
|
800
898
|
|
|
801
|
-
/**
|
|
802
|
-
* Gets the base path of the current module
|
|
803
|
-
* Uses import.meta.url (ES Module equivalent of __dirname)
|
|
804
|
-
* @returns {string} Base path of the current module (with trailing slash)
|
|
805
|
-
*/
|
|
806
|
-
function getModuleBasePath() {
|
|
807
|
-
try {
|
|
808
|
-
const url = new URL('.', import.meta.url);
|
|
809
|
-
return url.href;
|
|
810
|
-
} catch (e) {
|
|
811
|
-
// Fallback
|
|
812
|
-
return './';
|
|
813
|
-
}
|
|
814
|
-
}
|
|
815
|
-
|
|
816
|
-
/**
|
|
817
|
-
* Resolves a relative path to absolute URL (ESM version)
|
|
818
|
-
* Handles various path formats
|
|
819
|
-
* @param {string} relativePath - Relative path
|
|
820
|
-
* @returns {string} Absolute URL
|
|
821
|
-
*/
|
|
822
|
-
function resolvePath(relativePath) {
|
|
823
|
-
// If relativePath is already absolute, return as-is
|
|
824
|
-
if (relativePath.startsWith('http://') || relativePath.startsWith('https://') || relativePath.startsWith('//')) {
|
|
825
|
-
return relativePath;
|
|
826
|
-
}
|
|
827
|
-
|
|
828
|
-
try {
|
|
829
|
-
return new URL(relativePath, import.meta.url).href;
|
|
830
|
-
} catch (e) {
|
|
831
|
-
// Fallback
|
|
832
|
-
const base = getModuleBasePath();
|
|
833
|
-
const cleanPath = relativePath.replace(/^\.\//, '');
|
|
834
|
-
return base + cleanPath;
|
|
835
|
-
}
|
|
836
|
-
}
|
|
837
|
-
|
|
838
899
|
/**
|
|
839
900
|
* Loads the WebAssembly module (ESM version)
|
|
840
901
|
* Supports ESM format WASM modules (generated with MODULARIZE and EXPORT_ES6)
|
|
902
|
+
* With SINGLE_FILE=1, WASM binary and data files are embedded in the JS file
|
|
841
903
|
* @param {Object} [options] - Options
|
|
842
|
-
* @param {string} [options.wasmUrl] - Custom WASM URL (
|
|
843
|
-
* @param {Function} [options.locateFile] - Custom locateFile function
|
|
904
|
+
* @param {string} [options.wasmUrl] - Custom WASM module URL (defaults to facebetter-core npm package)
|
|
905
|
+
* @param {Function} [options.locateFile] - Custom locateFile function (usually not needed with SINGLE_FILE=1)
|
|
844
906
|
* @returns {Promise<Object>} Promise that resolves with the WASM module
|
|
845
907
|
*/
|
|
846
908
|
async function loadWasmModule(options = {}) {
|
|
@@ -854,72 +916,23 @@ async function loadWasmModule(options = {}) {
|
|
|
854
916
|
|
|
855
917
|
wasmModulePromise = (async () => {
|
|
856
918
|
// Try to import the WASM module factory
|
|
857
|
-
//
|
|
919
|
+
// facebetter-core.js is an Emscripten-generated ESM file (with MODULARIZE and EXPORT_ES6)
|
|
920
|
+
// It's provided by the facebetter-core npm package
|
|
858
921
|
let FaceBetterModuleFactory;
|
|
859
922
|
|
|
860
923
|
try {
|
|
861
|
-
// Get the WASM module
|
|
862
|
-
let wasmModuleUrl;
|
|
924
|
+
// Get the WASM module
|
|
863
925
|
if (options.wasmUrl) {
|
|
864
|
-
|
|
926
|
+
// If custom URL is provided, use it (for backward compatibility or custom builds)
|
|
927
|
+
const wasmModule = await import(options.wasmUrl);
|
|
928
|
+
FaceBetterModuleFactory = wasmModule.default || wasmModule.createFaceBetterModule;
|
|
865
929
|
} else {
|
|
866
|
-
//
|
|
867
|
-
|
|
868
|
-
wasmModuleUrl = resolvePath('./facebetter_wasm.js');
|
|
930
|
+
// Import from facebetter-core npm package
|
|
931
|
+
const wasmModule = await import('facebetter-core');
|
|
869
932
|
|
|
870
|
-
//
|
|
871
|
-
|
|
872
|
-
try {
|
|
873
|
-
// Try to use package exports path (Vite will handle it at runtime)
|
|
874
|
-
const pkgPath = 'facebetter/wasm.js';
|
|
875
|
-
// Use Function constructor to avoid Rollup static analysis
|
|
876
|
-
const dynamicImport = new Function('path', 'return import(path)');
|
|
877
|
-
const wasmModule = await dynamicImport(pkgPath);
|
|
878
|
-
if (wasmModule && (wasmModule.default || wasmModule.createFaceBetterModule)) {
|
|
879
|
-
// ESM format: module.default is the factory function
|
|
880
|
-
FaceBetterModuleFactory = wasmModule.default || wasmModule.createFaceBetterModule;
|
|
881
|
-
if (FaceBetterModuleFactory) {
|
|
882
|
-
// Configure locateFile for WASM and data files
|
|
883
|
-
const moduleOptions = {
|
|
884
|
-
locateFile: options.locateFile || function(path, prefix) {
|
|
885
|
-
// Auto-resolve .wasm and .data files
|
|
886
|
-
if (path.endsWith('.wasm') || path.endsWith('.data')) {
|
|
887
|
-
try {
|
|
888
|
-
if (typeof import.meta.resolve === 'function') {
|
|
889
|
-
if (path.endsWith('.wasm')) {
|
|
890
|
-
return import.meta.resolve('facebetter/wasm');
|
|
891
|
-
} else if (path.endsWith('.data')) {
|
|
892
|
-
const wasmUrl = import.meta.resolve('facebetter/wasm');
|
|
893
|
-
return wasmUrl.replace(/\.wasm$/, '.data');
|
|
894
|
-
}
|
|
895
|
-
}
|
|
896
|
-
} catch (e) {
|
|
897
|
-
// Fallback
|
|
898
|
-
}
|
|
899
|
-
return resolvePath(path);
|
|
900
|
-
}
|
|
901
|
-
return prefix + path;
|
|
902
|
-
},
|
|
903
|
-
...options
|
|
904
|
-
};
|
|
905
|
-
wasmModuleInstance = await FaceBetterModuleFactory(moduleOptions);
|
|
906
|
-
wasmModuleInstance.ready = true;
|
|
907
|
-
return wasmModuleInstance;
|
|
908
|
-
}
|
|
909
|
-
}
|
|
910
|
-
} catch (e) {
|
|
911
|
-
// Fallback to direct import below
|
|
912
|
-
}
|
|
913
|
-
}
|
|
933
|
+
// Get the factory function (default export or named export)
|
|
934
|
+
FaceBetterModuleFactory = wasmModule.default || wasmModule.createFaceBetterModule;
|
|
914
935
|
}
|
|
915
|
-
|
|
916
|
-
// Load WASM module using dynamic import (ESM format)
|
|
917
|
-
// Emscripten with MODULARIZE=1 and EXPORT_ES6=1 generates:
|
|
918
|
-
// export default function createFaceBetterModule(options) { ... }
|
|
919
|
-
const wasmModule = await import(wasmModuleUrl);
|
|
920
|
-
|
|
921
|
-
// Get the factory function (default export or named export)
|
|
922
|
-
FaceBetterModuleFactory = wasmModule.default || wasmModule.createFaceBetterModule;
|
|
923
936
|
|
|
924
937
|
if (!FaceBetterModuleFactory) {
|
|
925
938
|
throw new Error('WASM module does not export createFaceBetterModule function');
|
|
@@ -928,25 +941,13 @@ async function loadWasmModule(options = {}) {
|
|
|
928
941
|
throw new FacebetterError(`Failed to load WASM module: ${e.message}`);
|
|
929
942
|
}
|
|
930
943
|
|
|
931
|
-
// Configure
|
|
944
|
+
// Configure module options
|
|
945
|
+
// With SINGLE_FILE=1, WASM binary and data files are embedded in the JS file
|
|
946
|
+
// No need to handle .wasm and .data file paths separately
|
|
932
947
|
const moduleOptions = {
|
|
933
948
|
locateFile: options.locateFile || function(path, prefix) {
|
|
934
|
-
//
|
|
935
|
-
if
|
|
936
|
-
try {
|
|
937
|
-
if (typeof import.meta.resolve === 'function') {
|
|
938
|
-
if (path.endsWith('.wasm')) {
|
|
939
|
-
return import.meta.resolve('facebetter/wasm');
|
|
940
|
-
} else if (path.endsWith('.data')) {
|
|
941
|
-
const wasmUrl = import.meta.resolve('facebetter/wasm');
|
|
942
|
-
return wasmUrl.replace(/\.wasm$/, '.data');
|
|
943
|
-
}
|
|
944
|
-
}
|
|
945
|
-
} catch (e) {
|
|
946
|
-
// Fallback
|
|
947
|
-
}
|
|
948
|
-
return resolvePath(path);
|
|
949
|
-
}
|
|
949
|
+
// With SINGLE_FILE=1, Emscripten handles embedded files automatically
|
|
950
|
+
// Custom locateFile is only needed if user provides one
|
|
950
951
|
return prefix + path;
|
|
951
952
|
},
|
|
952
953
|
...options
|