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