central-auth-js-sdk 1.0.12 → 1.0.13

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.
@@ -96,6 +96,7 @@ Call once (e.g. at app load) to set defaults. Returns `{ startSignup, startLogin
96
96
  | `loginMethods` | object | — | Default login methods (see Login config) |
97
97
  | `frsAsSecondFactor` | boolean | `false` | Default: require face verification after primary auth |
98
98
  | `enableFaceRegistration` | boolean | `true` | Whether sign-up includes face registration step |
99
+ | `onBack` | function | — | Default callback when user clicks ← Back on the first screen of signup/login (see [§ 9](#9-returning-to-the-parent-application)) |
99
100
 
100
101
  **Environments:** The SDK uses only `env` to choose the API base URL:
101
102
 
@@ -142,6 +143,7 @@ Starts the **sign-up flow** (email + name → verify → mobile → verify → p
142
143
  | `enableFaceRegistration` | boolean | no | Include face registration step (default from init or `true`) |
143
144
  | **`onSignupComplete(payload)`** | function | no | Called **once** when signup completes. `payload`: `email`, `mobile`, `userId` + any fields from final API (e.g. `access_token`, `refresh_token`). |
144
145
  | **`onContinue()`** | function | no | Called when user clicks **Continue to application** on the success screen. Use to unmount and redirect. |
146
+ | **`onBack()`** | function | no | Called when user clicks **← Back** on the **first screen** of signup. Use to unmount the SDK and return to your app. See [§ 9](#9-returning-to-the-parent-application). |
145
147
  | **`onSwitchToLogin()`** | function | no | Called when user clicks **Sign in** (e.g. “Already have an account? Sign in”). Use to unmount signup and start the login flow in the **same container** (e.g. login + FRS). |
146
148
 
147
149
  **Returns:** `{ unmount, getState, getFlow, getCurrentStep }` or `null` if container is invalid.
@@ -163,6 +165,10 @@ const instance = startSignup({
163
165
  instance.unmount();
164
166
  redirectToDashboard();
165
167
  },
168
+ onBack() {
169
+ instance.unmount();
170
+ showYourAppScreen();
171
+ },
166
172
  onSwitchToLogin() {
167
173
  instance.unmount();
168
174
  startLogin({ container: '#auth-container', env: 'dev', appIdentifier: 'my-app', loginMethods: { emailFrs: true }, onContinue: goToApp, onSignUp: showSignup });
@@ -266,6 +272,7 @@ Starts the **login flow**. If the parent enables **only one** login method, the
266
272
  | **`onSignUp()`** | function | no | Called when user clicks **Sign up** on the login screen (e.g. unmount login and start signup in the **same container**). |
267
273
  | **`onLoginComplete(payload)`** | function | no | Called **once** when login completes. `payload`: `identifier` + any fields from final API (e.g. `access_token`, `refresh_token`). |
268
274
  | **`onContinue()`** | function | no | Called when user clicks **Continue to application** on the success screen. |
275
+ | **`onBack()`** | function | no | Called when user clicks **← Back** on the **first screen** of login. Use to unmount the SDK and return to your app. See [§ 9](#9-returning-to-the-parent-application). |
269
276
 
270
277
  **Login methods** (`loginMethods`):
271
278
 
@@ -298,6 +305,10 @@ startLogin({
298
305
  instance.unmount();
299
306
  redirectToApp();
300
307
  },
308
+ onBack() {
309
+ instance.unmount();
310
+ redirectToApp();
311
+ },
301
312
  onSignUp() {
302
313
  instance.unmount();
303
314
  startSignup({ container: '#auth-container', env: 'dev', appIdentifier: '...', onContinue: goToApp, onSwitchToLogin: showLoginFrs });
@@ -321,6 +332,7 @@ startLogin({
321
332
  frsAsSecondFactor: true,
322
333
  onLoginComplete(payload) { /* ... */ },
323
334
  onContinue() { /* ... */ },
335
+ onBack() { /* unmount + return to parent app */ },
324
336
  onSignUp() { /* ... */ },
325
337
  });
326
338
  ```
