@glideidentity/web-client-sdk 5.0.1-beta.0 → 5.1.1-beta.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (97) hide show
  1. package/README.md +8 -108
  2. package/dist/adapters/angular/index.js +1 -0
  3. package/dist/adapters/angular/phone-auth.service.d.ts +18 -0
  4. package/dist/adapters/angular/phone-auth.service.js +26 -0
  5. package/dist/adapters/react/index.js +3 -0
  6. package/dist/adapters/react/useClient.js +1 -0
  7. package/dist/adapters/react/usePhoneAuth.js +16 -1
  8. package/dist/adapters/vanilla/client.js +1 -0
  9. package/dist/adapters/vanilla/index.js +1 -0
  10. package/dist/adapters/vanilla/phone-auth.js +31 -0
  11. package/dist/adapters/vue/index.js +4 -0
  12. package/dist/adapters/vue/useClient.js +5 -0
  13. package/dist/adapters/vue/usePhoneAuth.js +20 -1
  14. package/dist/browser/web-client-sdk.min.js +1 -2
  15. package/dist/browser.js +6 -0
  16. package/dist/core/client.js +12 -0
  17. package/dist/core/logger.js +81 -1
  18. package/dist/core/phone-auth/api-types.d.ts +1 -4
  19. package/dist/core/phone-auth/api-types.js +83 -0
  20. package/dist/core/phone-auth/client.js +374 -38
  21. package/dist/core/phone-auth/error-utils.js +83 -1
  22. package/dist/core/phone-auth/index.d.ts +1 -1
  23. package/dist/core/phone-auth/index.js +2 -2
  24. package/dist/core/phone-auth/status-types.d.ts +78 -0
  25. package/dist/core/phone-auth/status-types.js +17 -0
  26. package/dist/core/phone-auth/strategies/desktop.d.ts +2 -0
  27. package/dist/core/phone-auth/strategies/desktop.js +136 -13
  28. package/dist/core/phone-auth/strategies/index.d.ts +4 -0
  29. package/dist/core/phone-auth/strategies/index.js +4 -0
  30. package/dist/core/phone-auth/strategies/link.d.ts +2 -0
  31. package/dist/core/phone-auth/strategies/link.js +97 -13
  32. package/dist/core/phone-auth/strategies/ts43.d.ts +19 -0
  33. package/dist/core/phone-auth/strategies/ts43.js +33 -2
  34. package/dist/core/phone-auth/strategies/types.js +4 -0
  35. package/dist/core/phone-auth/type-guards.js +131 -0
  36. package/dist/core/phone-auth/types.d.ts +5 -0
  37. package/dist/core/phone-auth/types.js +32 -0
  38. package/dist/core/phone-auth/ui/mobile-debug-console.js +28 -2
  39. package/dist/core/phone-auth/ui/modal.d.ts +55 -33
  40. package/dist/core/phone-auth/ui/modal.js +422 -889
  41. package/dist/core/phone-auth/validation-utils.d.ts +0 -9
  42. package/dist/core/phone-auth/validation-utils.js +34 -25
  43. package/dist/core/version.js +2 -1
  44. package/dist/esm/adapters/angular/index.js +1 -0
  45. package/dist/esm/adapters/angular/phone-auth.service.d.ts +18 -0
  46. package/dist/esm/adapters/angular/phone-auth.service.js +26 -0
  47. package/dist/esm/adapters/react/index.js +3 -0
  48. package/dist/esm/adapters/react/useClient.js +1 -0
  49. package/dist/esm/adapters/react/usePhoneAuth.js +16 -1
  50. package/dist/esm/adapters/vanilla/client.js +1 -0
  51. package/dist/esm/adapters/vanilla/index.js +1 -0
  52. package/dist/esm/adapters/vanilla/phone-auth.d.ts +24 -0
  53. package/dist/esm/adapters/vanilla/phone-auth.js +31 -0
  54. package/dist/esm/adapters/vue/index.js +4 -0
  55. package/dist/esm/adapters/vue/useClient.js +5 -0
  56. package/dist/esm/adapters/vue/usePhoneAuth.js +20 -1
  57. package/dist/esm/browser.js +6 -0
  58. package/dist/esm/core/client.d.ts +10 -0
  59. package/dist/esm/core/client.js +12 -0
  60. package/dist/esm/core/logger.d.ts +53 -0
  61. package/dist/esm/core/logger.js +81 -1
  62. package/dist/esm/core/phone-auth/api-types.d.ts +313 -1
  63. package/dist/esm/core/phone-auth/api-types.js +83 -0
  64. package/dist/esm/core/phone-auth/client.d.ts +144 -0
  65. package/dist/esm/core/phone-auth/client.js +375 -39
  66. package/dist/esm/core/phone-auth/error-utils.d.ts +29 -0
  67. package/dist/esm/core/phone-auth/error-utils.js +83 -1
  68. package/dist/esm/core/phone-auth/index.d.ts +1 -1
  69. package/dist/esm/core/phone-auth/index.js +4 -2
  70. package/dist/esm/core/phone-auth/status-types.d.ts +78 -0
  71. package/dist/esm/core/phone-auth/status-types.js +17 -0
  72. package/dist/esm/core/phone-auth/strategies/desktop.d.ts +65 -0
  73. package/dist/esm/core/phone-auth/strategies/desktop.js +136 -13
  74. package/dist/esm/core/phone-auth/strategies/index.d.ts +4 -0
  75. package/dist/esm/core/phone-auth/strategies/index.js +4 -0
  76. package/dist/esm/core/phone-auth/strategies/link.d.ts +50 -0
  77. package/dist/esm/core/phone-auth/strategies/link.js +97 -13
  78. package/dist/esm/core/phone-auth/strategies/ts43.d.ts +19 -0
  79. package/dist/esm/core/phone-auth/strategies/ts43.js +33 -2
  80. package/dist/esm/core/phone-auth/strategies/types.d.ts +13 -0
  81. package/dist/esm/core/phone-auth/strategies/types.js +4 -0
  82. package/dist/esm/core/phone-auth/type-guards.d.ts +128 -0
  83. package/dist/esm/core/phone-auth/type-guards.js +131 -0
  84. package/dist/esm/core/phone-auth/types.d.ts +113 -0
  85. package/dist/esm/core/phone-auth/types.js +32 -0
  86. package/dist/esm/core/phone-auth/ui/mobile-debug-console.d.ts +4 -0
  87. package/dist/esm/core/phone-auth/ui/mobile-debug-console.js +28 -2
  88. package/dist/esm/core/phone-auth/ui/modal.d.ts +68 -27
  89. package/dist/esm/core/phone-auth/ui/modal.js +422 -889
  90. package/dist/esm/core/phone-auth/validation-utils.d.ts +26 -4
  91. package/dist/esm/core/phone-auth/validation-utils.js +34 -24
  92. package/dist/esm/core/types.d.ts +35 -0
  93. package/dist/esm/core/version.js +2 -1
  94. package/dist/esm/index.js +9 -1
  95. package/dist/index.js +7 -0
  96. package/package.json +1 -1
  97. package/dist/browser/web-client-sdk.min.js.LICENSE.txt +0 -1
@@ -1,40 +1,55 @@
1
1
  "use strict";
2
+ /**
3
+ * Modal UI Component for Phone Authentication
4
+ *
5
+ * This file creates the UI components (modals, buttons) that are shown
6
+ * when the SDK is NOT in headless mode. Think of it like a popup window
7
+ * that handles the authentication flow for you.
8
+ */
9
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
10
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
11
+ return new (P || (P = Promise))(function (resolve, reject) {
12
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
13
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
14
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
15
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
16
+ });
17
+ };
2
18
  Object.defineProperty(exports, "__esModule", { value: true });
3
19
  exports.AuthModal = void 0;
