facebetter 1.5.1 → 2.0.0

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.
@@ -4,238 +4,312 @@
4
4
  (global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global.Facebetter = {}));
5
5
  })(this, (function (exports) { 'use strict';
6
6
 
7
- /**
8
- * Facebetter Error Classes
9
- * Platform-agnostic error handling
10
- */
11
-
12
- /**
13
- * Facebetter error class
14
- */
15
- class FacebetterError extends Error {
16
- constructor(message, code = -1) {
17
- super(message);
18
- this.name = 'FacebetterError';
19
- this.code = code;
20
- }
21
- }
22
-
23
- /**
24
- * Checks if a result code indicates success
25
- * @param {number} result - Result code from WASM function
26
- * @throws {FacebetterError} If result indicates failure
27
- */
28
- function checkResult(result, errorMessage = 'Operation failed') {
29
- if (result !== 0) {
30
- throw new FacebetterError(`${errorMessage} (error code: ${result})`, result);
31
- }
32
- }
33
-
34
7
  /**
35
8
  * Engine Configuration
36
9
  * Platform-agnostic configuration class
37
10
  */
38
11
 
39
-
40
12
  /**
41
13
  * Engine configuration class
42
- * Similar to EngineConfig struct in C++/Java/OC
14
+ * Analogous to EngineConfig in C++ / Java / Objective-C, with Web-only auth fields.
43
15
  */
44
16
  class EngineConfig {
45
17
  /**
46
- * Creates a new EngineConfig instance
47
- * @param {Object} config - Configuration object
48
- * @param {string} [config.appId] - Application ID (required if licenseJson is not provided)
49
- * @param {string} [config.appKey] - Application key (required if licenseJson is not provided)
50
- * @param {string} [config.licenseJson] - License JSON string (optional, if provided, appId and appKey are not required)
18
+ * @param {Object} config
19
+ * @param {string} [config.licenseToken] Compact JWS, or the raw `{success, token}`
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
+ * @param {boolean} [config.externalContext] Kept for cross-platform alignment;
28
+ * unused on Web/WASM today.
51
29
  */
52
30
  constructor(config = {}) {
53
- this.appId = config.appId || null;
54
- this.appKey = config.appKey || null;
55
- this.licenseJson = config.licenseJson || null;
31
+ this.licenseToken = config.licenseToken || null;
32
+ this.authProxyUrl = config.authProxyUrl || null;
33
+ this.fetchAuthResponse = config.fetchAuthResponse || null;
56
34
  this.resourcePath = '/resource.fbd';
57
35
  /**
58
36
  * Whether to use an external GL context (native platforms only).
59
- * In Web/WASM environments, this field is kept for configuration structure alignment,
60
- * but it does not take effect in the current implementation.
37
+ * On Web/WASM this field is kept for config-shape alignment and has no effect.
61
38
  */
62
39
  this.externalContext = !!config.externalContext;
63
40
  }
64
41
 
65
- /**
66
- * Validates the configuration
67
- * @returns {boolean} True if valid
68
- */
69
42
  isValid() {
70
- // If licenseJson is provided, use it for validation
71
- if (this.licenseJson) {
72
- return typeof this.licenseJson === 'string' && this.licenseJson.trim() !== '';
43
+ if (this.licenseToken && typeof this.licenseToken === 'string' &&
44
+ this.licenseToken.trim() !== '') {
45
+ return true;
73
46
  }
74
- // Otherwise appId and appKey are required
75
- return this.appId && typeof this.appId === 'string' && this.appId.trim() !== '' &&
76
- this.appKey && typeof this.appKey === 'string' && this.appKey.trim() !== '';
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;
77
55
  }
78
56
 
79
- /**
80
- * Returns a string representation of the config
81
- * @returns {string} String representation
82
- */
83
57
  toString() {
84
- return `EngineConfig{appId='${this.appId ? this.appId.substring(0, Math.min(this.appId.length, 8)) + '...' : 'null'}', appKey='${this.appKey ? '***' : 'null'}', licenseJson=${this.licenseJson ? 'provided' : 'null'}}`;
58
+ const mode = this.licenseToken
59
+ ? 'licenseToken'
60
+ : this.fetchAuthResponse
61
+ ? 'fetchAuthResponse'
62
+ : this.authProxyUrl
63
+ ? 'authProxyUrl'
64
+ : 'invalid';
65
+ return `EngineConfig{mode='${mode}'}`;
85
66
  }
86
67
  }
87
68
 
88
69
  /**
89
- * Facebetter Constants and Enums
90
- * Platform-agnostic constants
70
+ * Facebetter Error Classes
71
+ * Platform-agnostic error handling
91
72
  */
92
73
 
93
74
  /**
94
- * Beauty type enumeration
75
+ * Facebetter error class
95
76
  */
96
- const BeautyType$1 = {
97
- Basic: 0, // Basic beauty (smoothing, whitening, etc.)
98
- Reshape: 1, // Face reshaping (face thinning, big eyes, etc.)
99
- Makeup: 2, // Makeup effects (lipstick, blush, etc.)
100
- VirtualBackground: 3, // Virtual background (blur, image replacement)
101
- ChromaKey: 4, // Chroma key (green screen removal)
102
- Filter: 5, // LUT based filter
103
- Sticker: 6 // 2D/3D sticker
104
- };
77
+ class FacebetterError extends Error {
78
+ constructor(message, code = -1) {
79
+ super(message);
80
+ this.name = 'FacebetterError';
81
+ this.code = code;
82
+ }
83
+ }
105
84
 
106
85
  /**
107
- * Basic beauty parameter enumeration
108
- * All values should be in range [0.0, 1.0]
86
+ * Checks if a result code indicates success
87
+ * @param {number} result - Result code from WASM function
88
+ * @throws {FacebetterError} If result indicates failure
109
89
  */
110
- const BasicParam$1 = {
111
- Smoothing: 0, // Skin smoothing (0.0: none, 1.0: maximum)
112
- Sharpening: 1, // Image sharpening (0.0: none, 1.0: maximum)
113
- Whitening: 2, // Skin whitening (0.0: none, 1.0: maximum)
114
- Rosiness: 3 // Skin rosiness (0.0: none, 1.0: maximum)
115
- };
90
+ function checkResult(result, errorMessage = 'Operation failed') {
91
+ if (result !== 0) {
92
+ throw new FacebetterError(`${errorMessage} (error code: ${result})`, result);
93
+ }
94
+ }
116
95
 
117
96
  /**
118
- * Face reshape parameter enumeration
119
- * All values should be in range [0.0, 1.0]
97
+ * Facebetter Constants and Enums
98
+ * Platform-agnostic constants. Ordinals match facebetter::beauty_params.
120
99
  */
121
- const ReshapeParam$1 = {
122
- FaceThinning: 0, // Face thinning (0.0: none, 1.0: maximum)
123
- FaceVShape: 1, // V-shape face (0.0: none, 1.0: maximum)
124
- FaceNarrowing: 2, // Face narrowing (0.0: none, 1.0: maximum)
125
- FaceShortening: 3,// Face shortening (0.0: none, 1.0: maximum)
126
- Cheekbone: 4, // Cheekbone slimming (0.0: none, 1.0: maximum)
127
- Jawbone: 5, // Jawbone slimming (0.0: none, 1.0: maximum)
128
- Chin: 6, // Chin length adjustment (0.0: none, 1.0: maximum)
129
- NoseSlimming: 7, // Nose slimming (0.0: none, 1.0: maximum)
130
- EyeSize: 8, // Eye size (0.0: normal, 1.0: maximum)
131
- EyeDistance: 9 // Eye distance (0.0: normal, 1.0: maximum)
100
+
101
+ const WhiteningStyle$1 = {
102
+ ColdWhite: 0, // Cool white
103
+ PinkWhite: 1, // Pink white
104
+ WarmWhite: 2, // Warm white
105
+ Wheat: 3, // Light wheat / olive
106
+ Tan: 4 // Tan / bronzed
132
107
  };
133
108
 
134
- /**
135
- * Makeup parameter enumeration
136
- * All values should be in range [0.0, 1.0]
137
- */
138
- const MakeupParam$1 = {
139
- Lipstick: 0, // Lipstick intensity (0.0: none, 1.0: maximum)
140
- Blush: 1 // Blush intensity (0.0: none, 1.0: maximum)
109
+ const SmoothingStyle$1 = {
110
+ Natural: 0, // Natural: keeps pores
111
+ Texture: 1, // Cleaner skin while retaining texture
112
+ Smooth: 2 // Creamy / porcelain finish
141
113
  };
142
114
 
143
115
  /**
144
- * Lipstick style types
116
+ * Face reshape parameters.
117
+ * Values match facebetter::beauty_params::Reshape.
118
+ * Intensity range is [-1.0, 1.0]; 0 is off. Comments list + / - directions.
145
119
  */
146
- const LipstickStyle$1 = {
147
- Rouge: 0, // Rose red
148
- Coral: 1, // Coral
149
- Pink: 2 // Pink
120
+ const Reshape$1 = {
121
+ FaceThin: 0, // +slim face / -fuller cheeks
122
+ FaceVShape: 1, // +V-shape jaw / -square jaw
123
+ FaceNarrow: 2, // +narrow face / -wider face
124
+ FaceShort: 3, // +shorter face / -longer face
125
+ Cheekbone: 4, // +slim cheekbones / -wider cheekbones
126
+ Jawbone: 5, // +slim jaw / -wider jaw
127
+ Chin: 6, // +longer chin / -shorter chin
128
+ NoseSlim: 7, // +slimmer nose / -wider nose
129
+ EyeSize: 8, // +larger eyes / -smaller eyes
130
+ EyeDistance: 9, // +wider eye spacing / -closer eyes
131
+ FaceSmall: 10, // +smaller face / -larger face
132
+ Forehead: 11, // +fuller forehead / -lower forehead
133
+ NoseLong: 12, // +longer nose / -shorter nose
134
+ Philtrum: 13, // +shorter philtrum / -longer philtrum
135
+ MouthSize: 14, // +larger mouth / -smaller mouth
136
+ MouthPosition: 15, // +mouth lower / -mouth higher
137
+ MouthSmile: 16, // +smile lift / -droop corners
138
+ LipThickness: 17, // +thicker lips / -thinner lips
139
+ EyeRound: 18, // +rounder eyes / -narrower eyes
140
+ EyePosition: 19, // +eyes lower / -eyes higher
141
+ EyeAngle: 20, // +outer corner up / -outer corner down
142
+ EyeCornerOpen: 21, // +open eye corners / -close eye corners
143
+ LowerEyelid: 22, // +lower eyelid down / -lift lower eyelid
144
+ BrowPosition: 23, // +brows higher / -brows lower
145
+ BrowDistance: 24, // +wider brow spacing / -closer brows
146
+ BrowThickness: 25 // +thicker brows / -thinner brows
147
+ };
148
+
149
+ const EngineEventCode$1 = {
150
+ LicenseValidationSuccess: 0,
151
+ LicenseValidationFailed: 1,
152
+ InitializationComplete: 100,
153
+ InitializationFailed: 101
154
+ };
155
+
156
+ const LipstickColor$1 = {
157
+ Rouge: 0, // Classic rose
158
+ RetroRed: 1, // Retro red
159
+ Peach: 2, // Peach
160
+ CoralOrange: 3, // Coral orange
161
+ GentlePink: 4, // Soft pink
162
+ VitalityOrange: 5 // Bright orange
150
163
  };
151
164
 
152
- /**
153
- * Blush style types
154
- */
155
165
  const BlushStyle$1 = {
156
- Classic: 0, // Classic
157
- Peach: 1, // Peach
158
- Rose: 2 // Rose
166
+ SunKissed: 0, // Sun-kissed sweep across cheekbones and bridge
167
+ Igari: 1, // Flushed band across the nose bridge
168
+ Soft: 2, // Soft dual cheeks
169
+ Apple: 3, // Round apple cheeks
170
+ Classic: 4, // Classic two spots
171
+ Doll: 5, // Cheeks + nose tip
172
+ Rose: 6 // Rose dual cheeks
159
173
  };
160
174
 
161
- /**
162
- * Chroma Key parameter enumeration
163
- */
164
- const ChromaKeyParam = {
165
- KeyColor: 0, // Key color (0.0: Green, 1.0: Blue, 2.0: Red)
166
- Similarity: 1, // Color similarity (0.0 - 1.0)
167
- Smoothness: 2, // Edge smoothness (0.0 - 1.0)
168
- Desaturation: 3 // Spill desaturation (0.0 - 1.0)
175
+ const BlushColor$1 = {
176
+ CoralPink: 0, // Coral pink
177
+ DustyRose: 1, // Dusty rose
178
+ VividRed: 2, // Vivid red
179
+ Berry: 3, // Berry
180
+ SunsetOrange: 4 // Sunset orange
169
181
  };
170
182
 
171
- /**
172
- * Background mode enumeration
173
- */
174
- const BackgroundMode$1 = {
175
- None: 0, // No background processing
176
- Blur: 1, // Blurred background
177
- Image: 2 // Background image replacement
183
+ const ContourStyle$1 = {
184
+ Natural: 0, // Soft everyday contour
185
+ Sculpt: 1, // Deeper sculpted contour
186
+ Glow: 2, // Highlight-focused
187
+ Slim: 3, // Slim cheekbones
188
+ Nose: 4, // Nose bridge lift
189
+ Glam: 5 // Spot highlights
178
190
  };
179
191
 
180
- /**
181
- * Frame type enumeration
182
- */
183
- const FrameType$1 = {
184
- Image: 0, // Image mode
185
- Video: 1 // Video mode
192
+ const EyeShadowStyle$1 = {
193
+ Soft: 0, // Soft wash
194
+ Crease: 1, // Crease deepen
195
+ Smoky: 2, // Smoky surround
196
+ Halo: 3, // Halo around the eye
197
+ Glow: 4, // Outer-corner glow
198
+ Drama: 5, // Full dramatic cover
199
+ Warm: 6 // Warm-tone wash
186
200
  };
187
201
 
188
- /**
189
- * Mirror mode enumeration (apply to input before processing)
190
- */
191
- const MirrorMode$1 = {
192
- None: 0, // No mirror
193
- Horizontal: 1, // Mirror horizontally (e.g. front camera selfie)
194
- Vertical: 2, // Mirror vertically
195
- Both: 3 // Mirror both axes
202
+ const EyeShadowColor$1 = {
203
+ Plum: 0, // Plum / mauve (default)
204
+ Brown: 1, // Warm brown
205
+ Gold: 2, // Soft gold
206
+ Pink: 3 // Dusty pink
196
207
  };
197
208
 
198
- /**
199
- * Virtual background options class
200
- * Matches the C++/Java/OC API design
201
- */
202
- let VirtualBackgroundOptions$1 = class VirtualBackgroundOptions {
203
- /**
204
- * Creates a new VirtualBackgroundOptions instance
205
- * @param {Object} [options] - Options object
206
- * @param {number} [options.mode] - Background mode (use BackgroundMode enum)
207
- * @param {ImageData|HTMLImageElement|HTMLCanvasElement} [options.backgroundImage] - Background image (required when mode is Image)
208
- */
209
- constructor(options = {}) {
210
- this.mode = options.mode !== undefined ? options.mode : BackgroundMode$1.None;
211
- this.backgroundImage = options.backgroundImage || null;
212
- }
209
+ const EyeLinerStyle$1 = {
210
+ Classic: 0, // Full liner with winged tip
211
+ Flick: 1, // Extended outer flick
212
+ CatEye: 2, // Short cat-eye lift
213
+ Natural: 3, // Thin soft natural line
214
+ Bold: 4, // Bold cover
215
+ Soft: 5 // Soft feathered wing
216
+ };
213
217
 
214
- /**
215
- * Validates the options
216
- * @returns {boolean} True if valid
217
- */
218
- isValid() {
219
- if (this.mode === BackgroundMode$1.Image) {
220
- return this.backgroundImage !== null;
221
- }
222
- return true;
223
- }
218
+ const EyeLinerColor$1 = {
219
+ Burgundy: 0, // Burgundy brown
220
+ Plum: 1, // Deep plum
221
+ Chocolate: 2, // Chocolate brown
222
+ Coffee: 3, // Near-black coffee
223
+ Mauve: 4 // Mauve grey
224
+ };
225
+
226
+ const EyebrowStyle$1 = {
227
+ Natural: 0, // Natural arch with hair strokes
228
+ Soft: 1, // Soft powdery thick brow
229
+ Feathered: 2, // Feathered hair strokes
230
+ Mist: 3, // Light powder mist
231
+ Arched: 4, // Classic high arch
232
+ Powder: 5, // Dense powder fill
233
+ Wild: 6, // Wispy upper edge
234
+ Full: 7, // Full balanced brow
235
+ Straight: 8 // Straight low arch
236
+ };
237
+
238
+ const EyebrowColor$1 = {
239
+ DarkBrown: 0, // Dark brown (default)
240
+ Black: 1, // Black
241
+ SoftBrown: 2 // Soft brown
242
+ };
243
+
244
+ const EyelashStyle$1 = {
245
+ Classic: 0, // Balanced clusters + lower lashes
246
+ Manga: 1, // Bold spiked clusters
247
+ Winged: 2, // Extended outer corner
248
+ Wispy: 3, // Fluffy lifted tips
249
+ Clustered: 4, // Distinct clusters
250
+ Doll: 5 // Short doll lashes
251
+ };
252
+
253
+ const EyelashColor$1 = {
254
+ Black: 0, // Near-black (default)
255
+ Brown: 1, // Soft brown
256
+ SoftBlack: 2 // Slightly softer black
257
+ };
258
+
259
+ const PupilColor$1 = {
260
+ Hazel: 0, // Amber / honey brown
261
+ Ice: 1, // Icy blue
262
+ Mocha: 2, // Dark brown with sparkle
263
+ Olive: 3, // Forest green
264
+ Gloss: 4, // Glossy dark brown
265
+ Moss: 5, // Muted olive
266
+ Sand: 6, // Sandy beige brown
267
+ Glow: 7, // Lower-iris crescent highlight
268
+ Slate: 8 // Cool blue-grey
269
+ };
270
+
271
+ const ChromaKeyColor$1 = {
272
+ Green: 0,
273
+ Blue: 1,
274
+ Red: 2
275
+ };
276
+
277
+ /** Frame type for processing (affects temporal smoothing / tracking). */
278
+ const FrameType$1 = {
279
+ Image: 0, // Still image
280
+ Video: 1 // Continuous video stream
281
+ };
282
+
283
+ /** Mirror mode applied to the input before processing. */
284
+ const MirrorMode$1 = {
285
+ None: 0, // No mirror
286
+ Horizontal: 1, // Horizontal (e.g. front-camera selfie)
287
+ Vertical: 2, // Vertical
288
+ Both: 3 // Both axes
224
289
  };
225
290
 
226
291
  var constants = /*#__PURE__*/Object.freeze({
227
292
  __proto__: null,
228
- BackgroundMode: BackgroundMode$1,
229
- BasicParam: BasicParam$1,
230
- BeautyType: BeautyType$1,
293
+ BlushColor: BlushColor$1,
231
294
  BlushStyle: BlushStyle$1,
232
- ChromaKeyParam: ChromaKeyParam,
295
+ ChromaKeyColor: ChromaKeyColor$1,
296
+ ContourStyle: ContourStyle$1,
297
+ EngineEventCode: EngineEventCode$1,
298
+ EyeLinerColor: EyeLinerColor$1,
299
+ EyeLinerStyle: EyeLinerStyle$1,
300
+ EyeShadowColor: EyeShadowColor$1,
301
+ EyeShadowStyle: EyeShadowStyle$1,
302
+ EyebrowColor: EyebrowColor$1,
303
+ EyebrowStyle: EyebrowStyle$1,
304
+ EyelashColor: EyelashColor$1,
305
+ EyelashStyle: EyelashStyle$1,
233
306
  FrameType: FrameType$1,
234
- LipstickStyle: LipstickStyle$1,
235
- MakeupParam: MakeupParam$1,
307
+ LipstickColor: LipstickColor$1,
236
308
  MirrorMode: MirrorMode$1,
237
- ReshapeParam: ReshapeParam$1,
238
- VirtualBackgroundOptions: VirtualBackgroundOptions$1
309
+ PupilColor: PupilColor$1,
310
+ Reshape: Reshape$1,
311
+ SmoothingStyle: SmoothingStyle$1,
312
+ WhiteningStyle: WhiteningStyle$1
239
313
  });
240
314
 
241
315
  /**
@@ -272,17 +346,28 @@
272
346
  console.warn(formatLogLine('warning', message));
273
347
  }
274
348
 
275
- /** @param {string} message */
276
- function logError(message) {
277
- console.error(formatLogLine('error', message));
278
- }
279
-
280
349
  /**
281
350
  * Beauty Effect Engine Core
282
351
  * Platform-agnostic engine implementation with dependency injection
283
352
  */
284
353
 
285
354
 
355
+ /** Byte layouts of fb_engine_config_t / fb_log_config_t on wasm32. */
356
+ const FB_ENGINE_CONFIG_SIZE = 28;
357
+ const FB_LOG_CONFIG_SIZE = 16;
358
+
359
+ function allocUtf8(Module, text) {
360
+ const value = text || '';
361
+ const len = Module.lengthBytesUTF8(value) + 1;
362
+ const ptr = Module._malloc(len);
363
+ Module.stringToUTF8(value, ptr, len);
364
+ return ptr;
365
+ }
366
+
367
+ function writeI32(Module, ptr, index, value) {
368
+ Module.HEAP32[(ptr >> 2) + index] = value;
369
+ }
370
+
286
371
  /**
287
372
  * Beauty effect engine class
288
373
  * Provides high-level API for face beauty effects processing
@@ -297,9 +382,8 @@
297
382
  * @param {Function} platformAPI.getWasmModule - Get WASM module instance
298
383
  * @param {Function} platformAPI.getWasmBuffer - Get WASM memory buffer
299
384
  * @param {Function} platformAPI.toImageData - Convert to ImageData
300
- * @param {Function} platformAPI.verifyAppKeyOnline - Verify app key online
301
- * @param {Function} [platformAPI.ensureGPUPixelCanvas] - Ensure GPU canvas exists (browser only)
302
- * @param {Function} [platformAPI.cleanupGPUPixelCanvas] - Cleanup GPU canvas (browser only)
385
+ * @param {Function} [platformAPI.ensureFacebetterCanvas] - Ensure GPU canvas exists (browser only)
386
+ * @param {Function} [platformAPI.cleanupFacebetterCanvas] - Cleanup GPU canvas (browser only)
303
387
  */
304
388
  constructor(config, platformAPI) {
305
389
  if (!config) {
@@ -325,9 +409,9 @@
325
409
  }
326
410
 
327
411
  this.config = engineConfig;
328
- this.appId = engineConfig.appId;
329
- this.appKey = engineConfig.appKey;
330
- this.licenseJson = engineConfig.licenseJson;
412
+ this.licenseToken = engineConfig.licenseToken;
413
+ this.authProxyUrl = engineConfig.authProxyUrl;
414
+ this.fetchAuthResponse = engineConfig.fetchAuthResponse;
331
415
  this.resourcePath = '/resource.fbd';
332
416
  this.enginePtr = null;
333
417
  this.initialized = false;
@@ -345,18 +429,18 @@
345
429
  this._getWasmModule = platformAPI.getWasmModule;
346
430
  this._getWasmBuffer = platformAPI.getWasmBuffer;
347
431
  this._toImageData = platformAPI.toImageData;
348
- this._ensureGPUPixelCanvas = platformAPI.ensureGPUPixelCanvas;
349
- this._cleanupGPUPixelCanvas = platformAPI.cleanupGPUPixelCanvas;
432
+ this._ensureFacebetterCanvas = platformAPI.ensureFacebetterCanvas;
433
+ this._cleanupFacebetterCanvas = platformAPI.cleanupFacebetterCanvas;
350
434
 
351
435
  // Browser-specific state
352
- this._gpupixelCanvas = null;
353
- this._createdGPUPixelCanvas = false;
436
+ this._facebetterCanvas = null;
437
+ this._createdFacebetterCanvas = false;
354
438
  this._offscreenCanvas = null;
355
439
  this._offscreenCtx = null;
356
440
 
357
- // Automatically create gpupixel_canvas if it doesn't exist (browser only)
358
- if (this._ensureGPUPixelCanvas) {
359
- this._ensureGPUPixelCanvas();
441
+ // Automatically create facebetter_canvas if it doesn't exist (browser only)
442
+ if (this._ensureFacebetterCanvas) {
443
+ this._ensureFacebetterCanvas();
360
444
  }
361
445
 
362
446
  // Start loading WASM module immediately in constructor
@@ -453,6 +537,10 @@
453
537
  if (!Module.onReportUsage) {
454
538
  Module.onReportUsage = async (payloadJson) => {
455
539
  try {
540
+ const payload = JSON.parse(payloadJson);
541
+ if (!payload.app_id || !payload.hmac_signature) {
542
+ return true;
543
+ }
456
544
  const url = 'https://facebetter.pixpark.net/facebetter/v1/report';
457
545
  const response = await fetch(url, {
458
546
  method: 'POST',
@@ -468,48 +556,97 @@
468
556
  };
469
557
  }
470
558
 
471
- // Setup online auth proxy for WASM(纯在线,失败不回退本地缓存)。
472
- if (!Module.onOnlineAuth) {
473
- Module.onOnlineAuth = async (payloadJson) => {
474
- const authUrl = 'https://facebetter.pixpark.net/facebetter/v1/auth';
475
- logInfo('Online license: requesting auth server');
476
- try {
477
- const response = await fetch(authUrl, {
478
- method: 'POST',
479
- headers: {
480
- 'Content-Type': 'application/json',
481
- },
482
- body: payloadJson,
483
- });
484
- if (!response.ok) {
485
- logWarn(`Online license: HTTP ${response.status}`);
559
+ // Web v2 auth: never accept appId/appKey in the browser.
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
563
+ if (!this.licenseToken) {
564
+ if (typeof this.fetchAuthResponse === 'function') {
565
+ Module.onOnlineAuth = async (payloadJson) => {
566
+ try {
567
+ const challenge = JSON.parse(payloadJson);
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
+ }
486
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
+ });
487
588
  }
488
- const text = await response.text();
489
- logInfo('Online license: server response ok');
490
- return text;
491
- } catch (err) {
492
- logWarn(
493
- `Online license: network error${err?.message ? `: ${err.message}` : ''}`
494
- );
495
- return '';
496
- }
497
- };
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
+ }
498
619
  }
499
620
 
500
- // Create engine instance, auth (online/offline) is handled by WASM layer via onOnlineAuth
501
- const enginePtr = Module.ccall(
502
- 'CreateBeautyEffectEngineEx',
503
- 'number',
504
- ['string', 'string', 'string', 'string', 'number'],
505
- [
506
- this.resourcePath,
507
- this.licenseJson || '',
508
- this.appId || '',
509
- this.appKey || '',
510
- this.config.externalContext ? 1 : 0
511
- ]
512
- );
621
+ // C ABI still has app_id/app_key slots; leave them empty on Web.
622
+ const appIdPtr = allocUtf8(Module, '');
623
+ const appKeyPtr = allocUtf8(Module, '');
624
+ const licensePtr = allocUtf8(Module, this.licenseToken);
625
+ const resourcePtr = allocUtf8(Module, this.resourcePath);
626
+ const configPtr = Module._malloc(FB_ENGINE_CONFIG_SIZE);
627
+ writeI32(Module, configPtr, 0, appIdPtr);
628
+ writeI32(Module, configPtr, 1, appKeyPtr);
629
+ writeI32(Module, configPtr, 2, licensePtr);
630
+ writeI32(Module, configPtr, 3, resourcePtr);
631
+ writeI32(Module, configPtr, 4, 0);
632
+ writeI32(Module, configPtr, 5, this.config.externalContext ? 1 : 0);
633
+ writeI32(Module, configPtr, 6, 0);
634
+
635
+ let enginePtr = 0;
636
+ try {
637
+ enginePtr = Module.ccall(
638
+ 'fb_engine_create',
639
+ 'number',
640
+ ['number'],
641
+ [configPtr]
642
+ );
643
+ } finally {
644
+ Module._free(configPtr);
645
+ Module._free(appIdPtr);
646
+ Module._free(appKeyPtr);
647
+ Module._free(licensePtr);
648
+ Module._free(resourcePtr);
649
+ }
513
650
 
514
651
  if (!enginePtr) {
515
652
  throw new FacebetterError(
@@ -557,7 +694,10 @@
557
694
  this._callbackSharedBufferSize = 0;
558
695
  }
559
696
 
560
- Module.ccall('DestroyBeautyEffectEngine', null, ['number'], [this.enginePtr]);
697
+ Module.ccall('fb_engine_destroy', null, ['number'], [this.enginePtr]);
698
+ if (Module._engine_event_callbacks) {
699
+ delete Module._engine_event_callbacks[this.enginePtr];
700
+ }
561
701
  this.enginePtr = null;
562
702
  this.initialized = false;
563
703
  this.bufferSize = 0;
@@ -569,15 +709,15 @@
569
709
  this._offscreenCtx = null;
570
710
  }
571
711
 
572
- // Clean up gpupixel_canvas if we created it
573
- if (this._cleanupGPUPixelCanvas) {
574
- this._cleanupGPUPixelCanvas();
712
+ // Clean up facebetter_canvas if we created it
713
+ if (this._cleanupFacebetterCanvas) {
714
+ this._cleanupFacebetterCanvas();
575
715
  }
576
716
  }
577
717
 
578
718
  /**
579
719
  * Sets log configuration
580
- * Can be called before init() since SetLogConfig is a global function
720
+ * Can be called before init() since fb_set_log_config is a global function
581
721
  * @param {Object} config - Log configuration
582
722
  * @param {boolean} config.consoleEnabled - Enable console logging
583
723
  * @param {boolean} config.fileEnabled - Enable file logging
@@ -590,406 +730,415 @@
590
730
  await this._wasmLoadPromise;
591
731
  const Module = this._getWasmModule();
592
732
 
593
- const result = Module.ccall(
594
- 'SetLogConfig',
595
- 'number',
596
- ['bool', 'bool', 'number', 'string'],
597
- [
598
- config.consoleEnabled || false,
599
- config.fileEnabled || false,
600
- config.level || 0,
601
- config.fileName || ''
602
- ]
603
- );
733
+ const fileNamePtr = allocUtf8(Module, config.fileName);
734
+ const configPtr = Module._malloc(FB_LOG_CONFIG_SIZE);
735
+ writeI32(Module, configPtr, 0, config.consoleEnabled ? 1 : 0);
736
+ writeI32(Module, configPtr, 1, config.fileEnabled ? 1 : 0);
737
+ writeI32(Module, configPtr, 2, config.level || 0);
738
+ writeI32(Module, configPtr, 3, fileNamePtr);
739
+
740
+ let result;
741
+ try {
742
+ result = Module.ccall(
743
+ 'fb_set_log_config',
744
+ 'number',
745
+ ['number'],
746
+ [configPtr]
747
+ );
748
+ } finally {
749
+ Module._free(configPtr);
750
+ Module._free(fileNamePtr);
751
+ }
604
752
 
605
753
  checkResult(result, 'Failed to set log config');
606
754
  }
607
755
 
608
756
  /**
609
- * [Deprecated] No-op in parameter-driven mode. Use:
610
- * - setBasicParam/setReshapeParam/setMakeupParam
611
- * - setVirtualBackground
612
- * - setFilter/setSticker
613
- * Kept for compatibility and may be removed in a future release.
614
- * @deprecated
615
- * @param {number} beautyType - Beauty type (use BeautyType enum)
616
- * @param {boolean} enabled - Enable or disable
757
+ * Sets skin smoothing intensity
758
+ * @param {number} value - Parameter value (0.0 - 1.0)
617
759
  */
618
- setBeautyTypeEnabled(beautyType, enabled) {
760
+ setSmoothing(value) {
619
761
  this._ensureInitialized();
620
- const Module = this._getWasmModule();
621
-
622
- const result = Module.ccall(
623
- 'SetBeautyTypeEnabled',
624
- 'number',
625
- ['number', 'number', 'number'],
626
- [this.enginePtr, beautyType, enabled ? 1 : 0]
627
- );
762
+ this._setIntensity('fb_set_smoothing', value);
763
+ }
628
764
 
629
- checkResult(result, `Failed to ${enabled ? 'enable' : 'disable'} beauty type`);
765
+ /**
766
+ * Sets skin smoothing style
767
+ * @param {number} style - Style (use SmoothingStyle enum, e.g., SmoothingStyle.Natural)
768
+ */
769
+ setSmoothingStyle(style) {
770
+ this._setMakeupStyle('fb_set_smoothing_style', style, 'Failed to set smoothing style');
630
771
  }
631
772
 
632
773
  /**
633
- * [Deprecated] Always returns false in parameter-driven mode.
634
- * Query your own UI state or parameters instead.
635
- * Kept for compatibility and may be removed in a future release.
636
- * @deprecated
637
- * @param {number} beautyType - Beauty type (use BeautyType enum)
638
- * @returns {boolean} True if enabled
774
+ * Sets skin whitening intensity
775
+ * @param {number} value - Parameter value (0.0 - 1.0)
639
776
  */
640
- isBeautyTypeEnabled(beautyType) {
777
+ setWhitening(value) {
641
778
  this._ensureInitialized();
642
- const Module = this._getWasmModule();
643
-
644
- const result = Module.ccall(
645
- 'IsBeautyTypeEnabled',
646
- 'number',
647
- ['number', 'number'],
648
- [this.enginePtr, beautyType]
649
- );
650
-
651
- if (result === -1) {
652
- throw new FacebetterError('Failed to check beauty type status');
653
- }
779
+ this._setIntensity('fb_set_whitening', value);
780
+ }
654
781
 
655
- return result === 1;
782
+ /**
783
+ * Sets whitening style by swapping the custom LUT
784
+ * @param {number} style - Style (use WhiteningStyle enum, e.g., WhiteningStyle.ColdWhite)
785
+ */
786
+ setWhiteningStyle(style) {
787
+ this._setMakeupStyle('fb_set_whitening_style', style, 'Failed to set whitening style');
656
788
  }
657
789
 
658
790
  /**
659
- * [Deprecated] No-op that returns success. To reset, explicitly zero parameters
660
- * and clear effects via parameter-driven APIs. Kept for compatibility and may
661
- * be removed in a future release.
662
- * @deprecated
791
+ * Sets image sharpening intensity
792
+ * @param {number} value - Parameter value (0.0 - 1.0)
663
793
  */
664
- disableAllBeautyTypes() {
794
+ setSharpening(value) {
665
795
  this._ensureInitialized();
666
- const Module = this._getWasmModule();
667
-
668
- const result = Module.ccall(
669
- 'DisableAllBeautyTypes',
670
- 'number',
671
- ['number'],
672
- [this.enginePtr]
673
- );
674
-
675
- checkResult(result, 'Failed to disable all beauty types');
796
+ this._setIntensity('fb_set_sharpening', value);
676
797
  }
677
798
 
678
799
  /**
679
- * Sets a basic beauty parameter
680
- * @param {number} param - Parameter (use BasicParam enum, e.g., BasicParam.Smoothing)
800
+ * Sets skin rosiness intensity
681
801
  * @param {number} value - Parameter value (0.0 - 1.0)
682
802
  */
683
- setBasicParam(param, value) {
803
+ setRosiness(value) {
684
804
  this._ensureInitialized();
685
- this._setBeautyParam('SetBeautyParamBasic', param, value);
805
+ this._setIntensity('fb_set_rosiness', value);
686
806
  }
687
807
 
688
808
  /**
689
809
  * Sets a reshape parameter
690
- * @param {number} param - Parameter (use ReshapeParam enum, e.g., ReshapeParam.FaceThinning)
691
- * @param {number} value - Parameter value (0.0 - 1.0)
810
+ * @param {number} param - Parameter (use Reshape enum, e.g., Reshape.FaceThin)
811
+ * @param {number} value - Parameter value in [-1.0, 1.0]. 0 is off.
692
812
  */
693
- setReshapeParam(param, value) {
813
+ setReshape(param, value) {
694
814
  this._ensureInitialized();
695
- this._setBeautyParam('SetBeautyParamReshape', param, value);
815
+ this._setBeautyParam('fb_set_reshape', param, value);
696
816
  }
697
817
 
698
818
  /**
699
- * Sets a makeup parameter
700
- * @param {number} param - Parameter (use MakeupParam enum, e.g., MakeupParam.Lipstick)
819
+ * Sets lipstick intensity
701
820
  * @param {number} value - Parameter value (0.0 - 1.0)
702
821
  */
703
- setMakeupParam(param, value) {
822
+ setLipstick(value) {
704
823
  this._ensureInitialized();
705
- this._setBeautyParam('SetBeautyParamMakeup', param, value);
824
+ this._setIntensity('fb_set_lipstick', value);
706
825
  }
707
826
 
708
827
  /**
709
- * Sets lipstick style/texture
710
- * @param {number} style - Style (use LipstickStyle enum, e.g., LipstickStyle.Rouge)
828
+ * Sets lipstick colour preset
829
+ * @param {number} style - Style (use LipstickColor enum, e.g., LipstickColor.Rouge)
711
830
  */
712
- setLipstickStyle(style) {
713
- this._ensureInitialized();
714
- const Module = this._getWasmModule();
715
- const result = Module.ccall(
716
- 'SetLipstickStyle',
717
- 'number',
718
- ['number', 'number'],
719
- [this.enginePtr, style]
720
- );
721
- checkResult(result, 'Failed to set lipstick style');
831
+ setLipstickColor(style) {
832
+ this._setMakeupStyle('fb_set_lipstick_color', style, 'Failed to set lipstick style');
722
833
  }
723
834
 
724
835
  /**
725
- * Sets blush style/texture
726
- * @param {number} style - Style (use BlushStyle enum, e.g., BlushStyle.Classic)
836
+ * Sets blush intensity
837
+ * @param {number} value - Parameter value (0.0 - 1.0)
727
838
  */
839
+ setBlush(value) {
840
+ this._ensureInitialized();
841
+ this._setIntensity('fb_set_blush', value);
842
+ }
843
+
728
844
  setBlushStyle(style) {
845
+ this._setMakeupStyle('fb_set_blush_style', style, 'Failed to set blush style');
846
+ }
847
+
848
+ setBlushColor(color) {
849
+ this._setMakeupStyle('fb_set_blush_color', color, 'Failed to set blush color');
850
+ }
851
+
852
+ setContour(value) {
729
853
  this._ensureInitialized();
730
- const Module = this._getWasmModule();
731
- const result = Module.ccall(
732
- 'SetBlushStyle',
733
- 'number',
734
- ['number', 'number'],
735
- [this.enginePtr, style]
736
- );
737
- checkResult(result, 'Failed to set blush style');
854
+ this._setIntensity('fb_set_contour', value);
738
855
  }
739
856
 
740
- /**
741
- * Sets chroma key parameter
742
- * @param {number} param - Parameter (use ChromaKeyParam enum, e.g., ChromaKeyParam.Similarity)
743
- * @param {number} value - Parameter value (KeyColor: 0.0=Green, 1.0=Blue, 2.0=Red; others 0.0-1.0)
744
- */
745
- setChromaKeyParam(param, value) {
857
+ setContourStyle(style) {
858
+ this._setMakeupStyle('fb_set_contour_style', style, 'Failed to set contour style');
859
+ }
860
+
861
+ setEyeShadow(value) {
746
862
  this._ensureInitialized();
747
- // For KeyColor, values are 0, 1, 2, not 0.0-1.0
748
- if (param === 0) { // ChromaKeyParam.KeyColor
749
- const Module = this._getWasmModule();
750
- const result = Module.ccall(
751
- 'SetBeautyParamChromaKey',
752
- 'number',
753
- ['number', 'number', 'number'],
754
- [this.enginePtr, param, value]
755
- );
756
- checkResult(result, `Failed to set chroma key parameter`);
757
- return;
758
- }
759
- this._setBeautyParam('SetBeautyParamChromaKey', param, value);
863
+ this._setIntensity('fb_set_eye_shadow', value);
864
+ }
865
+
866
+ setEyeShadowStyle(style) {
867
+ this._setMakeupStyle('fb_set_eye_shadow_style', style, 'Failed to set eyeshadow style');
868
+ }
869
+
870
+ setEyeShadowColor(style) {
871
+ this._setMakeupStyle('fb_set_eye_shadow_color', style, 'Failed to set eyeshadow style');
872
+ }
873
+
874
+ setEyeLiner(value) {
875
+ this._ensureInitialized();
876
+ this._setIntensity('fb_set_eye_liner', value);
877
+ }
878
+
879
+ setEyeLinerStyle(style) {
880
+ this._setMakeupStyle('fb_set_eye_liner_style', style, 'Failed to set eyeliner style');
881
+ }
882
+
883
+ setEyeLinerColor(style) {
884
+ this._setMakeupStyle('fb_set_eye_liner_color', style, 'Failed to set eyeliner style');
885
+ }
886
+
887
+ setEyebrow(value) {
888
+ this._ensureInitialized();
889
+ this._setIntensity('fb_set_eyebrow', value);
890
+ }
891
+
892
+ setEyebrowStyle(style) {
893
+ this._setMakeupStyle('fb_set_eyebrow_style', style, 'Failed to set eyebrow style');
894
+ }
895
+
896
+ setEyebrowColor(style) {
897
+ this._setMakeupStyle('fb_set_eyebrow_color', style, 'Failed to set eyebrow style');
898
+ }
899
+
900
+ setEyelash(value) {
901
+ this._ensureInitialized();
902
+ this._setIntensity('fb_set_eyelash', value);
903
+ }
904
+
905
+ setEyelashStyle(style) {
906
+ this._setMakeupStyle('fb_set_eyelash_style', style, 'Failed to set eyelash style');
907
+ }
908
+
909
+ setEyelashColor(style) {
910
+ this._setMakeupStyle('fb_set_eyelash_color', style, 'Failed to set eyelash style');
911
+ }
912
+
913
+ setPupil(value) {
914
+ this._ensureInitialized();
915
+ this._setIntensity('fb_set_pupil', value);
916
+ }
917
+
918
+ setPupilColor(style) {
919
+ this._setMakeupStyle('fb_set_pupil_color', style, 'Failed to set pupil style');
760
920
  }
761
921
 
762
922
  /**
763
- * Sets a LUT-based filter
764
- * @param {string} filterId - Unique identifier of the filter (e.g., "chuxin"). Pass "" to clear.
923
+ * Uses chroma keying as the virtual-background mask.
924
+ * Fill is still controlled by setVirtualBackgroundBlur / setVirtualBackground.
925
+ * @param {number} color - Key colour (use ChromaKeyColor enum)
765
926
  */
766
- setFilter(filterId) {
767
- this._ensureInitialized();
768
- const Module = this._getWasmModule();
769
- const result = Module.ccall(
770
- 'SetFilter',
771
- 'number',
772
- ['number', 'string'],
773
- [this.enginePtr, filterId || '']
774
- );
775
- checkResult(result, 'Failed to set filter');
927
+ setChromaKey(color) {
928
+ this._setMakeupStyle('fb_set_chroma_key', color, 'Failed to set chroma key');
776
929
  }
777
930
 
778
931
  /**
779
- * Sets the intensity of the current filter
780
- * @param {number} intensity - Filter intensity (0.0 - 1.0)
932
+ * Turns off chroma keying and restores portrait-segmentation as the mask.
781
933
  */
782
- setFilterIntensity(intensity) {
934
+ clearChromaKey() {
783
935
  this._ensureInitialized();
784
936
  const Module = this._getWasmModule();
785
937
  const result = Module.ccall(
786
- 'SetFilterIntensity',
938
+ 'fb_clear_chroma_key',
787
939
  'number',
788
- ['number', 'number'],
789
- [this.enginePtr, intensity]
940
+ ['number'],
941
+ [this.enginePtr]
790
942
  );
791
- checkResult(result, 'Failed to set filter intensity');
943
+ checkResult(result, 'Failed to clear chroma key');
792
944
  }
793
945
 
794
- /**
795
- * Sets a 2D sticker
796
- * @param {string} stickerId - Unique identifier of the sticker (e.g., "樱花"). Pass "" to clear.
797
- */
798
- setSticker(stickerId) {
946
+ /** How close a pixel must be to the key colour to be keyed out. [0.0, 1.0]. */
947
+ setChromaKeySimilarity(value) {
948
+ this._setChromaKeyFloat('fb_set_chroma_key_similarity', value);
949
+ }
950
+
951
+ /** Edge feather around the key. [0.0, 1.0]. */
952
+ setChromaKeySmoothness(value) {
953
+ this._setChromaKeyFloat('fb_set_chroma_key_smoothness', value);
954
+ }
955
+
956
+ /** Spill suppression on semi-transparent edges. [0.0, 1.0]. */
957
+ setChromaKeyDesaturation(value) {
958
+ this._setChromaKeyFloat('fb_set_chroma_key_desaturation', value);
959
+ }
960
+
961
+ _setChromaKeyFloat(cFuncName, value) {
799
962
  this._ensureInitialized();
800
963
  const Module = this._getWasmModule();
801
964
  const result = Module.ccall(
802
- 'SetSticker',
965
+ cFuncName,
803
966
  'number',
804
- ['number', 'string'],
805
- [this.enginePtr, stickerId || '']
967
+ ['number', 'number'],
968
+ [this.enginePtr, value]
806
969
  );
807
- checkResult(result, 'Failed to set sticker');
970
+ checkResult(result, `Failed to call ${cFuncName}`);
808
971
  }
809
972
 
810
973
  /**
811
- * Registers a filter from a file path or data
812
- * @param {string} filterId - Unique identifier for the filter
813
- * @param {string|Uint8Array} resource - Path to .fbd file or Uint8Array data
974
+ * Applies a LUT filter from a file path or in-memory .fbd data.
975
+ * @param {string|Uint8Array} resource - Path to .fbd file or Uint8Array data.
814
976
  */
815
- registerFilter(filterId, resource) {
977
+ setFilter(resource) {
816
978
  this._ensureInitialized();
817
979
  const Module = this._getWasmModule();
818
980
 
819
981
  if (typeof resource === 'string') {
982
+ if (!resource) {
983
+ throw new FacebetterError('Filter path must not be empty; use clearFilter()');
984
+ }
820
985
  const result = Module.ccall(
821
- 'RegisterFilterPath',
986
+ 'fb_set_filter',
822
987
  'number',
823
- ['number', 'string', 'string'],
824
- [this.enginePtr, filterId, resource]
988
+ ['number', 'string'],
989
+ [this.enginePtr, resource]
825
990
  );
826
- checkResult(result, `Failed to register filter path: ${filterId}`);
827
- } else if (resource instanceof Uint8Array) {
991
+ checkResult(result, 'Failed to set filter from path');
992
+ return;
993
+ }
994
+
995
+ if (resource instanceof Uint8Array) {
828
996
  const dataPtr = Module._malloc(resource.length);
829
997
  Module.HEAPU8.set(resource, dataPtr);
830
998
  try {
831
999
  const result = Module.ccall(
832
- 'RegisterFilterData',
1000
+ 'fb_set_filter_data',
833
1001
  'number',
834
- ['number', 'string', 'number', 'number'],
835
- [this.enginePtr, filterId, dataPtr, resource.length]
1002
+ ['number', 'number', 'number'],
1003
+ [this.enginePtr, dataPtr, resource.length]
836
1004
  );
837
- checkResult(result, `Failed to register filter data: ${filterId}`);
1005
+ checkResult(result, 'Failed to set filter from data');
838
1006
  } finally {
839
1007
  Module._free(dataPtr);
840
1008
  }
841
- } else {
842
- throw new FacebetterError('Resource must be a string path or Uint8Array data');
1009
+ return;
843
1010
  }
1011
+
1012
+ throw new FacebetterError('Filter resource must be a string path or Uint8Array');
1013
+ }
1014
+
1015
+ /**
1016
+ * Clears the current LUT filter.
1017
+ */
1018
+ clearFilter() {
1019
+ this._ensureInitialized();
1020
+ const Module = this._getWasmModule();
1021
+ const result = Module.ccall(
1022
+ 'fb_clear_filter',
1023
+ 'number',
1024
+ ['number'],
1025
+ [this.enginePtr]
1026
+ );
1027
+ checkResult(result, 'Failed to clear filter');
1028
+ }
1029
+
1030
+ /**
1031
+ * Sets the intensity of the current filter
1032
+ * @param {number} intensity - Filter intensity (0.0 - 1.0)
1033
+ */
1034
+ setFilterIntensity(intensity) {
1035
+ this._ensureInitialized();
1036
+ const Module = this._getWasmModule();
1037
+ const result = Module.ccall(
1038
+ 'fb_set_filter_intensity',
1039
+ 'number',
1040
+ ['number', 'number'],
1041
+ [this.enginePtr, intensity]
1042
+ );
1043
+ checkResult(result, 'Failed to set filter intensity');
844
1044
  }
845
1045
 
846
1046
  /**
847
- * Registers a sticker from a file path or data
848
- * @param {string} stickerId - Unique identifier for the sticker
849
- * @param {string|Uint8Array} resource - Path to .fbd file or Uint8Array data
1047
+ * Applies a 2D sticker from a file path or in-memory .fbd data.
1048
+ * @param {string|Uint8Array} resource - Path to .fbd file or Uint8Array data.
850
1049
  */
851
- registerSticker(stickerId, resource) {
1050
+ setSticker(resource) {
852
1051
  this._ensureInitialized();
853
1052
  const Module = this._getWasmModule();
854
1053
 
855
1054
  if (typeof resource === 'string') {
1055
+ if (!resource) {
1056
+ throw new FacebetterError('Sticker path must not be empty; use clearSticker()');
1057
+ }
856
1058
  const result = Module.ccall(
857
- 'RegisterStickerPath',
1059
+ 'fb_set_sticker',
858
1060
  'number',
859
- ['number', 'string', 'string'],
860
- [this.enginePtr, stickerId, resource]
1061
+ ['number', 'string'],
1062
+ [this.enginePtr, resource]
861
1063
  );
862
- checkResult(result, `Failed to register sticker path: ${stickerId}`);
863
- } else if (resource instanceof Uint8Array) {
1064
+ checkResult(result, 'Failed to set sticker from path');
1065
+ return;
1066
+ }
1067
+
1068
+ if (resource instanceof Uint8Array) {
864
1069
  const dataPtr = Module._malloc(resource.length);
865
1070
  Module.HEAPU8.set(resource, dataPtr);
866
1071
  try {
867
1072
  const result = Module.ccall(
868
- 'RegisterStickerData',
1073
+ 'fb_set_sticker_data',
869
1074
  'number',
870
- ['number', 'string', 'number', 'number'],
871
- [this.enginePtr, stickerId, dataPtr, resource.length]
1075
+ ['number', 'number', 'number'],
1076
+ [this.enginePtr, dataPtr, resource.length]
872
1077
  );
873
- checkResult(result, `Failed to register sticker data: ${stickerId}`);
1078
+ checkResult(result, 'Failed to set sticker from data');
874
1079
  } finally {
875
1080
  Module._free(dataPtr);
876
1081
  }
877
- } else {
878
- throw new FacebetterError('Resource must be a string path or Uint8Array data');
1082
+ return;
879
1083
  }
1084
+
1085
+ throw new FacebetterError('Sticker resource must be a string path or Uint8Array');
880
1086
  }
881
1087
 
882
1088
  /**
883
- * Unregisters a specific filter
884
- * @param {string} filterId - Filter ID to unregister
1089
+ * Clears the current 2D sticker.
885
1090
  */
886
- unregisterFilter(filterId) {
1091
+ clearSticker() {
887
1092
  this._ensureInitialized();
888
1093
  const Module = this._getWasmModule();
889
1094
  const result = Module.ccall(
890
- 'UnregisterFilter',
1095
+ 'fb_clear_sticker',
891
1096
  'number',
892
- ['number', 'string'],
893
- [this.enginePtr, filterId]
1097
+ ['number'],
1098
+ [this.enginePtr]
894
1099
  );
895
- checkResult(result, `Failed to unregister filter: ${filterId}`);
1100
+ checkResult(result, 'Failed to clear sticker');
896
1101
  }
897
1102
 
898
1103
  /**
899
- * Unregisters all filters
1104
+ * Internal method to set a single intensity parameter
1105
+ * @private
900
1106
  */
901
- unregisterAllFilters() {
902
- this._ensureInitialized();
1107
+ _setIntensity(functionName, value) {
1108
+ if (value < 0 || value > 1) {
1109
+ throw new FacebetterError('Parameter value must be between 0.0 and 1.0');
1110
+ }
1111
+
903
1112
  const Module = this._getWasmModule();
904
1113
  const result = Module.ccall(
905
- 'UnregisterAllFilters',
1114
+ functionName,
906
1115
  'number',
907
- ['number'],
908
- [this.enginePtr]
1116
+ ['number', 'number'],
1117
+ [this.enginePtr, value]
909
1118
  );
910
- checkResult(result, 'Failed to unregister all filters');
1119
+
1120
+ checkResult(result, `Failed to set beauty parameter`);
911
1121
  }
912
1122
 
913
- /**
914
- * Unregisters a specific sticker
915
- * @param {string} stickerId - Sticker ID to unregister
916
- */
917
- unregisterSticker(stickerId) {
1123
+ _setMakeupStyle(cFuncName, value, errorMessage) {
918
1124
  this._ensureInitialized();
919
1125
  const Module = this._getWasmModule();
920
1126
  const result = Module.ccall(
921
- 'UnregisterSticker',
1127
+ cFuncName,
922
1128
  'number',
923
- ['number', 'string'],
924
- [this.enginePtr, stickerId]
1129
+ ['number', 'number'],
1130
+ [this.enginePtr, value]
925
1131
  );
926
- checkResult(result, `Failed to unregister sticker: ${stickerId}`);
1132
+ checkResult(result, errorMessage);
927
1133
  }
928
1134
 
929
1135
  /**
930
- * Unregisters all stickers
931
- */
932
- unregisterAllStickers() {
933
- this._ensureInitialized();
934
- const Module = this._getWasmModule();
935
- const result = Module.ccall(
936
- 'UnregisterAllStickers',
937
- 'number',
938
- ['number'],
939
- [this.enginePtr]
940
- );
941
- checkResult(result, 'Failed to unregister all stickers');
942
- }
943
-
944
- /**
945
- * Gets the list of registered filter IDs
946
- * @returns {string[]} Array of registered filter IDs
947
- */
948
- getRegisteredFilters() {
949
- this._ensureInitialized();
950
- const Module = this._getWasmModule();
951
- const jsonStr = Module.ccall(
952
- 'GetRegisteredFilters',
953
- 'string',
954
- ['number'],
955
- [this.enginePtr]
956
- );
957
- try {
958
- return JSON.parse(jsonStr || '[]');
959
- } catch (e) {
960
- logError(`Failed to parse registered filters JSON: ${e}`);
961
- return [];
962
- }
963
- }
964
-
965
- /**
966
- * Gets the list of registered sticker IDs
967
- * @returns {string[]} Array of registered sticker IDs
968
- */
969
- getRegisteredStickers() {
970
- this._ensureInitialized();
971
- const Module = this._getWasmModule();
972
- const jsonStr = Module.ccall(
973
- 'GetRegisteredStickers',
974
- 'string',
975
- ['number'],
976
- [this.enginePtr]
977
- );
978
- try {
979
- return JSON.parse(jsonStr || '[]');
980
- } catch (e) {
981
- logError(`Failed to parse registered stickers JSON: ${e}`);
982
- return [];
983
- }
984
- }
985
-
986
- /**
987
- * Internal method to set beauty parameters
1136
+ * Internal method to set beauty parameters
988
1137
  * @private
989
1138
  */
990
1139
  _setBeautyParam(functionName, param, value) {
991
- if (value < 0 || value > 1) {
992
- throw new FacebetterError('Parameter value must be between 0.0 and 1.0');
1140
+ if (value < -1 || value > 1) {
1141
+ throw new FacebetterError('Parameter value must be between -1.0 and 1.0');
993
1142
  }
994
1143
 
995
1144
  const Module = this._getWasmModule();
@@ -1004,15 +1153,25 @@
1004
1153
  }
1005
1154
 
1006
1155
  /**
1007
- * Sets engine callbacks (face landmarks detection)
1156
+ * Sets engine callbacks (face landmarks and engine events)
1008
1157
  * @param {Object} callbacks - Callback functions
1009
- * @param {Function} callbacks.onFaceLandmarks - Callback for face landmarks detection
1158
+ * @param {Function} [callbacks.onFaceLandmarks] - Callback for face landmarks detection
1159
+ * @param {Function} [callbacks.onEngineEvent] - Callback for license / init events (code, message)
1010
1160
  * @param {number} [callbacks.maxFaces=10] - Maximum number of faces to support (affects shared buffer size)
1011
1161
  */
1012
1162
  setCallbacks(callbacks) {
1013
1163
  this._ensureInitialized();
1014
1164
  const Module = this._getWasmModule();
1015
-
1165
+
1166
+ if (!Module._engine_event_callbacks) {
1167
+ Module._engine_event_callbacks = {};
1168
+ }
1169
+ if (callbacks && typeof callbacks.onEngineEvent === 'function') {
1170
+ Module._engine_event_callbacks[this.enginePtr] = callbacks.onEngineEvent;
1171
+ } else {
1172
+ delete Module._engine_event_callbacks[this.enginePtr];
1173
+ }
1174
+
1016
1175
  // Initialize callback storage (if it doesn't exist)
1017
1176
  if (!Module._face_landmarks_callbacks) {
1018
1177
  Module._face_landmarks_callbacks = [];
@@ -1028,9 +1187,9 @@
1028
1187
 
1029
1188
  // Pre-allocate shared memory
1030
1189
  // Metadata: 2 ints (frame_number, face_count) = 8 bytes
1031
- // Face data: maxFaces * 343 floats * 4 bytes = maxFaces * 1372 bytes
1190
+ // Face data: maxFaces * 342 floats * 4 bytes = maxFaces * 1368 bytes
1032
1191
  const metadataSize = 2 * 4; // 2 ints
1033
- const faceDataSize = maxFaces * 343 * 4; // 343 floats per face
1192
+ const faceDataSize = maxFaces * 342 * 4; // 342 floats per face
1034
1193
  sharedBufferSize = metadataSize + faceDataSize;
1035
1194
  sharedBufferPtr = Module._malloc(sharedBufferSize);
1036
1195
 
@@ -1047,7 +1206,7 @@
1047
1206
  const dataOffset = dataPtr / 4; // float is 4 bytes
1048
1207
 
1049
1208
  const results = [];
1050
- const FLOATS_PER_FACE = 343;
1209
+ const FLOATS_PER_FACE = 342;
1051
1210
 
1052
1211
  for (let i = 0; i < faceCount; i++) {
1053
1212
  const faceOffset = dataOffset + i * FLOATS_PER_FACE;
@@ -1063,7 +1222,6 @@
1063
1222
 
1064
1223
  // Read basic fields
1065
1224
  const faceId = Math.round(heap[offset++]);
1066
- const faceAction = Math.round(heap[offset++]);
1067
1225
  const score = heap[offset++];
1068
1226
  const pitch = heap[offset++];
1069
1227
  const roll = heap[offset++];
@@ -1089,7 +1247,6 @@
1089
1247
  key_points: keyPoints,
1090
1248
  visibility,
1091
1249
  face_id: faceId,
1092
- face_action: faceAction,
1093
1250
  score,
1094
1251
  pitch,
1095
1252
  roll,
@@ -1108,7 +1265,7 @@
1108
1265
 
1109
1266
  // Call C API
1110
1267
  const result = Module.ccall(
1111
- 'SetCallbacks',
1268
+ 'fb_engine_set_wasm_callbacks',
1112
1269
  'number',
1113
1270
  ['number', 'number', 'number', 'number'],
1114
1271
  [
@@ -1137,55 +1294,81 @@
1137
1294
  }
1138
1295
 
1139
1296
  /**
1140
- * Sets virtual background options (unified API, matches C++/Java/OC)
1141
- * @param {VirtualBackgroundOptions|Object} options - Virtual background options
1142
- * @param {number} [options.mode] - Background mode (use BackgroundMode enum)
1143
- * @param {ImageData|HTMLImageElement|HTMLCanvasElement} [options.backgroundImage] - Background image (required when mode is Image)
1297
+ * Enables virtual background blur.
1298
+ * Level is continuous in [0.0, 1.0] (downsample + mix, no shader rebuild).
1299
+ * @param {number} level - Blur strength in [0.0, 1.0]. 0 clears the virtual background.
1144
1300
  */
1145
- setVirtualBackground(options) {
1301
+ setVirtualBackgroundBlur(level) {
1146
1302
  this._ensureInitialized();
1147
1303
  const Module = this._getWasmModule();
1148
-
1149
- // Support both VirtualBackgroundOptions object and plain object
1150
- const opts = options.mode !== undefined ? options : { mode: BackgroundMode$1.None, backgroundImage: null };
1151
-
1152
- let imageData = null;
1153
- let imageBufferPtr = null;
1154
-
1155
- try {
1156
- // If a background image is provided, convert to ImageData and prepare buffer
1157
- if (opts.backgroundImage) {
1158
- imageData = this._toImageData(opts.backgroundImage);
1159
- const bufferSize = imageData.width * imageData.height * 4;
1160
- imageBufferPtr = Module._malloc(bufferSize);
1161
-
1162
- const buffer = this._getWasmBuffer();
1163
- const view = new Uint8Array(buffer, imageBufferPtr, bufferSize);
1164
- view.set(imageData.data);
1304
+ const result = Module.ccall(
1305
+ 'fb_set_virtual_background_blur',
1306
+ 'number',
1307
+ ['number', 'number'],
1308
+ [this.enginePtr, level]
1309
+ );
1310
+ checkResult(result, 'Failed to set virtual background blur');
1311
+ }
1312
+
1313
+ /**
1314
+ * Replaces the background with an image file path or encoded png/jpg bytes.
1315
+ * @param {string|Uint8Array} resource - Image path or Uint8Array data.
1316
+ */
1317
+ setVirtualBackground(resource) {
1318
+ this._ensureInitialized();
1319
+ const Module = this._getWasmModule();
1320
+
1321
+ if (typeof resource === 'string') {
1322
+ if (!resource) {
1323
+ throw new FacebetterError(
1324
+ 'Virtual background path must not be empty; use clearVirtualBackground()'
1325
+ );
1165
1326
  }
1166
-
1167
- // Use unified SetVirtualBackground C interface (consistent with other platforms)
1168
1327
  const result = Module.ccall(
1169
- 'SetVirtualBackground',
1328
+ 'fb_set_virtual_background',
1170
1329
  'number',
1171
- ['number', 'number', 'number', 'number', 'number', 'number'],
1172
- [
1173
- this.enginePtr,
1174
- opts.mode,
1175
- imageBufferPtr || 0, // If null, pass 0
1176
- imageData ? imageData.width : 0,
1177
- imageData ? imageData.height : 0,
1178
- imageData ? imageData.width * 4 : 0
1179
- ]
1330
+ ['number', 'string'],
1331
+ [this.enginePtr, resource]
1180
1332
  );
1181
-
1182
- checkResult(result, 'Failed to set virtual background');
1183
- } finally {
1184
- // Free memory
1185
- if (imageBufferPtr) {
1186
- Module._free(imageBufferPtr);
1333
+ checkResult(result, 'Failed to set virtual background from path');
1334
+ return;
1335
+ }
1336
+
1337
+ if (resource instanceof Uint8Array) {
1338
+ const dataPtr = Module._malloc(resource.length);
1339
+ Module.HEAPU8.set(resource, dataPtr);
1340
+ try {
1341
+ const result = Module.ccall(
1342
+ 'fb_set_virtual_background_data',
1343
+ 'number',
1344
+ ['number', 'number', 'number'],
1345
+ [this.enginePtr, dataPtr, resource.length]
1346
+ );
1347
+ checkResult(result, 'Failed to set virtual background from data');
1348
+ } finally {
1349
+ Module._free(dataPtr);
1187
1350
  }
1351
+ return;
1188
1352
  }
1353
+
1354
+ throw new FacebetterError(
1355
+ 'setVirtualBackground expects a file path string or Uint8Array'
1356
+ );
1357
+ }
1358
+
1359
+ /**
1360
+ * Clears virtual background (blur or image replacement).
1361
+ */
1362
+ clearVirtualBackground() {
1363
+ this._ensureInitialized();
1364
+ const Module = this._getWasmModule();
1365
+ const result = Module.ccall(
1366
+ 'fb_clear_virtual_background',
1367
+ 'number',
1368
+ ['number'],
1369
+ [this.enginePtr]
1370
+ );
1371
+ checkResult(result, 'Failed to clear virtual background');
1189
1372
  }
1190
1373
 
1191
1374
  /**
@@ -1193,12 +1376,12 @@
1193
1376
  * When enabled, beauty effects will only be applied to detected skin areas.
1194
1377
  * @param {boolean} enabled - True to enable skin-only beauty, false to apply to entire image.
1195
1378
  */
1196
- setSkinOnlyBeauty(enabled) {
1379
+ setBeautySkinOnly(enabled) {
1197
1380
  this._ensureInitialized();
1198
1381
  const Module = this._getWasmModule();
1199
1382
 
1200
1383
  const result = Module.ccall(
1201
- 'SetSkinOnlyBeauty',
1384
+ 'fb_set_beauty_skin_only',
1202
1385
  'number',
1203
1386
  ['number', 'number'],
1204
1387
  [this.enginePtr, enabled ? 1 : 0]
@@ -1207,6 +1390,35 @@
1207
1390
  checkResult(result, 'Failed to set skin only beauty');
1208
1391
  }
1209
1392
 
1393
+ /**
1394
+ * Gets engine performance statistics.
1395
+ * @returns {{fps: number, avgProcessTimeMs: number, sessionTimeS: number}}
1396
+ */
1397
+ getStats() {
1398
+ this._ensureInitialized();
1399
+ const Module = this._getWasmModule();
1400
+ const ptr = Module._malloc(24);
1401
+ try {
1402
+ const result = Module.ccall(
1403
+ 'fb_engine_get_stats',
1404
+ 'number',
1405
+ ['number', 'number'],
1406
+ [this.enginePtr, ptr]
1407
+ );
1408
+ checkResult(result, 'Failed to get stats');
1409
+ if ((ptr & 7) !== 0) {
1410
+ throw new FacebetterError('Unaligned stats buffer');
1411
+ }
1412
+ const base = ptr >> 3;
1413
+ return {
1414
+ fps: Module.HEAPF64[base],
1415
+ avgProcessTimeMs: Module.HEAPF64[base + 1],
1416
+ sessionTimeS: Module.HEAPF64[base + 2]
1417
+ };
1418
+ } finally {
1419
+ Module._free(ptr);
1420
+ }
1421
+ }
1210
1422
 
1211
1423
  /**
1212
1424
  * Ensures buffers are allocated for the given dimensions
@@ -1260,7 +1472,7 @@
1260
1472
  srcView.set(imageData.data);
1261
1473
 
1262
1474
  const result = Module.ccall(
1263
- 'ProcessImageRGBA',
1475
+ 'fb_process_rgba',
1264
1476
  'number',
1265
1477
  ['number', 'number', 'number', 'number', 'number', 'number', 'number', 'number'],
1266
1478
  [
@@ -1321,7 +1533,7 @@
1321
1533
 
1322
1534
  try {
1323
1535
  const result = Module.ccall(
1324
- 'ProcessImageTexture',
1536
+ 'fb_process_texture',
1325
1537
  'number',
1326
1538
  ['number', 'number', 'number', 'number', 'number', 'number', 'number'],
1327
1539
  [
@@ -1361,37 +1573,37 @@
1361
1573
  }
1362
1574
 
1363
1575
  /**
1364
- * Ensures gpupixel_canvas exists in the DOM (browser only)
1576
+ * Ensures facebetter_canvas exists in the DOM (browser only)
1365
1577
  * This canvas is required by GPUPixel for WebGL context creation
1366
1578
  * @private
1367
1579
  */
1368
- _ensureGPUPixelCanvas() {
1580
+ _ensureFacebetterCanvas() {
1369
1581
  if (typeof document === 'undefined') {
1370
1582
  return;
1371
1583
  }
1372
1584
 
1373
- let canvas = document.getElementById('gpupixel_canvas');
1585
+ let canvas = document.getElementById('facebetter_canvas');
1374
1586
  if (!canvas) {
1375
1587
  canvas = document.createElement('canvas');
1376
- canvas.id = 'gpupixel_canvas';
1588
+ canvas.id = 'facebetter_canvas';
1377
1589
  canvas.style.display = 'none';
1378
1590
  canvas.width = 1;
1379
1591
  canvas.height = 1;
1380
1592
  document.body.appendChild(canvas);
1381
- this._createdGPUPixelCanvas = true;
1593
+ this._createdFacebetterCanvas = true;
1382
1594
  }
1383
- this._gpupixelCanvas = canvas;
1595
+ this._facebetterCanvas = canvas;
1384
1596
  }
1385
1597
 
1386
1598
  /**
1387
- * Cleans up gpupixel_canvas if it was created by this engine instance (browser only)
1599
+ * Cleans up facebetter_canvas if it was created by this engine instance (browser only)
1388
1600
  * @private
1389
1601
  */
1390
- _cleanupGPUPixelCanvas() {
1391
- if (this._createdGPUPixelCanvas && this._gpupixelCanvas) {
1392
- this._gpupixelCanvas.remove();
1393
- this._gpupixelCanvas = null;
1394
- this._createdGPUPixelCanvas = false;
1602
+ _cleanupFacebetterCanvas() {
1603
+ if (this._createdFacebetterCanvas && this._facebetterCanvas) {
1604
+ this._facebetterCanvas.remove();
1605
+ this._facebetterCanvas = null;
1606
+ this._createdFacebetterCanvas = false;
1395
1607
  }
1396
1608
  }
1397
1609
 
@@ -1420,6 +1632,102 @@
1420
1632
 
1421
1633
  }
1422
1634
 
1635
+ /** Cloudflare v2 online auth URL. Proxy through your server in production; never ship keys in the frontend bundle. */
1636
+ const FACEBETTER_AUTH_URL =
1637
+ 'https://facebetter.pixpark.net/facebetter/v2/auth';
1638
+
1639
+ function toHex(buffer) {
1640
+ return Array.from(new Uint8Array(buffer), (b) =>
1641
+ b.toString(16).padStart(2, '0')
1642
+ ).join('');
1643
+ }
1644
+
1645
+ async function hmacSha256Hex(key, message) {
1646
+ const encoder = new TextEncoder();
1647
+ const cryptoKey = await crypto.subtle.importKey(
1648
+ 'raw',
1649
+ encoder.encode(key),
1650
+ { name: 'HMAC', hash: 'SHA-256' },
1651
+ false,
1652
+ ['sign']
1653
+ );
1654
+ const signature = await crypto.subtle.sign(
1655
+ 'HMAC',
1656
+ cryptoKey,
1657
+ encoder.encode(message)
1658
+ );
1659
+ return toHex(signature);
1660
+ }
1661
+
1662
+ /**
1663
+ * Call Cloudflare v2/auth directly with app_id / app_key.
1664
+ * Intended for local debugging, or environments where key leakage is not a risk.
1665
+ *
1666
+ * @param {Object} options
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)
1672
+ */
1673
+ async function requestFacebetterAuth({
1674
+ appId,
1675
+ appKey,
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
+ );
1686
+ }
1687
+ if (!challenge || typeof challenge !== 'object') {
1688
+ throw new FacebetterError(
1689
+ 'requestFacebetterAuth requires a WASM auth challenge',
1690
+ 'LICENSE_ERROR'
1691
+ );
1692
+ }
1693
+
1694
+ const nonce = String(challenge.nonce || '');
1695
+ const timestamp = Number(challenge.timestamp);
1696
+ const platform = String(challenge.platform || 'web');
1697
+ const userAgent = String(challenge.user_agent || '');
1698
+ const payload = `v2|${id}|${timestamp}|${nonce}|${platform}`;
1699
+ const hmac = await hmacSha256Hex(key, payload);
1700
+
1701
+ const response = await fetch(authUrl, {
1702
+ method: 'POST',
1703
+ headers: { 'Content-Type': 'application/json' },
1704
+ body: JSON.stringify({
1705
+ app_id: id,
1706
+ hmac_signature: hmac,
1707
+ timestamp,
1708
+ nonce,
1709
+ platform,
1710
+ user_agent: userAgent,
1711
+ }),
1712
+ });
1713
+ return response.text();
1714
+ }
1715
+
1716
+ /**
1717
+ * Build an `EngineConfig.fetchAuthResponse` that talks to Cloudflare directly
1718
+ * (no customer proxy).
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>}
1725
+ */
1726
+ function createDirectAuthFetcher({ appId, appKey, authUrl } = {}) {
1727
+ return (challenge) =>
1728
+ requestFacebetterAuth({ appId, appKey, challenge, authUrl });
1729
+ }
1730
+
1423
1731
  /**
1424
1732
  * Browser-specific WASM Module Loader
1425
1733
  * Supports ESM format WASM modules (generated with MODULARIZE and EXPORT_ES6)
@@ -1684,15 +1992,15 @@
1684
1992
 
1685
1993
 
1686
1994
  // Browser-specific GPU canvas management
1687
- function ensureGPUPixelCanvas() {
1995
+ function ensureFacebetterCanvas() {
1688
1996
  if (typeof document === 'undefined') {
1689
1997
  return;
1690
1998
  }
1691
1999
 
1692
- let canvas = document.getElementById('gpupixel_canvas');
2000
+ let canvas = document.getElementById('facebetter_canvas');
1693
2001
  if (!canvas) {
1694
2002
  canvas = document.createElement('canvas');
1695
- canvas.id = 'gpupixel_canvas';
2003
+ canvas.id = 'facebetter_canvas';
1696
2004
  canvas.style.display = 'none';
1697
2005
  canvas.width = 1;
1698
2006
  canvas.height = 1;
@@ -1702,7 +2010,7 @@
1702
2010
  }
1703
2011
  }
1704
2012
 
1705
- function cleanupGPUPixelCanvas() {
2013
+ function cleanupFacebetterCanvas() {
1706
2014
  // Cleanup is handled by engine instance
1707
2015
  }
1708
2016
 
@@ -1712,8 +2020,8 @@
1712
2020
  getWasmModule: getWasmModule,
1713
2021
  getWasmBuffer: getWasmBuffer,
1714
2022
  toImageData: toImageData,
1715
- ensureGPUPixelCanvas,
1716
- cleanupGPUPixelCanvas
2023
+ ensureFacebetterCanvas,
2024
+ cleanupFacebetterCanvas
1717
2025
  };
1718
2026
 
1719
2027
  // Export factory function for UMD
@@ -1731,42 +2039,68 @@
1731
2039
  }
1732
2040
 
1733
2041
  // Export constants with proper names
1734
- const BeautyType = BeautyType$1;
1735
- const BasicParam = BasicParam$1;
1736
- const ReshapeParam = ReshapeParam$1;
1737
- const MakeupParam = MakeupParam$1;
1738
- const LipstickStyle = LipstickStyle$1;
2042
+ const WhiteningStyle = WhiteningStyle$1;
2043
+ const SmoothingStyle = SmoothingStyle$1;
2044
+ const Reshape = Reshape$1;
2045
+ const LipstickColor = LipstickColor$1;
1739
2046
  const BlushStyle = BlushStyle$1;
1740
- const BackgroundMode = BackgroundMode$1;
2047
+ const BlushColor = BlushColor$1;
2048
+ const ContourStyle = ContourStyle$1;
2049
+ const EyeShadowStyle = EyeShadowStyle$1;
2050
+ const EyeShadowColor = EyeShadowColor$1;
2051
+ const EyeLinerStyle = EyeLinerStyle$1;
2052
+ const EyeLinerColor = EyeLinerColor$1;
2053
+ const EyebrowStyle = EyebrowStyle$1;
2054
+ const EyebrowColor = EyebrowColor$1;
2055
+ const EyelashStyle = EyelashStyle$1;
2056
+ const EyelashColor = EyelashColor$1;
2057
+ const PupilColor = PupilColor$1;
2058
+ const ChromaKeyColor = ChromaKeyColor$1;
1741
2059
  const FrameType = FrameType$1;
1742
2060
  const MirrorMode = MirrorMode$1;
1743
- const VirtualBackgroundOptions = VirtualBackgroundOptions$1;
2061
+ const EngineEventCode = EngineEventCode$1;
1744
2062
 
1745
2063
  // Default export
1746
2064
  var index = {
1747
2065
  BeautyEffectEngine: BrowserBeautyEffectEngine,
1748
2066
  EngineConfig,
1749
2067
  FacebetterError,
2068
+ FACEBETTER_AUTH_URL,
2069
+ createDirectAuthFetcher,
2070
+ requestFacebetterAuth,
1750
2071
  ...constants,
1751
2072
  loadWasmModule: loadWasmModule
1752
2073
  };
1753
2074
 
1754
- exports.BackgroundMode = BackgroundMode;
1755
- exports.BasicParam = BasicParam;
1756
2075
  exports.BeautyEffectEngine = BrowserBeautyEffectEngine;
1757
- exports.BeautyType = BeautyType;
2076
+ exports.BlushColor = BlushColor;
1758
2077
  exports.BlushStyle = BlushStyle;
2078
+ exports.ChromaKeyColor = ChromaKeyColor;
2079
+ exports.ContourStyle = ContourStyle;
1759
2080
  exports.EngineConfig = EngineConfig;
2081
+ exports.EngineEventCode = EngineEventCode;
2082
+ exports.EyeLinerColor = EyeLinerColor;
2083
+ exports.EyeLinerStyle = EyeLinerStyle;
2084
+ exports.EyeShadowColor = EyeShadowColor;
2085
+ exports.EyeShadowStyle = EyeShadowStyle;
2086
+ exports.EyebrowColor = EyebrowColor;
2087
+ exports.EyebrowStyle = EyebrowStyle;
2088
+ exports.EyelashColor = EyelashColor;
2089
+ exports.EyelashStyle = EyelashStyle;
2090
+ exports.FACEBETTER_AUTH_URL = FACEBETTER_AUTH_URL;
1760
2091
  exports.FacebetterError = FacebetterError;
1761
2092
  exports.FrameType = FrameType;
1762
- exports.LipstickStyle = LipstickStyle;
1763
- exports.MakeupParam = MakeupParam;
2093
+ exports.LipstickColor = LipstickColor;
1764
2094
  exports.MirrorMode = MirrorMode;
1765
- exports.ReshapeParam = ReshapeParam;
1766
- exports.VirtualBackgroundOptions = VirtualBackgroundOptions;
2095
+ exports.PupilColor = PupilColor;
2096
+ exports.Reshape = Reshape;
2097
+ exports.SmoothingStyle = SmoothingStyle;
2098
+ exports.WhiteningStyle = WhiteningStyle;
1767
2099
  exports.createBeautyEffectEngine = createBeautyEffectEngine;
2100
+ exports.createDirectAuthFetcher = createDirectAuthFetcher;
1768
2101
  exports.default = index;
1769
2102
  exports.loadWasmModule = loadWasmModule;
2103
+ exports.requestFacebetterAuth = requestFacebetterAuth;
1770
2104
 
1771
2105
  Object.defineProperty(exports, '__esModule', { value: true });
1772
2106