@@ -372,16 +384,79 @@ You can also read **`instance.getState()`** after the flow completes; it include
372
384
 
373
385
  ## 9. Returning to the parent application
374
386
 
375
- When the user clicks **Continue to application** on the success screen, the SDK calls **`onContinue()`** if you provided it. Use it to unmount the SDK and show your app (e.g. redirect or switch view):
387
+ The SDK does **not** automatically navigate your app when the user leaves auth. Your parent app is responsible for showing/hiding the auth container and routing the user back to your own UI. Use the callbacks below.
388
+
389
+ ### Two kinds of “Back”
390
+
391
+ | Where | What happens |
392
+ |-------|----------------|
393
+ | **First screen** (signup email entry, login method picker, or single-method identifier screen) | **← Back** calls **`onBack()`** — intended to leave auth and return to your app |
394
+ | **Later steps** (password, OTP, face capture, etc.) | **← Back** goes to the **previous step inside the SDK** only. Does **not** call `onBack()` |
395
+
396
+ The first-screen back button is always shown. What happens when it is clicked depends on whether you pass **`onBack`**.
397
+
398
+ ### If you omit `onBack`
399
+
400
+ The SDK uses a **default**: it unmounts itself (removes its UI from the container and cleans up theme). It does **not** show your app’s main screen, change routes, or hide overlays — you must do that yourself if needed.
401
+
402
+ ### What the parent app should do (recommended)
403
+
404
+ Define one helper (e.g. `returnToApp`) and pass it to **`onBack`** and usually **`onContinue`** as well:
405
+
406
+ ```javascript
407
+ let authInstance = null;
408
+
409
+ function returnToApp() {
410
+ if (authInstance) authInstance.unmount();
411
+ authInstance = null;
412
+ document.getElementById('auth-container').style.display = 'none';
413
+ document.getElementById('app-root').style.display = 'block';
414
+ // or: router.push('/'); window.history.back(); etc.
415
+ }
416
+
417
+ authInstance = startLogin({
418
+ container: '#auth-container',
419
+ env: 'dev',
420
+ loginMethods: { emailPassword: true },
421
+ onBack: returnToApp,
422
+ onContinue: returnToApp,
423
+ onLoginComplete(payload) {
424
+ saveToken(payload.access_token);
425
+ returnToApp();
426
+ },
427
+ });
428
+ ```
429
+
430
+ **Typical steps inside `returnToApp` (or equivalent):**
431
+
432
+ 1. Call **`instance.unmount()`** — removes SDK DOM and theme
433
+ 2. Clear your reference to the instance (`authInstance = null`)
434
+ 3. Hide the auth container and show your main app UI (or navigate with your router)
435
+
436
+ You can set **`onBack`** once in **`initializeAuth({ onBack: returnToApp, ... })`** and reuse it in `startSignup` / `startLogin` (per-call config overrides defaults).
437
+
438
+ ### Success screen — `onContinue()`
439
+
440
+ When the user clicks **Continue to application** on the success screen, the SDK calls **`onContinue()`** if you provided it. Use the same pattern as **`onBack`**: unmount the SDK and show your app:
376
441
 
377
442
  ```javascript
378
443
  onContinue() {
379
444
  instance.unmount();
380
445
  window.location.href = '/dashboard';
381
- // or: showYourAppView();
446
+ // or: returnToApp();
382
447
  }
383
448
  ```
384
449
 
450
+ ### Summary
451
+
452
+ | Callback | When it fires | Parent app action |
453
+ |----------|---------------|-------------------|
454
+ | **`onBack()`** | User clicks ← Back on **first screen** | Unmount SDK + return to your app UI |
455
+ | **`onContinue()`** | User clicks Continue on **success screen** | Unmount SDK + enter your app (often same as `onBack`) |
456
+ | *(none)* | First-screen back, if `onBack` omitted | SDK unmounts only; parent UI unchanged |
457
+
458
+ Returning to the parent app is **not automatic** beyond SDK unmount. Integrators should pass **`onBack`** (and **`onContinue`**) to fully control navigation in their application.
459
+
385
460
  ---