20
+ /**
21
+ * Creates and manages a modal dialog for authentication
22
+ *
23
+ * @example
24
+ * const modal = new AuthModal({
25
+ * title: "Verify Your Phone",
26
+ * description: "Complete authentication to continue"
27
+ * });
28
+ * modal.show();
29
+ */
4
30
  class AuthModal {
5
31
  constructor(options, callbacks) {
6
32
  this.container = null;
7
33
  this.backdrop = null;
8
34
  this.isOpen = false;
9
- this.currentStep = 'os-choice';
10
- this.qrCodeData = null;
11
- this.statusMessage = '';
12
- this.originalBodyOverflow = '';
13
- this.isClosing = false;
14
- this.iconApple = `<svg class="glide-icon glide-icon-os" viewBox="0 0 384 512" fill="currentColor"><path d="M318.7 268.7c-.2-36.7 16.4-64.4 50-84.8-18.8-26.9-47.2-41.7-84.7-44.6-35.5-2.8-74.3 20.7-88.5 20.7-15 0-49.4-19.7-76.4-19.7C63.3 141.2 4 184.8 4 273.5q0 39.3 14.4 81.2c12.8 36.7 59 126.7 107.2 125.2 25.2-.6 43-17.9 75.8-17.9 31.8 0 48.3 17.9 76.4 17.9 48.6-.7 90.4-82.5 102.6-119.3-65.2-30.7-61.7-90-61.7-91.9zm-56.6-164.2c27.3-32.4 24.8-61.9 24-72.5-24.1 1.4-52 16.4-67.9 34.9-17.5 19.8-27.8 44.3-25.6 71.9 26.1 2 52.3-11.4 69.5-34.3z"/></svg>`;
15
- this.iconAndroid = `<svg class="glide-icon glide-icon-os" viewBox="0 0 576 512" fill="currentColor"><path d="M420.55,301.93a24,24,0,1,1,24-24,24,24,0,0,1-24,24m-265.1,0a24,24,0,1,1,24-24,24,24,0,0,1-24,24m273.7-144.48,47.94-83a10,10,0,1,0-17.27-10h0l-48.54,84.07a301.25,301.25,0,0,0-246.56,0L116.18,64.45a10,10,0,1,0-17.27,10h0l47.94,83C64.53,202.22,8.24,285.55,0,384H576c-8.24-98.45-64.54-181.78-146.85-226.55"/></svg>`;
16
- this.iconBack = `<svg class="glide-icon" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"><line x1="19" y1="12" x2="5" y2="12"></line><polyline points="12 19 5 12 12 5"></polyline></svg>`;
17
35
  this.options = options || {};
18
36
  this.callbacks = callbacks || {};
19
- this.theme = this.options.theme || 'auto';
37
+ // Bind escape key handler
20
38
  this.handleEscapeKey = this.handleEscapeKey.bind(this);
21
39
  }
22
- shouldUseDarkMode() {
23
- if (this.theme === 'dark')
24
- return true;
25
- if (this.theme === 'light')
26
- return false;
27
- return window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches;
28
- }
29
40
  handleEscapeKey(event) {
30
41
  var _a, _b;
31
42
  if (event.key === 'Escape' && this.isOpen) {
43
+ // Check if escape closing is enabled (default true)
32
44
  if (((_a = this.options) === null || _a === void 0 ? void 0 : _a.closeOnEscape) !== false) {
33
- (_b = this.closeCallback) === null || _b === void 0 ? void 0 : _b.call(this);
45
+ (_b = this.closeCallback) === null || _b === void 0 ? void 0 : _b.call(this); // Call the cancellation callback if set
34
46
  this.close();
35
47
  }
36
48
  }
37
49
  }
50
+ /**
51
+ * Escape HTML to prevent XSS attacks
52
+ */
38
53
  escapeHtml(unsafe) {
39
54
  return unsafe
40
55
  .replace(/&/g, "&amp;")
@@ -43,1023 +58,541 @@ class AuthModal {
43
58
  .replace(/"/g, "&quot;")
44
59
  .replace(/'/g, "&#039;");
45
60
  }
46
- showQRCode(qrCodeData, statusMessage = 'Scan with your phone camera') {
47
- this.qrCodeData = qrCodeData;
48
- this.statusMessage = statusMessage;
49
- const mode = this.options.viewMode || 'toggle';
50
- if (typeof qrCodeData === 'string') {
51
- this.renderToggleMode(qrCodeData, statusMessage);
61
+ /**
62
+ * Shows the modal with a QR code for desktop authentication
63
+ * Supports both single QR code (legacy) and dual-platform QR codes (iOS + Android)
64
+ */
65
+ showQRCode(qrCodeData, statusMessage = 'Scan QR code with your phone') {
66
+ console.log('[Modal] showQRCode called with:', qrCodeData);
67
+ // If modal is already open, don't recreate it
68
+ if (this.isOpen) {
69
+ console.log('[Modal] Modal already open, skipping recreation');
70
+ return;
52
71
  }
53
- else {
54
- switch (mode) {
55
- case 'dual':
56
- this.renderDualMode(qrCodeData, statusMessage);
57
- break;
58
- case 'pre-step':
59
- this.renderPreStepMode(qrCodeData, statusMessage);
60
- break;
61
- case 'toggle':
62
- default:
63
- this.renderToggleMode(qrCodeData, statusMessage);
64
- break;
72
+ // Check if it's the new dual-platform format with VALID Android QR code
73
+ if (typeof qrCodeData === 'object' && qrCodeData.iosQRCode) {
74
+ const hasValidAndroidQR = qrCodeData.androidQRCode &&
75
+ qrCodeData.androidQRCode.length > 0 &&
76
+ qrCodeData.androidQRCode !== qrCodeData.iosQRCode;
77
+ console.log('[Modal] Has valid Android QR?', hasValidAndroidQR);
78
+ if (hasValidAndroidQR) {
79
+ console.log('[Modal] Using dual-platform modal');
80
+ this.createDualPlatformQRModal(qrCodeData, statusMessage);
81
+ // Note: createDualPlatformQRModal calls show() internally
82
+ return;
65
83
  }
66
- }
67
- this.show();
68
- }
69
- updateStatus(status, isError = false) {
70
- this.statusMessage = status;
71
- if (this.container) {
72
- const el = this.container.querySelector('#glide-status');
73
- if (el) {
74
- el.textContent = status;
75
- if (isError) {
76
- el.style.color = '#ff3b30';
77
- }
78
- else {
79
- el.style.color = '';
80
- }
84
+ else {
85
+ console.log('[Modal] Android QR missing/empty, using single iOS QR');
86
+ // Only iOS QR code available - show single QR
87
+ this.createModal(`
88
+ <div class="glide-auth-qr-container">
89
+ <img src="${this.escapeHtml(qrCodeData.iosQRCode)}" alt="QR Code" class="glide-auth-qr-code" />
90
+ <p class="glide-auth-status">Scan with your iPhone to authenticate</p>
91
+ </div>
92
+ `);
81
93
  }
82
94
  }
83
- }
84
- setCloseCallback(callback) {
85
- this.closeCallback = callback;
86
- }
87
- renderToggleMode(data, msg) {
88
- let iosQR = '';
89
- let androidQR = '';
90
- if (typeof data === 'string') {
91
- iosQR = data;
92
- androidQR = data;
93
- }
94
95
  else {
95
- iosQR = data.iosQRCode;
96
- androidQR = data.androidQRCode || data.iosQRCode;
97
- }
98
- const content = `
99
- <div class="glide-content">
100
- <div class="glide-toggle" id="glide-toggle" data-active="ios">
101
- <div class="glide-toggle-slider"></div>
102
- <button class="glide-btn glide-toggle-btn active" data-platform="ios">${this.iconApple}<span>iOS</span></button>
103
- <button class="glide-btn glide-toggle-btn" data-platform="android">${this.iconAndroid}<span>Android</span></button>
96
+ console.log('[Modal] Using legacy single QR code modal');
97
+ // Legacy single QR code
98
+ this.createModal(`
99
+ <div class="glide-auth-qr-container">
100
+ <img src="${this.escapeHtml(qrCodeData)}" alt="QR Code" class="glide-auth-qr-code" />
101
+ <p class="glide-auth-status">${this.escapeHtml(statusMessage)}</p>
104
102
  </div>
105
-
106
- <div class="glide-qr-area">
107
- <img
108
- class="glide-qr-img"
109
- id="glide-qr-img"
110
- src="${this.escapeHtml(iosQR)}"
111
- alt="QR Code"
112
- class="glide-qr-img"
113
- data-ios="${this.escapeHtml(iosQR)}"
114
- data-android="${this.escapeHtml(androidQR)}"
115
- />
116
-
117
- <!-- Animation Overlay (Inside QR Area for centering) -->
118
- <div id="glide-phone-overlay" class="glide-phone-animation-overlay">
119
- <div class="glide-outlined-phone">
120
- <div class="glide-scan-line"></div>
121
- </div>
122
- </div>
123
- </div>
124
- </div>
125
- `;
126
- this.createModal(content, '', true);
127
- this.setupPlatformToggles();
128
- this.setupHelpInteraction();
103
+ `);
104
+ }
105
+ this.show();
129
106
  }
130
- renderDualMode(data, msg) {
131
- const qrOverlayHtml = `
132
- <div class="glide-phone-animation-overlay">
133
- <div class="glide-outlined-phone">
134
- <div class="glide-scan-line"></div>
135
- </div>
136
- </div>
137
- `;
107
+ /**
108
+ * Creates a modal with iOS/Android platform toggle
109
+ */
110
+ createDualPlatformQRModal(qrCodeData, statusMessage) {
138
111
  this.createModal(`
139
- <div class="glide-content glide-dual-mode">
140
- <div class="glide-dual-container">
141
- <div class="glide-dual-item">
142
- <div class="glide-os-logo">
143
- ${this.iconApple}
144
- <span>iOS</span>
145
- </div>
146
- <div class="glide-qr-area">
147
- <img src="${this.escapeHtml(data.iosQRCode)}" alt="iOS QR" class="glide-qr-img" />
148
- ${qrOverlayHtml}
149
- </div>
150
- </div>
151
- <div class="glide-dual-separator">
152
- <div class="glide-separator-line"></div>
153
- <span class="glide-separator-text">or</span>
154
- <div class="glide-separator-line"></div>
155
- </div>
156
- <div class="glide-dual-item">
157
- <div class="glide-os-logo">
158
- ${this.iconAndroid}
159
- <span>Android</span>
160
- </div>
161
- <div class="glide-qr-area">
162
- <img src="${this.escapeHtml(data.androidQRCode || data.iosQRCode)}" alt="Android QR" class="glide-qr-img" />
163
- ${qrOverlayHtml}
164
- </div>
165
- </div>
112
+ <div class="glide-auth-qr-container">
113
+ <!-- Platform Switcher -->
114
+ <div class="glide-platform-switcher">
115
+ <button class="glide-platform-btn glide-platform-ios active" data-platform="ios">
116
+ <svg width="18" height="18" viewBox="0 0 24 24" fill="currentColor">
117
+ <path d="M17.05 20.28c-.98.95-2.05.88-3.08.4-1.09-.5-2.08-.48-3.24 0-1.44.62-2.2.44-3.06-.4C2.79 15.25 3.51 7.59 9.05 7.31c1.35.07 2.29.74 3.08.8 1.18-.24 2.31-.93 3.57-.84 1.51.12 2.65.72 3.4 1.8-3.12 1.87-2.38 5.98.48 7.13-.57 1.5-1.31 2.99-2.54 4.09l.01-.01zM12.03 7.25c-.15-2.23 1.66-4.07 3.74-4.25.29 2.58-2.34 4.5-3.74 4.25z"/>
118
+ </svg>
119
+ <span>iOS</span>
120
+ </button>
121
+ <button class="glide-platform-btn glide-platform-android" data-platform="android">
122
+ <svg width="18" height="18" viewBox="0 0 24 24" fill="currentColor">
123
+ <path d="M17.6 9.48l1.84-3.18c.16-.31.04-.69-.26-.85-.29-.15-.65-.06-.83.22l-1.88 3.24c-2.86-1.21-6.08-1.21-8.94 0L5.65 5.67c-.19-.28-.54-.38-.83-.22-.3.16-.42.54-.26.85l1.84 3.18C4.08 11.08 2.4 13.97 2.4 17.2h19.2c0-3.23-1.68-6.12-4.0-7.72zM7.0 14.8c-.66 0-1.2-.54-1.2-1.2s.54-1.2 1.2-1.2 1.2.54 1.2 1.2-.54 1.2-1.2 1.2zm10 0c-.66 0-1.2-.54-1.2-1.2s.54-1.2 1.2-1.2 1.2.54 1.2 1.2-.54 1.2-1.2 1.2z"/>
124
+ </svg>
125
+ <span>Android</span>
126
+ </button>
166
127
  </div>
128
+
129
+ <!-- QR Code Image -->
130
+ <img
131
+ id="glide-qr-code-img"
132
+ src="${this.escapeHtml(qrCodeData.iosQRCode)}"
133
+ alt="QR Code"
134
+ class="glide-auth-qr-code"
135
+ data-ios="${this.escapeHtml(qrCodeData.iosQRCode)}"
136
+ data-android="${this.escapeHtml(qrCodeData.androidQRCode || '')}"
137
+ />
138
+
139
+ <!-- Status Message -->
140
+ <p class="glide-auth-status" id="glide-platform-message">Scan with your iPhone to authenticate</p>
167
141
  </div>
168
- `, 'glide-modal-wide', true);
169
- this.setupHelpInteraction();
170
- }
171
- renderPreStepMode(data, msg) {
172
- this.currentStep = 'os-choice';
173
- const contentHtml = `
174
- <div class="glide-pre-step-container">
175
- <button class="glide-os-choice-btn" id="glide-btn-ios">
176
- ${this.iconApple}
177
- <span>iOS</span>
178
- </button>
179
- <button class="glide-os-choice-btn" id="glide-btn-android">
180
- ${this.iconAndroid}
181
- <span>Android</span>
182
- </button>
183
- </div>
184
- `;
185
- this.createModal(contentHtml, '', false);
186
- this.setupPreStepListeners();
142
+ `);
143
+ // IMPORTANT: Call show() to actually display the modal!
187
144
  this.show();
188
145
  }
189
- setupPreStepListeners() {
190
- var _a, _b;
191
- if (!this.container)
192
- return;
193
- (_a = this.container.querySelector('#glide-btn-ios')) === null || _a === void 0 ? void 0 : _a.addEventListener('click', () => {
194
- this.currentStep = 'ios-qr';
195
- this.updatePreStepUI();
196
- });
197
- (_b = this.container.querySelector('#glide-btn-android')) === null || _b === void 0 ? void 0 : _b.addEventListener('click', () => {
198
- this.currentStep = 'android-qr';
199
- this.updatePreStepUI();
200
- });
146
+ /**
147
+ * Sets a callback to be called when the modal is cancelled/closed
148
+ */
149
+ setCloseCallback(callback) {
150
+ this.closeCallback = callback;
201
151
  }
202
- updatePreStepUI() {
203
- var _a, _b, _c;
204
- if (!this.container)
205
- return;
206
- let contentHtml = '';
207
- if (this.currentStep === 'os-choice') {
208
- contentHtml = `
209
- <div class="glide-pre-step-container">
210
- <button class="glide-os-choice-btn" id="glide-btn-ios">
211
- ${this.iconApple}
212
- <span>iOS</span>
213
- </button>
214
- <button class="glide-os-choice-btn" id="glide-btn-android">
215
- ${this.iconAndroid}
216
- <span>Android</span>
152
+ /**
153
+ * Shows the modal with a button for Link authentication (App Clips)
154
+ * IMPORTANT: The button click is required for iOS to recognize the app link
155
+ */
156
+ showLinkButton(url, buttonText) {
157
+ return new Promise((resolve) => {
158
+ var _a, _b, _c;
159
+ const text = buttonText || ((_a = this.options) === null || _a === void 0 ? void 0 : _a.buttonText) || 'Open Verification App';
160
+ this.createModal(`
161
+ <div class="glide-auth-link-container">
162
+ <p class="glide-auth-description">
163
+ ${((_b = this.options) === null || _b === void 0 ? void 0 : _b.description) || 'Click below to verify your phone number'}
164
+ </p>
165
+ <button class="glide-auth-button" id="glide-auth-link-button">
166
+ ${text}
217
167
  </button>
168
+ <p class="glide-auth-helper-text">
169
+ You'll be redirected to complete verification
170
+ </p>
218
171
  </div>
219
- `;
220
- this.createModal(contentHtml, '', false);
221
- this.setupPreStepListeners();
222
- }
223
- else if (this.currentStep === 'ios-qr') {
224
- const qr = (typeof this.qrCodeData === 'object' && ((_a = this.qrCodeData) === null || _a === void 0 ? void 0 : _a.iosQRCode))
225
- ? this.qrCodeData.iosQRCode
226
- : this.qrCodeData;
227
- contentHtml = `
228
- <div class="glide-content">
229
- <div class="glide-qr-area">
230
- <img src="${this.escapeHtml(qr)}" alt="QR Code" class="glide-qr-img" />
231
-
232
- <!-- Animation Overlay -->
233
- <div id="glide-phone-overlay" class="glide-phone-animation-overlay">
234
- <div class="glide-outlined-phone">
235
- <div class="glide-scan-line"></div>
236
- </div>
237
- </div>
238
- </div>
239
- </div>
240
- `;
241
- this.createModal(contentHtml, '', true, true);
242
- this.setupBackButton();
243
- this.setupHelpInteraction();
244
- }
245
- else if (this.currentStep === 'android-qr') {
246
- const qr = (typeof this.qrCodeData === 'object' && ((_b = this.qrCodeData) === null || _b === void 0 ? void 0 : _b.androidQRCode))
247
- ? this.qrCodeData.androidQRCode
248
- : (typeof this.qrCodeData === 'object' && ((_c = this.qrCodeData) === null || _c === void 0 ? void 0 : _c.iosQRCode))
249
- ? this.qrCodeData.iosQRCode
250
- : this.qrCodeData;
251
- contentHtml = `
252
- <div class="glide-content">
253
- <div class="glide-qr-area">
254
- <img src="${this.escapeHtml(qr)}" alt="QR Code" class="glide-qr-img" />
255
-
256
- <!-- Animation Overlay -->
257
- <div id="glide-phone-overlay" class="glide-phone-animation-overlay">
258
- <div class="glide-outlined-phone">
259
- <div class="glide-scan-line"></div>
260
- </div>
261
- </div>
262
- </div>
263
- </div>
264
- `;
265
- this.createModal(contentHtml, '', true, true);
266
- this.setupBackButton();
267
- this.setupHelpInteraction();
268
- }
269
- }
270
- setupBackButton() {
271
- var _a;
272
- if (!this.container)
273
- return;
274
- (_a = this.container.querySelector('#glide-back-btn')) === null || _a === void 0 ? void 0 : _a.addEventListener('click', () => {
275
- this.currentStep = 'os-choice';
276
- this.updatePreStepUI();
172
+ `);
173
+ // Show modal first so elements are in the DOM
174
+ this.show();
175
+ // Add click handler for the button AFTER modal is in DOM
176
+ // CRITICAL: This click event is required for iOS to recognize the app link
177
+ // Do NOT call window.open automatically - it must be triggered by user interaction
178
+ const button = (_c = this.container) === null || _c === void 0 ? void 0 : _c.querySelector('#glide-auth-link-button');
179
+ if (button) {
180
+ button.addEventListener('click', (event) => {
181
+ var _a, _b;
182
+ (_b = (_a = this.callbacks) === null || _a === void 0 ? void 0 : _a.onAuthStart) === null || _b === void 0 ? void 0 : _b.call(_a);
183
+ // User-initiated click is required for app links to work properly on iOS
184
+ // This ensures the OS recognizes it should open an app, not just a browser tab
185
+ window.open(url, '_blank');
186
+ resolve();
187
+ });
188
+ }
189
+ else {
190
+ console.error('[Modal] Link button not found in modal');
191
+ }
277
192
  });
278
193
  }
279
- createModal(content, extraClass = '', includeHelpButton = false, includeBackButton = false) {
280
- var _a, _b, _c, _d, _e;
281
- if (this.isClosing) {
282
- this.isClosing = false;
283
- this.cleanup();
284
- }
285
- if (!this.container) {
286
- this.backdrop = document.createElement('div');
287
- this.backdrop.className = 'glide-backdrop';
288
- this.backdrop.id = 'glide-backdrop';
289
- this.container = document.createElement('div');
290
- this.container.className = `glide-modal ${((_a = this.options) === null || _a === void 0 ? void 0 : _a.className) || ''} ${extraClass}`;
291
- this.container.id = 'glide-modal';
292
- if (this.shouldUseDarkMode()) {
293
- this.container.classList.add('dark');
294
- }
295
- if (this.isOpen) {
296
- document.body.appendChild(this.backdrop);
297
- document.body.appendChild(this.container);
194
+ /**
195
+ * Shows the modal with a button for TS43 authentication
196
+ * IMPORTANT: The button click is required for Digital Credentials API (transient activation)
197
+ */
198
+ showTS43Button(onAuthenticate) {
199
+ return new Promise((resolve, reject) => {
200
+ var _a, _b, _c;
201
+ const text = ((_a = this.options) === null || _a === void 0 ? void 0 : _a.buttonText) || 'Verify with Carrier';
202
+ this.createModal(`
203
+ <div class="glide-auth-ts43-container">
204
+ <p class="glide-auth-description">
205
+ ${((_b = this.options) === null || _b === void 0 ? void 0 : _b.description) || 'Verify using your carrier credentials'}
206
+ </p>
207
+ <button class="glide-auth-button" id="glide-auth-ts43-button">
208
+ ${text}
209
+ </button>
210
+ <p class="glide-auth-helper-text">
211
+ Secure verification through your mobile carrier
212
+ </p>
213
+ </div>
214
+ `);
215
+ // Show modal first so elements are in the DOM
216
+ this.show();
217
+ // Add click handler for the button AFTER modal is in DOM
218
+ // CRITICAL: Digital Credentials API requires "transient activation" (user interaction)
219
+ // Do NOT call onAuthenticate automatically - it must be triggered by user click
220
+ const button = (_c = this.container) === null || _c === void 0 ? void 0 : _c.querySelector('#glide-auth-ts43-button');
221
+ if (button) {
222
+ button.addEventListener('click', (event) => __awaiter(this, void 0, void 0, function* () {
223
+ var _a, _b, _c, _d, _e, _f;
224
+ (_b = (_a = this.callbacks) === null || _a === void 0 ? void 0 : _a.onAuthStart) === null || _b === void 0 ? void 0 : _b.call(_a);
225
+ button.setAttribute('disabled', 'true');
226
+ button.textContent = 'Verifying...';
227
+ try {
228
+ const result = yield onAuthenticate();
229
+ (_d = (_c = this.callbacks) === null || _c === void 0 ? void 0 : _c.onAuthComplete) === null || _d === void 0 ? void 0 : _d.call(_c, result);
230
+ resolve(result);
231
+ this.close();
232
+ }
233
+ catch (error) {
234
+ (_f = (_e = this.callbacks) === null || _e === void 0 ? void 0 : _e.onError) === null || _f === void 0 ? void 0 : _f.call(_e, error);
235
+ button.removeAttribute('disabled');
236
+ button.textContent = text;
237
+ reject(error);
238
+ }
239
+ }));
298
240
  }
299
- }
300
- else {
301
- this.container.className = `glide-modal ${((_b = this.options) === null || _b === void 0 ? void 0 : _b.className) || ''} ${extraClass}`;
302
- if (this.shouldUseDarkMode()) {
303
- this.container.classList.add('dark');
241
+ else {
242
+ console.error('[Modal] TS43 button not found in modal');
304
243
  }
244
+ });
245
+ }
246
+ /**
247
+ * Updates the status message in the modal
248
+ */
249
+ updateStatus(message, isError = false) {
250
+ var _a;
251
+ const statusEl = (_a = this.container) === null || _a === void 0 ? void 0 : _a.querySelector('.glide-auth-status');
252
+ if (statusEl) {
253
+ statusEl.textContent = message;
254
+ statusEl.className = isError ? 'glide-auth-status glide-auth-error' : 'glide-auth-status';
305
255
  }
256
+ }
257
+ /**
258
+ * Creates the modal HTML structure
259
+ */
260
+ createModal(content) {
261
+ var _a, _b, _c;
262
+ // Clean up any existing modal
263
+ this.cleanup();
264
+ // Create backdrop (dark overlay)
265
+ this.backdrop = document.createElement('div');
266
+ this.backdrop.className = 'glide-auth-backdrop';
267
+ this.backdrop.style.cssText = `
268
+ position: fixed;
269
+ top: 0;
270
+ left: 0;
271
+ right: 0;
272
+ bottom: 0;
273
+ background: rgba(0, 0, 0, 0.5);
274
+ z-index: 9998;
275
+ opacity: 0;
276
+ transition: opacity 0.3s ease;
277
+ `;
278
+ // Create modal container
279
+ this.container = document.createElement('div');
280
+ this.container.className = `glide-auth-modal ${((_a = this.options) === null || _a === void 0 ? void 0 : _a.className) || ''}`;
281
+ this.container.style.cssText = `
282
+ position: fixed;
283
+ top: 50%;
284
+ left: 50%;
285
+ transform: translate(-50%, -50%) scale(0.9);
286
+ background: white;
287
+ border-radius: 12px;
288
+ padding: 24px;
289
+ max-width: 400px;
290
+ width: 90%;
291
+ box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3);
292
+ z-index: 9999;
293
+ opacity: 0;
294
+ transition: opacity 0.3s ease, transform 0.3s ease;
295
+ `;
296
+ // Add modal content
306
297
  this.container.innerHTML = `
307
- ${includeBackButton ? `
308
- <button class="glide-btn glide-btn-back" id="glide-back-btn" aria-label="Back">
309
- ${this.iconBack}
310
- </button>
311
- ` : ''}
312
- ${((_c = this.options) === null || _c === void 0 ? void 0 : _c.showCloseButton) !== false ? `
313
- <button class="glide-btn glide-btn-close" id="glide-close-btn" aria-label="Close">
314
- <svg class="glide-icon" width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3" stroke-linecap="round" stroke-linejoin="round">
315
- <line x1="18" y1="6" x2="6" y2="18"></line>
316
- <line x1="6" y1="6" x2="18" y2="18"></line>
317
- </svg>
318
- </button>
319
- ` : ''}
320
- <h2 class="glide-title">${((_d = this.options) === null || _d === void 0 ? void 0 : _d.title) || 'Scan to Verify'}</h2>
321
- <div id="glide-modal-content">${content}</div>
322
- <p class="glide-status" id="glide-status">${((_e = this.options) === null || _e === void 0 ? void 0 : _e.description) || ''}</p>
323
- ${includeHelpButton ? `
324
- <button class="glide-btn glide-btn-help" id="glide-help-btn">?</button>
325
- ` : ''}
298
+ <div class="glide-auth-header">
299
+ ${((_b = this.options) === null || _b === void 0 ? void 0 : _b.showCloseButton) !== false ? `
300
+ <button class="glide-auth-close" aria-label="Close">
301
+ <svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
302
+ <line x1="18" y1="6" x2="6" y2="18"></line>
303
+ <line x1="6" y1="6" x2="18" y2="18"></line>
304
+ </svg>
305
+ </button>
306
+ ` : ''}
307
+ <h2 class="glide-auth-title">${((_c = this.options) === null || _c === void 0 ? void 0 : _c.title) || 'Phone Verification'}</h2>
308
+ </div>
309
+ <div class="glide-auth-content">
310
+ ${content}
311
+ </div>
326
312
  `;
313
+ // Add styles
327
314
  this.injectStyles();
328
- const closeBtn = this.container.querySelector('#glide-close-btn');
315
+ // Add close button handler
316
+ const closeBtn = this.container.querySelector('.glide-auth-close');
329
317
  if (closeBtn) {
330
318
  closeBtn.addEventListener('click', () => {
331
319
  var _a;
332
- (_a = this.closeCallback) === null || _a === void 0 ? void 0 : _a.call(this);
320
+ (_a = this.closeCallback) === null || _a === void 0 ? void 0 : _a.call(this); // Call cancellation callback if set
333
321
  this.close();
334
322
  });
335
323
  }
336
- if (this.backdrop) {
337
- this.backdrop.onclick = (e) => {
338
- var _a, _b;
339
- if (e.target === this.backdrop && ((_a = this.options) === null || _a === void 0 ? void 0 : _a.closeOnBackdrop) !== false) {
340
- (_b = this.closeCallback) === null || _b === void 0 ? void 0 : _b.call(this);
341
- this.close();
342
- }
343
- };
344
- }
345
- }
346
- setupHelpInteraction() {
347
- var _a;
348
- const trigger = (_a = this.container) === null || _a === void 0 ? void 0 : _a.querySelector('#glide-help-btn');
349
- if (trigger) {
350
- trigger.addEventListener('click', () => {
351
- var _a;
352
- const overlays = (_a = this.container) === null || _a === void 0 ? void 0 : _a.querySelectorAll('.glide-phone-animation-overlay');
353
- if (overlays) {
354
- overlays.forEach(overlay => {
355
- overlay.classList.remove('playing');
356
- void overlay.offsetWidth;
357
- overlay.classList.add('playing');
358
- setTimeout(() => {
359
- overlay.classList.remove('playing');
360
- }, 3000);
361
- });
362
- }
363
- });
364
- }
324
+ // Add backdrop click handler
325
+ this.backdrop.addEventListener('click', (e) => {
326
+ var _a, _b;
327
+ // Only close if backdrop itself was clicked and closeOnBackdrop is enabled (default true)
328
+ if (e.target === this.backdrop && ((_a = this.options) === null || _a === void 0 ? void 0 : _a.closeOnBackdrop) !== false) {
329
+ (_b = this.closeCallback) === null || _b === void 0 ? void 0 : _b.call(this); // Call cancellation callback if set
330
+ this.close();
331
+ }
332
+ });
365
333
  }
334
+ /**
335
+ * Injects CSS styles for the modal
336
+ */
366
337
  injectStyles() {
367
- if (document.getElementById('glide-modal-styles'))
338
+ if (document.getElementById('glide-auth-styles'))
368
339
  return;
369
340
  const styles = document.createElement('style');
370
- styles.id = 'glide-modal-styles';
341
+ styles.id = 'glide-auth-styles';
371
342
  styles.textContent = `
372
- :root {
373
- --glide-primary: #007AFF;
374
- --glide-text: #1d1d1f;
375
- --glide-bg-light: rgba(255, 255, 255, 0.6);
376
- --glide-bg-dark: rgba(30, 30, 30, 0.6);
377
- --glide-phone-border: #000000; /* High Contrast Black */
378
-
379
- /* Button sizes */
380
- --glide-btn-size: 28px;
381
- --glide-help-btn-size: 24px;
382
- --glide-toggle-icon-size: 14px;
383
- --glide-dual-icon-size: 18px;
384
- }
385
-
386
- #glide-backdrop {
387
- position: fixed;
388
- top: 0;
389
- left: 0;
390
- right: 0;
391
- bottom: 0;
392
- background: rgba(0, 0, 0, 0.2);
393
- backdrop-filter: blur(8px);
394
- -webkit-backdrop-filter: blur(8px);
395
- z-index: 9998;
396
- opacity: 0;
397
- transition: opacity 0.4s cubic-bezier(0.16, 1, 0.3, 1);
343
+ .glide-auth-backdrop {
344
+ backdrop-filter: blur(4px);
398
345
  }
399
346
 
400
- #glide-modal {
401
- position: fixed;
402
- top: 20px;
403
- left: 50%;
404
- transform: translateX(-50%) scale(0.94);
405
- background: var(--glide-bg-light);
406
- backdrop-filter: blur(24px) saturate(180%);
407
- -webkit-backdrop-filter: blur(24px) saturate(180%);
408
- border-radius: 24px;
409
- padding: 32px;
410
- width: 360px;
411
- max-width: 90%;
412
- box-shadow:
413
- 0 20px 40px rgba(0,0,0,0.2),
414
- 0 0 0 1px rgba(255,255,255,0.6) inset,
415
- 0 0 0 1px rgba(0,0,0,0.05);
416
- z-index: 9999;
417
- opacity: 0;
418
- transition: opacity 0.4s ease, transform 0.4s cubic-bezier(0.16, 1, 0.3, 1);
419
- font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
420
- text-align: center;
421
- color: var(--glide-text);
422
- }
423
-
424
- #glide-modal.glide-modal-wide {
425
- width: 600px;
426
- max-width: 95vw;
347
+ .glide-auth-modal {
348
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, sans-serif;
427
349
  }
428
350
 
429
- #glide-modal.dark {
430
- background: var(--glide-bg-dark);
431
- color: white;
432
- box-shadow:
433
- 0 20px 40px rgba(0,0,0,0.4),
434
- 0 0 0 1px rgba(255,255,255,0.15) inset,
435
- 0 0 0 1px rgba(0,0,0,0.5);
436
- --glide-phone-border: #ffffff; /* High Contrast White */
351
+ .glide-auth-header {
352
+ position: relative;
353
+ margin-bottom: 20px;
437
354
  }
438
355
 
439
- #glide-modal .glide-btn-close {
356
+ .glide-auth-close {
440
357
  position: absolute;
441
- top: 16px;
442
- right: 16px;
443
- width: var(--glide-btn-size);
444
- height: var(--glide-btn-size);
445
- min-width: var(--glide-btn-size);
446
- min-height: var(--glide-btn-size);
447
- max-width: var(--glide-btn-size);
448
- max-height: var(--glide-btn-size);
449
- background: rgba(118, 118, 128, 0.12);
358
+ top: 0;
359
+ right: 0;
360
+ background: none;
450
361
  border: none;
451
- border-radius: 50%;
452
- display: flex;
453
- align-items: center;
454
- justify-content: center;
455
362
  cursor: pointer;
456
- color: rgba(0,0,0,0.5);
457
- transition: background 0.2s;
458
363
  padding: 0;
459
- z-index: 20;
460
- box-sizing: border-box;
461
- flex-shrink: 0;
462
- }
463
-
464
- #glide-modal.dark .glide-btn-close {
465
- background: rgba(255, 255, 255, 0.1);
466
- color: rgba(255,255,255,0.5);
364
+ color: #666;
365
+ transition: color 0.2s;
467
366
  }
468
367
 
469
- #glide-modal .glide-btn-close:hover {
470
- background: rgba(118, 118, 128, 0.2);
471
- }
472
-
473
- #glide-modal.dark .glide-btn-close:hover {
474
- background: rgba(255, 255, 255, 0.2);
368
+ .glide-auth-close:hover {
369
+ color: #000;
475
370
  }
476
371
 
477
- .glide-title {
478
- margin: 0 0 8px 0;
479
- font-size: 22px;
372
+ .glide-auth-title {
373
+ margin: 0;
374
+ font-size: 24px;
480
375
  font-weight: 600;
481
- letter-spacing: -0.01em;
482
- }
483
-
484
- .glide-status {
485
- margin: 12px 0 0 0;
486
- font-size: 13px;
487
- color: rgba(0,0,0,0.5);
376
+ color: #333;
488
377
  text-align: center;
489
- min-height: 18px;
490
- }
491
-
492
- .glide-status:empty {
493
- display: none;
494
- }
495
-
496
- #glide-modal.dark .glide-status {
497
- color: rgba(255,255,255,0.5);
498
- }
499
-
500
- .glide-content {
501
- display: flex;
502
- flex-direction: column;
503
- align-items: center;
504
- position: relative;
505
- }
506
-
507
- /* --- Sliding Toggle Switch --- */
508
- #glide-toggle {
509
- background: rgba(118, 118, 128, 0.12);
510
- padding: 2px;
511
- border-radius: 8px;
512
- display: inline-flex;
513
- margin-bottom: 24px;
514
- margin-top: 16px;
515
- position: relative;
516
- height: 32px;
517
- width: 200px;
518
- box-sizing: border-box;
519
378
  }
520
379
 
521
- #glide-modal.dark #glide-toggle {
522
- background: rgba(118, 118, 128, 0.24);
523
- }
524
-
525
- /* The sliding background */
526
- .glide-toggle-slider {
527
- position: absolute;
528
- top: 2px;
529
- left: 2px;
530
- width: calc(50% - 2px);
531
- height: calc(100% - 4px);
532
- background: white;
533
- border-radius: 6px;
534
- box-shadow: 0 3px 8px rgba(0,0,0,0.12), 0 3px 1px rgba(0,0,0,0.04);
535
- transition: transform 0.3s cubic-bezier(0.25, 0.8, 0.25, 1);
536
- z-index: 0;
537
- }
538
-
539
- #glide-modal.dark .glide-toggle-slider {
540
- background: #636366;
380
+ .glide-auth-content {
381
+ text-align: center;
541
382
  }
542
-
543
- /* Move slider for Android */
544
- #glide-toggle[data-active="android"] .glide-toggle-slider {
545
- transform: translateX(100%);
383
+
384
+ .glide-auth-description {
385
+ color: #666;
386
+ margin: 0 0 20px 0;
387
+ font-size: 16px;
388
+ line-height: 1.5;
546
389
  }
547
390
 
548
- .glide-toggle-btn {
549
- flex: 1;
550
- background: none;
391
+ .glide-auth-button {
392
+ background: #007AFF;
393
+ color: white;
551
394
  border: none;
552
- padding: 0;
553
- margin: 0;
554
- font-size: 13px;
395
+ border-radius: 8px;
396
+ padding: 12px 24px;
397
+ font-size: 16px;
555
398
  font-weight: 500;
556
- color: inherit;
557
399
  cursor: pointer;
558
- position: relative;
559
- z-index: 1;
560
- transition: color 0.2s;
561
- display: flex;
562
- align-items: center;
563
- justify-content: center;
564
- gap: 6px;
565
- min-height: auto;
566
- height: auto;
567
- min-width: auto;
568
- border-radius: 0;
569
- }
570
-
571
- #glide-modal .glide-toggle-btn .glide-icon-os,
572
- #glide-modal .glide-toggle-btn svg {
573
- width: var(--glide-toggle-icon-size);
574
- height: var(--glide-toggle-icon-size);
575
- flex-shrink: 0;
576
- margin: 0;
577
- padding: 0;
578
- }
579
-
580
- .glide-toggle-btn span {
581
- margin: 0;
582
- padding: 0;
583
- line-height: 1;
584
- }
585
-
586
- #glide-modal.dark .glide-toggle-btn {
587
- color: rgba(255,255,255,0.6);
588
- }
589
-
590
- #glide-toggle[data-active="ios"] .glide-toggle-btn[data-platform="ios"],
591
- #glide-toggle[data-active="android"] .glide-toggle-btn[data-platform="android"] {
592
- color: #1d1d1f;
593
- }
594
-
595
- #glide-modal.dark #glide-toggle[data-active="ios"] .glide-toggle-btn[data-platform="ios"],
596
- #glide-modal.dark #glide-toggle[data-active="android"] .glide-toggle-btn[data-platform="android"] {
597
- color: white;
400
+ transition: background 0.2s, transform 0.1s;
401
+ min-width: 200px;
598
402
  }
599
403
 
600
- /* QR Area */
601
- .glide-qr-area {
602
- position: relative;
603
- margin: 0 auto;
404
+ .glide-auth-button:hover:not(:disabled) {
405
+ background: #0051D5;
406
+ transform: translateY(-1px);
604
407
  }
605
408
 
606
- .glide-qr-img {
607
- width: 200px;
608
- height: 200px;
609
- object-fit: contain;
610
- display: block;
611
- border-radius: 16px;
612
- }
613
-
614
- /* Dual Mode QR Area - no extra padding, same as single mode */
615
- .glide-dual-mode .glide-qr-area {
616
- background: transparent;
617
- padding: 0;
618
- }
619
-
620
- .glide-dual-mode .glide-qr-img {
621
- width: 180px;
622
- height: 180px;
623
- border-radius: 16px;
624
- }
625
-
626
- /* --- Help Icon & Interaction --- */
627
- .glide-btn-help {
628
- position: absolute;
629
- bottom: 20px;
630
- right: 20px;
631
- width: var(--glide-help-btn-size);
632
- height: var(--glide-help-btn-size);
633
- min-width: var(--glide-help-btn-size);
634
- min-height: var(--glide-help-btn-size);
635
- max-width: var(--glide-help-btn-size);
636
- max-height: var(--glide-help-btn-size);
637
- border-radius: 50%;
638
- border: 1.5px solid rgba(0,0,0,0.2);
639
- color: rgba(0,0,0,0.4);
640
- display: flex;
641
- align-items: center;
642
- justify-content: center;
643
- font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
644
- font-size: 14px;
645
- font-weight: 600;
646
- line-height: 1;
647
- cursor: pointer;
648
- transition: all 0.2s;
649
- background: transparent;
650
- padding: 0;
651
- margin: 0;
652
- box-sizing: border-box;
653
- z-index: 10;
654
- }
655
-
656
- #glide-modal.dark .glide-btn-help {
657
- border-color: rgba(255,255,255,0.3);
658
- color: rgba(255,255,255,0.5);
409
+ .glide-auth-button:active {
410
+ transform: translateY(0);
659
411
  }
660
412
 
661
- .glide-btn-help:hover {
662
- border-color: var(--glide-primary);
663
- color: var(--glide-primary);
664
- background: rgba(0, 122, 255, 0.1);
665
- }
666
-
667
- /* Tooltip */
668
- .glide-btn-help::after {
669
- content: "Need help?";
670
- position: absolute;
671
- bottom: 100%;
672
- left: 50%;
673
- transform: translateX(-50%) translateY(-8px);
674
- background: rgba(0,0,0,0.8);
675
- color: white;
676
- padding: 4px 8px;
677
- border-radius: 4px;
678
- font-size: 11px;
679
- white-space: nowrap;
680
- opacity: 0;
681
- pointer-events: none;
682
- transition: opacity 0.2s;
413
+ .glide-auth-button:disabled {
414
+ opacity: 0.6;
415
+ cursor: not-allowed;
683
416
  }
684
-
685
- .glide-btn-help:hover::after {
686
- opacity: 1;
417
+
418
+ .glide-auth-helper-text {
419
+ color: #999;
420
+ font-size: 14px;
421
+ margin: 16px 0 0 0;
687
422
  }
688
423
 
689
- /* --- Outlined Phone Animation --- */
690
- .glide-phone-animation-overlay {
691
- position: absolute;
692
- top: 0;
693
- left: 0;
424
+ .glide-auth-qr-code {
425
+ max-width: 256px;
694
426
  width: 100%;
695
- height: 100%;
696
- display: flex;
697
- align-items: center;
698
- justify-content: center;
699
- pointer-events: none;
700
- opacity: 0;
701
- transition: opacity 0.3s;
702
- border-radius: 16px;
703
- background: rgba(255,255,255,0.8);
704
- }
705
-
706
- #glide-modal.dark .glide-phone-animation-overlay {
707
- background: rgba(0,0,0,0.6);
708
- }
709
-
710
- .glide-phone-animation-overlay.playing {
711
- opacity: 1;
427
+ height: auto;
428
+ margin: 20px auto;
429
+ display: block;
712
430
  }
713
431
 
714
- .glide-outlined-phone {
715
- width: 100px;
716
- height: 180px;
717
- border: 4px solid var(--glide-phone-border);
718
- border-radius: 16px;
719
- position: relative;
720
- background: transparent;
721
- box-shadow: 0 10px 30px rgba(0,0,0,0.2);
722
- transform: translateY(20px);
723
- }
724
-
725
- /* Notch */
726
- .glide-outlined-phone::before {
727
- content: '';
728
- position: absolute;
729
- top: -1px;
730
- left: 50%;
731
- transform: translateX(-50%);
732
- width: 40%;
733
- height: 12px;
734
- background: var(--glide-phone-border);
735
- border-bottom-left-radius: 8px;
736
- border-bottom-right-radius: 8px;
737
- }
738
-
739
- /* Scanning Line */
740
- .glide-scan-line {
741
- position: absolute;
742
- top: 10%;
743
- left: 5%;
744
- width: 90%;
745
- height: 2px;
746
- background: var(--glide-primary);
747
- box-shadow: 0 0 8px var(--glide-primary);
748
- opacity: 0;
749
- }
750
-
751
- /* Animation Keyframes */
752
- @keyframes glide-scan-motion {
753
- 0% { top: 10%; opacity: 0; }
754
- 10% { opacity: 1; }
755
- 90% { opacity: 1; }
756
- 100% { top: 90%; opacity: 0; }
757
- }
758
-
759
- .glide-phone-animation-overlay.playing .glide-outlined-phone {
760
- animation: glide-phone-appear 3s ease-in-out forwards;
761
- }
762
-
763
- .glide-phone-animation-overlay.playing .glide-scan-line {
764
- animation: glide-scan-motion 2s ease-in-out 0.5s infinite;
765
- }
766
-
767
- @keyframes glide-phone-appear {
768
- 0% { transform: translateY(20px); opacity: 0; }
769
- 10% { transform: translateY(0); opacity: 1; }
770
- 90% { transform: translateY(0); opacity: 1; }
771
- 100% { transform: translateY(20px); opacity: 0; }
772
- }
773
-
774
- /* Dual Mode */
775
- .glide-dual-container {
776
- display: flex;
777
- justify-content: center;
778
- align-items: stretch;
779
- gap: 32px;
780
- margin-top: 20px;
781
- }
782
-
783
- .glide-dual-item {
784
- display: flex;
785
- flex-direction: column;
786
- align-items: center;
787
- }
788
-
789
- .glide-os-logo {
790
- display: flex;
791
- align-items: center;
792
- gap: 8px;
793
- font-weight: 600;
794
- margin-bottom: 12px;
795
- font-size: 15px;
796
- color: inherit;
797
- }
798
-
799
- /* Dual mode OS logo - aligned icons with text, spacing to QR */
800
- .glide-dual-mode .glide-os-logo {
801
- margin-bottom: 12px; /* Space between label and QR code */
802
- }
803
-
804
- #glide-modal .glide-dual-mode .glide-os-logo .glide-icon-os,
805
- #glide-modal .glide-dual-mode .glide-os-logo svg {
806
- width: var(--glide-dual-icon-size);
807
- height: var(--glide-dual-icon-size);
808
- margin: 0;
809
- padding: 0;
810
- flex-shrink: 0;
811
- }
812
-
813
- .glide-dual-mode .glide-os-logo span {
814
- margin: 0;
815
- padding: 0;
816
- line-height: 1;
432
+ .glide-auth-status {
433
+ color: #666;
434
+ font-size: 16px;
435
+ margin: 16px 0;
817
436
  }
818
-
819
- .glide-os-logo svg {
820
- width: 20px;
821
- height: 20px;
822
- opacity: 0.8;
437
+
438
+ .glide-auth-error {
439
+ color: #FF3B30;
823
440
  }
824
-
825
- /* Dual Mode Separator - aligned to QR codes only */
826
- .glide-dual-separator {
827
- display: flex;
828
- flex-direction: column;
829
- align-items: center;
830
- justify-content: center;
831
- align-self: stretch;
832
- /* Offset for OS label (18px icon + 12px margin = ~30px) */
833
- margin-top: 30px;
441
+
442
+ .glide-auth-qr-container,
443
+ .glide-auth-link-container,
444
+ .glide-auth-ts43-container {
834
445
  padding: 20px 0;
835
446
  }
836
-
837
- .glide-separator-line {
838
- width: 1px;
839
- flex: 1;
840
- background: linear-gradient(to bottom, transparent, rgba(128,128,128,0.3), transparent);
841
- }
842
-
843
- .glide-separator-text {
844
- padding: 8px 0;
845
- font-size: 11px;
846
- font-weight: 500;
847
- color: rgba(128,128,128,0.5);
848
- text-transform: uppercase;
849
- letter-spacing: 0.5px;
850
- }
851
-
852
- #glide-modal.dark .glide-separator-line {
853
- background: linear-gradient(to bottom, transparent, rgba(255,255,255,0.2), transparent);
854
- }
855
-
856
- #glide-modal.dark .glide-separator-text {
857
- color: rgba(255,255,255,0.4);
858
- }
859
-
860
- /* Pre-Step */
861
- .glide-pre-step-container {
447
+
448
+ /* Platform Switcher Styles */
449
+ .glide-platform-switcher {
862
450
  display: flex;
863
- gap: 20px;
864
451
  justify-content: center;
865
- margin: 24px 0;
452
+ gap: 10px;
453
+ margin-bottom: 20px;
866
454
  }
867
-
868
- .glide-os-choice-btn {
455
+
456
+ .glide-platform-btn {
869
457
  display: flex;
870
- flex-direction: column;
871
458
  align-items: center;
872
- justify-content: center;
873
- width: 140px;
874
- height: 140px;
875
- border: 1px solid rgba(0,0,0,0.08);
876
- border-radius: 20px;
877
- background: rgba(255,255,255,0.4);
459
+ gap: 6px;
460
+ padding: 10px 20px;
461
+ border: 2px solid;
462
+ background: transparent;
463
+ border-radius: 8px;
878
464
  cursor: pointer;
879
- transition: all 0.3s cubic-bezier(0.25, 0.8, 0.25, 1);
880
- position: relative;
881
- overflow: hidden;
882
465
  font-size: 14px;
883
- color: inherit;
884
- }
885
-
886
- #glide-modal.dark .glide-os-choice-btn {
887
- background: rgba(255,255,255,0.05);
888
- border-color: rgba(255,255,255,0.1);
889
- color: white;
890
- }
891
-
892
- .glide-os-choice-btn:hover {
893
- background: rgba(255,255,255,0.9);
894
- transform: translateY(-4px);
895
- box-shadow: 0 12px 24px rgba(0,0,0,0.1), 0 4px 8px rgba(0,0,0,0.04);
896
- border-color: var(--glide-primary);
897
- }
898
-
899
- #glide-modal.dark .glide-os-choice-btn:hover {
900
- background: rgba(255,255,255,0.15);
901
- box-shadow: 0 12px 24px rgba(0,0,0,0.3);
902
- border-color: var(--glide-primary);
903
- }
904
-
905
- .glide-icon-os {
906
- width: 40px;
907
- height: 40px;
908
- margin-bottom: 12px;
909
- transition: transform 0.3s;
910
- fill: currentColor;
911
- }
912
-
913
- .glide-os-choice-btn:hover .glide-icon-os {
914
- transform: scale(1.1);
466
+ font-weight: 600;
467
+ transition: all 0.2s ease;
915
468
  }
916
-
917
- #glide-modal .glide-btn-back {
918
- position: absolute;
919
- top: 16px;
920
- left: 16px;
921
- width: var(--glide-btn-size);
922
- height: var(--glide-btn-size);
923
- min-width: var(--glide-btn-size);
924
- min-height: var(--glide-btn-size);
925
- max-width: var(--glide-btn-size);
926
- max-height: var(--glide-btn-size);
927
- background: rgba(118, 118, 128, 0.12);
928
- border: none;
929
- border-radius: 50%;
930
- display: flex;
931
- align-items: center;
932
- justify-content: center;
933
- cursor: pointer;
934
- color: rgba(0,0,0,0.5);
935
- transition: background 0.2s;
936
- z-index: 20;
937
- padding: 0;
938
- margin: 0;
939
- box-sizing: border-box;
469
+
470
+ .glide-platform-btn svg {
940
471
  flex-shrink: 0;
941
472
  }
942
-
943
- #glide-modal.dark .glide-btn-back {
944
- background: rgba(255, 255, 255, 0.1);
945
- color: rgba(255,255,255,0.5);
473
+
474
+ .glide-platform-ios {
475
+ border-color: #007AFF;
476
+ color: #007AFF;
946
477
  }
947
478
 
948
- .glide-btn-back:hover {
949
- background: rgba(118, 118, 128, 0.2);
479
+ .glide-platform-ios.active {
480
+ background: #007AFF;
481
+ color: white;
950
482
  }
951
-
952
- #glide-modal.dark .glide-btn-back:hover {
953
- background: rgba(255, 255, 255, 0.2);
483
+
484
+ .glide-platform-ios:hover:not(.active) {
485
+ background: rgba(0, 122, 255, 0.1);
954
486
  }
955
-
956
- .glide-spinner {
957
- width: 40px;
958
- height: 40px;
959
- border: 3px solid rgba(0,0,0,0.1);
960
- border-top-color: var(--glide-primary);
961
- border-radius: 50%;
962
- animation: glide-spin 1s linear infinite;
963
- margin: 40px auto;
487
+
488
+ .glide-platform-android {
489
+ border-color: #3DDC84;
490
+ color: #3DDC84;
964
491
  }
965
-
966
- #glide-modal.dark .glide-spinner {
967
- border-color: rgba(255,255,255,0.1);
968
- border-top-color: var(--glide-primary);
492
+
493
+ .glide-platform-android.active {
494
+ background: #3DDC84;
495
+ color: white;
969
496
  }
970
-
971
- @keyframes glide-spin {
972
- to { transform: rotate(360deg); }
497
+
498
+ .glide-platform-android:hover:not(.active) {
499
+ background: rgba(61, 220, 132, 0.1);
973
500
  }
974
501
  `;
975
502
  document.head.appendChild(styles);
976
503
  }
504
+ /**
505
+ * Shows the modal with animation
506
+ */
977
507
  show() {
978
508
  var _a, _b;
979
- if (!this.container || !this.backdrop || this.isOpen) {
980
- if (this.isOpen && this.container && this.backdrop) {
981
- document.removeEventListener('keydown', this.handleEscapeKey);
982
- document.addEventListener('keydown', this.handleEscapeKey);
983
- }
509
+ if (!this.container || !this.backdrop || this.isOpen)
984
510
  return;
985
- }
986
511
  document.body.appendChild(this.backdrop);
987
512
  document.body.appendChild(this.container);
988
- this.lockBodyScroll();
513
+ // Add escape key listener for closing modal
989
514
  document.addEventListener('keydown', this.handleEscapeKey);
515
+ // Setup platform toggle handlers if they exist
516
+ this.setupPlatformToggles();
517
+ // Trigger animation
990
518
  requestAnimationFrame(() => {
991
519
  if (this.backdrop && this.container) {
992
520
  this.backdrop.style.opacity = '1';
993
521
  this.container.style.opacity = '1';
994
- this.container.style.transform = 'translateX(-50%) scale(1)';
522
+ this.container.style.transform = 'translate(-50%, -50%) scale(1)';
995
523
  }
996
524
  });
997
525
  this.isOpen = true;
998
526
  (_b = (_a = this.callbacks) === null || _a === void 0 ? void 0 : _a.onOpen) === null || _b === void 0 ? void 0 : _b.call(_a);
999
527
  }
528
+ /**
529
+ * Setup click handlers for iOS/Android platform toggle
530
+ */
1000
531
  setupPlatformToggles() {
1001
- var _a, _b, _c, _d;
1002
- const container = (_a = this.container) === null || _a === void 0 ? void 0 : _a.querySelector('#glide-toggle');
1003
- const platformBtns = (_b = this.container) === null || _b === void 0 ? void 0 : _b.querySelectorAll('.glide-toggle-btn');
1004
- const qrImg = (_c = this.container) === null || _c === void 0 ? void 0 : _c.querySelector('#glide-qr-img');
1005
- const message = (_d = this.container) === null || _d === void 0 ? void 0 : _d.querySelector('#glide-platform-message');
532
+ var _a, _b, _c;
533
+ const platformBtns = (_a = this.container) === null || _a === void 0 ? void 0 : _a.querySelectorAll('.glide-platform-btn');
534
+ const qrImg = (_b = this.container) === null || _b === void 0 ? void 0 : _b.querySelector('#glide-qr-code-img');
535
+ const message = (_c = this.container) === null || _c === void 0 ? void 0 : _c.querySelector('#glide-platform-message');
1006
536
  if (!platformBtns || !qrImg)
1007
537
  return;
1008
538
  platformBtns.forEach((btn) => {
1009
539
  btn.addEventListener('click', (e) => {
1010
540
  const target = e.currentTarget;
1011
541
  const platform = target.getAttribute('data-platform');
1012
- if (container && platform) {
1013
- container.setAttribute('data-active', platform);
1014
- }
542
+ // Update active state
1015
543
  platformBtns.forEach(b => b.classList.remove('active'));
1016
544
  target.classList.add('active');
545
+ // Switch QR code
1017
546
  if (platform === 'ios') {
1018
547
  qrImg.src = qrImg.getAttribute('data-ios') || '';
1019
548
  if (message)
1020
- message.textContent = 'Scan with your iPhone camera';
549
+ message.textContent = 'Scan with your iPhone to authenticate';
1021
550
  }
1022
551
  else if (platform === 'android') {
1023
552
  qrImg.src = qrImg.getAttribute('data-android') || '';
1024
553
  if (message)
1025
- message.textContent = 'Scan with your Android camera';
554
+ message.textContent = 'Scan with your Android device to authenticate';
1026
555
  }
1027
556
  });
1028
557
  });
1029
558
  }
1030
- lockBodyScroll() {
1031
- this.originalBodyOverflow = document.body.style.overflow;
1032
- document.body.style.overflow = 'hidden';
1033
- }
1034
- unlockBodyScroll() {
1035
- document.body.style.overflow = this.originalBodyOverflow;
1036
- }
559
+ /**
560
+ * Closes the modal with animation
561
+ */
1037
562
  close() {
1038
563
  if (!this.container || !this.backdrop || !this.isOpen)
1039
564
  return;
1040
- this.isClosing = true;
565
+ // Remove escape key listener
1041
566
  document.removeEventListener('keydown', this.handleEscapeKey);
567
+ // Animate out
1042
568
  this.backdrop.style.opacity = '0';
1043
569
  this.container.style.opacity = '0';
1044
- this.container.style.transform = 'translateX(-50%) scale(0.94)';
570
+ this.container.style.transform = 'translate(-50%, -50%) scale(0.9)';
571
+ // Clear any stored callbacks
1045
572
  this.closeCallback = undefined;
573
+ // Remove after animation
1046
574
  setTimeout(() => {
1047
575
  var _a, _b;
1048
- if (this.isClosing) {
1049
- this.cleanup();
1050
- this.isOpen = false;
1051
- this.isClosing = false;
1052
- (_b = (_a = this.callbacks) === null || _a === void 0 ? void 0 : _a.onClose) === null || _b === void 0 ? void 0 : _b.call(_a);
1053
- }
1054
- }, 400);
576
+ this.cleanup();
577
+ this.isOpen = false;
578
+ (_b = (_a = this.callbacks) === null || _a === void 0 ? void 0 : _a.onClose) === null || _b === void 0 ? void 0 : _b.call(_a);
579
+ }, 300);
1055
580
  }
581
+ /**
582
+ * Removes modal elements from DOM
583
+ */
1056
584
  cleanup() {
1057
585
  var _a, _b;
1058
586
  (_a = this.container) === null || _a === void 0 ? void 0 : _a.remove();
1059
587
  (_b = this.backdrop) === null || _b === void 0 ? void 0 : _b.remove();
1060
588
  this.container = null;
1061
589
  this.backdrop = null;
1062
- this.unlockBodyScroll();
590
+ }
591
+ /**
592
+ * Check if modal is currently open
593
+ */
594
+ isModalOpen() {
595
+ return this.isOpen;
1063
596
  }
1064
597
  }
1065
598
  exports.AuthModal = AuthModal;