386
461
 
387
462
  ## 10. Full integration example
@@ -421,6 +496,7 @@ function showSignup() {
421
496
  goToApp();
422
497
  },
423
498
  onContinue: goToApp,
499
+ onBack: goToApp,
424
500
  onSwitchToLogin: showLoginFrs,
425
501
  });
426
502
  }
@@ -435,6 +511,7 @@ function showLoginFrs() {
435
511
  goToApp();
436
512
  },
437
513
  onContinue: goToApp,
514
+ onBack: goToApp,
438
515
  onSignUp: showSignup,
439
516
  });
440
517
  }
@@ -449,6 +526,7 @@ function showLogin(loginMethods = { emailPassword: true }) {
449
526
  goToApp();
450
527
  },
451
528
  onContinue: goToApp,
529
+ onBack: goToApp,
452
530
  onSignUp: showSignup,
453
531
  });
454
532
  }
package/login.js CHANGED
@@ -208,6 +208,7 @@ export function mountLogin(container, options = {}) {
208
208
  const identifier = state.get('identifier');
209
209
  let apiResult = null;
210
210
 
211
+ layout.setLoading(true, 'Verifying your face…');
211
212
  try {
212
213
  if (method === LOGIN_METHODS.EMAIL_FRS) {
213
214
  apiResult = await api.loginEmailFr(identifier, file, null, config.appIdentifier);
@@ -230,6 +231,8 @@ export function mountLogin(container, options = {}) {
230
231
  const msg = err?.data?.error || err?.message || 'Verification failed';
231
232
  state.set('error', msg);
232
233
  showError(msg);
234
+ } finally {
235
+ layout.setLoading(false);
233
236
  }
234
237
  }
235
238
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "central-auth-js-sdk",
3
- "version": "1.0.12",
3
+ "version": "1.0.13",
4
4
  "description": "Vanilla JS sign-up and login flow SDK — embeddable auth UI with configurable methods",
5
5
  "type": "module",
6
6
  "main": "index.js",
package/signup.js CHANGED
@@ -229,6 +229,7 @@ export function mount(container, options = {}) {
229
229
  showError('Please capture your face first.');
230
230
  return;
231
231
  }
232
+ layout.setLoading(true, 'Registering your face…');
232
233
  try {
233
234
  let apiResult = null;
234
235
  if (isSocial()) {
@@ -253,6 +254,8 @@ export function mount(container, options = {}) {
253
254
  const msg = err?.data?.error || err?.message || 'Face registration failed';
254
255
  state.set('error', msg);
255
256
  showError(msg);
257
+ } finally {
258
+ layout.setLoading(false);
256
259
  }
257
260
  }
258
261
 
package/styles.css CHANGED
@@ -300,6 +300,67 @@
300
300
  display: none;
301
301
  }
302
302
 
303
+ /* ============================= LOADING BAR ============================= */
304
+
305
+ .wf-loading-bar {
306
+ height: 3px;
307
+ margin: -0.25rem 0 1rem;
308
+ border-radius: 999px;
309
+ background: var(--wf-muted-bg, rgba(0, 0, 0, 0.06));
310
+ overflow: hidden;
311
+ }
312
+
313
+ .wf-loading-bar.hidden {
314
+ display: none;
315
+ }
316
+
317
+ .wf-loading-bar-indeterminate {
318
+ height: 100%;
319
+ width: 40%;
320
+ border-radius: 999px;
321
+ background: linear-gradient(90deg, var(--wf-primary-grad-start) 0%, var(--wf-primary) 50%, var(--wf-primary-grad-end) 100%);
322
+ animation: wf-loading-slide 1.2s ease-in-out infinite;
323
+ }
324
+
325
+ @keyframes wf-loading-slide {
326
+ 0% { transform: translateX(-100%); }
327
+ 100% { transform: translateX(350%); }
328
+ }
329
+
330
+ .wf-loading-status {
331
+ display: flex;
332
+ align-items: center;
333
+ justify-content: center;
334
+ gap: 0.5rem;
335
+ margin-bottom: 1rem;
336
+ padding: 0.5rem 0.75rem;
337
+ border-radius: var(--wf-radius);
338
+ background: var(--wf-muted-bg, rgba(0, 0, 0, 0.04));
339
+ color: var(--wf-fg-soft);
340
+ font-size: 0.8125rem;
341
+ font-weight: 500;
342
+ }
343
+
344
+ .wf-loading-status.hidden {
345
+ display: none;
346
+ }
347
+
348
+ .wf-loading-status::before {
349
+ content: '';
350
+ width: 1rem;
351
+ height: 1rem;
352
+ border: 2px solid var(--wf-border);
353
+ border-top-color: var(--wf-primary);
354
+ border-radius: 50%;
355
+ animation: wf-spin 0.7s linear infinite;
356
+ flex-shrink: 0;
357
+ }
358
+
359
+ #wf-form-inner.wf-loading-active {
360
+ pointer-events: none;
361
+ opacity: 0.65;
362
+ }
363
+
303
364
  /* ============================= PAGE FOOTER ============================= */
304
365
 
305
366
  .wf-page-footer {
package/ui/layout.js CHANGED
@@ -45,6 +45,10 @@ export function createLayout(container) {
45
45
  </div>
46
46
  </div>
47
47
  <div class="wf-card-body">
48
+ <div class="wf-loading-bar hidden" id="wf-loading-bar" role="progressbar" aria-busy="false" aria-label="Loading">
49
+ <div class="wf-loading-bar-indeterminate"></div>
50
+ </div>
51
+ <div class="wf-loading-status hidden" id="wf-loading-status"></div>
48
52
  <div class="wf-error-banner hidden" id="wf-error-banner" role="alert"></div>
49
53
  <div id="wf-form-inner"></div>
50
54
  </div>
@@ -60,6 +64,8 @@ export function createLayout(container) {
60
64
  const ringNum = root.querySelector('#wf-ring-num');
61
65
  const ring = root.querySelector('.wf-ring');
62
66
  const errorBanner = root.querySelector('#wf-error-banner');
67
+ const loadingBar = root.querySelector('#wf-loading-bar');
68
+ const loadingStatus = root.querySelector('#wf-loading-status');
63
69
 
64
70
  function setContent(el) {
65
71
  formInner.innerHTML = '';
@@ -90,11 +96,33 @@ export function createLayout(container) {
90
96
  errorBanner.classList.remove('hidden');
91
97
  }
92
98
 
99
+ function setLoading(active, message = '') {
100
+ if (active) {
101
+ loadingBar.classList.remove('hidden');
102
+ loadingBar.setAttribute('aria-busy', 'true');
103
+ formInner.classList.add('wf-loading-active');
104
+ if (message) {
105
+ loadingStatus.textContent = message;
106
+ loadingStatus.classList.remove('hidden');
107
+ } else {
108
+ loadingStatus.textContent = '';
109
+ loadingStatus.classList.add('hidden');
110
+ }
111
+ } else {
112
+ loadingBar.classList.add('hidden');
113
+ loadingBar.setAttribute('aria-busy', 'false');
114
+ formInner.classList.remove('wf-loading-active');
115
+ loadingStatus.classList.add('hidden');
116
+ loadingStatus.textContent = '';
117
+ }
118
+ }
119
+
93
120
  return {
94
121
  root,
95
122
  setContent,
96
123
  updateProgress,
97
124
  showError,
125
+ setLoading,
98
126
  renderContent: setContent,
99
127
  };
100
128
  }