@followgate/js 0.5.0 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.mts +39 -58
- package/dist/index.d.ts +39 -58
- package/dist/index.js +753 -63
- package/dist/index.mjs +753 -63
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -26,6 +26,7 @@ __export(index_exports, {
|
|
|
26
26
|
});
|
|
27
27
|
module.exports = __toCommonJS(index_exports);
|
|
28
28
|
var DEFAULT_API_URL = "https://api.followgate.app";
|
|
29
|
+
var WAIT_TIME_SECONDS = 3;
|
|
29
30
|
var FollowGateError = class extends Error {
|
|
30
31
|
constructor(message, code, hint) {
|
|
31
32
|
super(message);
|
|
@@ -37,70 +38,808 @@ var FollowGateError = class extends Error {
|
|
|
37
38
|
function isValidApiKeyFormat(apiKey) {
|
|
38
39
|
return /^fg_(live|test)_[a-zA-Z0-9_-]+$/.test(apiKey);
|
|
39
40
|
}
|
|
41
|
+
var MODAL_STYLES = `
|
|
42
|
+
.fg-modal-backdrop {
|
|
43
|
+
position: fixed;
|
|
44
|
+
inset: 0;
|
|
45
|
+
z-index: 99999;
|
|
46
|
+
display: flex;
|
|
47
|
+
align-items: center;
|
|
48
|
+
justify-content: center;
|
|
49
|
+
background: rgba(0, 0, 0, 0.8);
|
|
50
|
+
backdrop-filter: blur(4px);
|
|
51
|
+
opacity: 0;
|
|
52
|
+
transition: opacity 0.3s ease;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
.fg-modal-backdrop.fg-visible {
|
|
56
|
+
opacity: 1;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
.fg-modal {
|
|
60
|
+
position: relative;
|
|
61
|
+
width: 100%;
|
|
62
|
+
max-width: 420px;
|
|
63
|
+
margin: 16px;
|
|
64
|
+
background: #0f172a;
|
|
65
|
+
border-radius: 16px;
|
|
66
|
+
border: 1px solid #334155;
|
|
67
|
+
box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.5);
|
|
68
|
+
padding: 32px;
|
|
69
|
+
transform: scale(0.95);
|
|
70
|
+
transition: transform 0.3s ease;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
.fg-modal-backdrop.fg-visible .fg-modal {
|
|
74
|
+
transform: scale(1);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
.fg-icon-box {
|
|
78
|
+
width: 64px;
|
|
79
|
+
height: 64px;
|
|
80
|
+
margin: 0 auto 24px;
|
|
81
|
+
background: #1e293b;
|
|
82
|
+
border-radius: 16px;
|
|
83
|
+
display: flex;
|
|
84
|
+
align-items: center;
|
|
85
|
+
justify-content: center;
|
|
86
|
+
border: 1px solid #334155;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
.fg-icon-box svg {
|
|
90
|
+
width: 32px;
|
|
91
|
+
height: 32px;
|
|
92
|
+
fill: currentColor;
|
|
93
|
+
color: white;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
.fg-icon-box.fg-success {
|
|
97
|
+
background: rgba(34, 197, 94, 0.1);
|
|
98
|
+
border-color: rgba(34, 197, 94, 0.3);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
.fg-icon-box.fg-success svg {
|
|
102
|
+
color: #22c55e;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
.fg-title {
|
|
106
|
+
font-size: 24px;
|
|
107
|
+
font-weight: 700;
|
|
108
|
+
color: white;
|
|
109
|
+
text-align: center;
|
|
110
|
+
margin: 0 0 8px;
|
|
111
|
+
font-family: system-ui, -apple-system, sans-serif;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
.fg-subtitle {
|
|
115
|
+
font-size: 14px;
|
|
116
|
+
color: #94a3b8;
|
|
117
|
+
text-align: center;
|
|
118
|
+
margin: 0 0 24px;
|
|
119
|
+
font-family: system-ui, -apple-system, sans-serif;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
.fg-user-badge {
|
|
123
|
+
display: flex;
|
|
124
|
+
align-items: center;
|
|
125
|
+
justify-content: center;
|
|
126
|
+
gap: 8px;
|
|
127
|
+
padding: 6px 12px;
|
|
128
|
+
background: #1e293b;
|
|
129
|
+
border-radius: 9999px;
|
|
130
|
+
width: fit-content;
|
|
131
|
+
margin: 0 auto 16px;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
.fg-user-badge-dot {
|
|
135
|
+
width: 8px;
|
|
136
|
+
height: 8px;
|
|
137
|
+
background: #22c55e;
|
|
138
|
+
border-radius: 50%;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
.fg-user-badge-text {
|
|
142
|
+
font-size: 14px;
|
|
143
|
+
color: #cbd5e1;
|
|
144
|
+
font-family: system-ui, -apple-system, sans-serif;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
.fg-input-wrapper {
|
|
148
|
+
position: relative;
|
|
149
|
+
margin-bottom: 16px;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
.fg-input-prefix {
|
|
153
|
+
position: absolute;
|
|
154
|
+
left: 16px;
|
|
155
|
+
top: 50%;
|
|
156
|
+
transform: translateY(-50%);
|
|
157
|
+
color: #64748b;
|
|
158
|
+
font-family: system-ui, -apple-system, sans-serif;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
.fg-input {
|
|
162
|
+
width: 100%;
|
|
163
|
+
padding: 16px 16px 16px 36px;
|
|
164
|
+
background: #1e293b;
|
|
165
|
+
border: 1px solid #334155;
|
|
166
|
+
border-radius: 12px;
|
|
167
|
+
color: white;
|
|
168
|
+
font-size: 16px;
|
|
169
|
+
outline: none;
|
|
170
|
+
transition: border-color 0.2s, box-shadow 0.2s;
|
|
171
|
+
font-family: system-ui, -apple-system, sans-serif;
|
|
172
|
+
box-sizing: border-box;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
.fg-input:focus {
|
|
176
|
+
border-color: var(--fg-accent, #6366f1);
|
|
177
|
+
box-shadow: 0 0 0 3px rgba(99, 102, 241, 0.1);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
.fg-input::placeholder {
|
|
181
|
+
color: #475569;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
.fg-btn {
|
|
185
|
+
width: 100%;
|
|
186
|
+
padding: 16px 24px;
|
|
187
|
+
border-radius: 12px;
|
|
188
|
+
font-size: 16px;
|
|
189
|
+
font-weight: 600;
|
|
190
|
+
cursor: pointer;
|
|
191
|
+
transition: all 0.2s;
|
|
192
|
+
display: flex;
|
|
193
|
+
align-items: center;
|
|
194
|
+
justify-content: center;
|
|
195
|
+
gap: 12px;
|
|
196
|
+
border: none;
|
|
197
|
+
font-family: system-ui, -apple-system, sans-serif;
|
|
198
|
+
box-sizing: border-box;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
.fg-btn:disabled {
|
|
202
|
+
opacity: 0.5;
|
|
203
|
+
cursor: not-allowed;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
.fg-btn-primary {
|
|
207
|
+
background: var(--fg-accent, #6366f1);
|
|
208
|
+
color: white;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
.fg-btn-primary:hover:not(:disabled) {
|
|
212
|
+
background: var(--fg-accent-hover, #4f46e5);
|
|
213
|
+
transform: scale(1.02);
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
.fg-btn-primary:active:not(:disabled) {
|
|
217
|
+
transform: scale(0.98);
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
.fg-btn-dark {
|
|
221
|
+
background: #000;
|
|
222
|
+
color: white;
|
|
223
|
+
border: 1px solid #334155;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
.fg-btn-dark:hover:not(:disabled) {
|
|
227
|
+
background: #111;
|
|
228
|
+
transform: scale(1.02);
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
.fg-btn-green {
|
|
232
|
+
background: #16a34a;
|
|
233
|
+
color: white;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
.fg-btn-green:hover:not(:disabled) {
|
|
237
|
+
background: #15803d;
|
|
238
|
+
transform: scale(1.02);
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
.fg-btn-secondary {
|
|
242
|
+
background: transparent;
|
|
243
|
+
color: #94a3b8;
|
|
244
|
+
border: 1px solid #334155;
|
|
245
|
+
margin-top: 12px;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
.fg-btn-secondary:hover:not(:disabled) {
|
|
249
|
+
background: #1e293b;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
.fg-btn svg {
|
|
253
|
+
width: 20px;
|
|
254
|
+
height: 20px;
|
|
255
|
+
flex-shrink: 0;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
.fg-spinner {
|
|
259
|
+
width: 20px;
|
|
260
|
+
height: 20px;
|
|
261
|
+
border: 2px solid transparent;
|
|
262
|
+
border-top-color: currentColor;
|
|
263
|
+
border-radius: 50%;
|
|
264
|
+
animation: fg-spin 0.8s linear infinite;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
@keyframes fg-spin {
|
|
268
|
+
to { transform: rotate(360deg); }
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
.fg-hint {
|
|
272
|
+
font-size: 12px;
|
|
273
|
+
color: #475569;
|
|
274
|
+
text-align: center;
|
|
275
|
+
margin-top: 16px;
|
|
276
|
+
font-family: system-ui, -apple-system, sans-serif;
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
.fg-info-box {
|
|
280
|
+
background: rgba(30, 41, 59, 0.5);
|
|
281
|
+
padding: 8px 12px;
|
|
282
|
+
border-radius: 8px;
|
|
283
|
+
margin-bottom: 12px;
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
.fg-info-box p {
|
|
287
|
+
font-size: 12px;
|
|
288
|
+
color: #64748b;
|
|
289
|
+
text-align: center;
|
|
290
|
+
margin: 0;
|
|
291
|
+
font-family: system-ui, -apple-system, sans-serif;
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
.fg-warning-box {
|
|
295
|
+
background: rgba(245, 158, 11, 0.1);
|
|
296
|
+
border: 1px solid rgba(245, 158, 11, 0.3);
|
|
297
|
+
border-radius: 12px;
|
|
298
|
+
padding: 16px;
|
|
299
|
+
margin-bottom: 24px;
|
|
300
|
+
display: flex;
|
|
301
|
+
align-items: flex-start;
|
|
302
|
+
gap: 12px;
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
.fg-warning-box svg {
|
|
306
|
+
width: 20px;
|
|
307
|
+
height: 20px;
|
|
308
|
+
color: #fbbf24;
|
|
309
|
+
flex-shrink: 0;
|
|
310
|
+
margin-top: 2px;
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
.fg-warning-box p {
|
|
314
|
+
font-size: 14px;
|
|
315
|
+
color: #fde68a;
|
|
316
|
+
margin: 0;
|
|
317
|
+
font-family: system-ui, -apple-system, sans-serif;
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
.fg-step-indicator {
|
|
321
|
+
display: flex;
|
|
322
|
+
align-items: center;
|
|
323
|
+
justify-content: center;
|
|
324
|
+
gap: 12px;
|
|
325
|
+
margin-bottom: 24px;
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
.fg-step-dot {
|
|
329
|
+
width: 32px;
|
|
330
|
+
height: 32px;
|
|
331
|
+
border-radius: 50%;
|
|
332
|
+
display: flex;
|
|
333
|
+
align-items: center;
|
|
334
|
+
justify-content: center;
|
|
335
|
+
font-size: 14px;
|
|
336
|
+
font-weight: 700;
|
|
337
|
+
transition: all 0.3s;
|
|
338
|
+
font-family: system-ui, -apple-system, sans-serif;
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
.fg-step-dot.fg-pending {
|
|
342
|
+
background: #334155;
|
|
343
|
+
color: #64748b;
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
.fg-step-dot.fg-active {
|
|
347
|
+
background: var(--fg-accent, #6366f1);
|
|
348
|
+
color: white;
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
.fg-step-dot.fg-done {
|
|
352
|
+
background: #22c55e;
|
|
353
|
+
color: white;
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
.fg-step-line {
|
|
357
|
+
width: 24px;
|
|
358
|
+
height: 2px;
|
|
359
|
+
background: #334155;
|
|
360
|
+
transition: background 0.3s;
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
.fg-step-line.fg-done {
|
|
364
|
+
background: #22c55e;
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
.fg-footer {
|
|
368
|
+
margin-top: 24px;
|
|
369
|
+
padding-top: 16px;
|
|
370
|
+
border-top: 1px solid #1e293b;
|
|
371
|
+
text-align: center;
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
.fg-footer p {
|
|
375
|
+
font-size: 12px;
|
|
376
|
+
color: #475569;
|
|
377
|
+
margin: 0;
|
|
378
|
+
font-family: system-ui, -apple-system, sans-serif;
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
.fg-footer a {
|
|
382
|
+
color: #64748b;
|
|
383
|
+
text-decoration: none;
|
|
384
|
+
transition: color 0.2s;
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
.fg-footer a:hover {
|
|
388
|
+
color: #94a3b8;
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
.fg-btn-row {
|
|
392
|
+
display: flex;
|
|
393
|
+
gap: 8px;
|
|
394
|
+
margin-top: 12px;
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
.fg-btn-row .fg-btn {
|
|
398
|
+
flex: 1;
|
|
399
|
+
padding: 12px 16px;
|
|
400
|
+
font-size: 14px;
|
|
401
|
+
}
|
|
402
|
+
`;
|
|
403
|
+
var ICONS = {
|
|
404
|
+
x: '<svg viewBox="0 0 24 24"><path d="M18.244 2.25h3.308l-7.227 8.26 8.502 11.24H16.17l-5.214-6.817L4.99 21.75H1.68l7.73-8.835L1.254 2.25H8.08l4.713 6.231zm-1.161 17.52h1.833L7.084 4.126H5.117z"/></svg>',
|
|
405
|
+
check: '<svg fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7"/></svg>',
|
|
406
|
+
repost: '<svg fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15"/></svg>',
|
|
407
|
+
warning: '<svg fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z"/></svg>'
|
|
408
|
+
};
|
|
40
409
|
var FollowGateClient = class {
|
|
41
410
|
config = null;
|
|
42
411
|
listeners = /* @__PURE__ */ new Map();
|
|
43
412
|
currentUser = null;
|
|
44
413
|
completedActions = [];
|
|
414
|
+
modalElement = null;
|
|
415
|
+
stylesInjected = false;
|
|
45
416
|
/**
|
|
46
417
|
* Initialize the SDK
|
|
47
|
-
* @throws {FollowGateError} If configuration is invalid
|
|
48
418
|
*/
|
|
49
419
|
init(config) {
|
|
50
420
|
if (!config.appId || typeof config.appId !== "string") {
|
|
51
421
|
throw new FollowGateError(
|
|
52
422
|
"[FollowGate] Missing or invalid appId",
|
|
53
423
|
"INVALID_APP_ID",
|
|
54
|
-
"Get your App ID from https://followgate.app/dashboard
|
|
424
|
+
"Get your App ID from https://followgate.app/dashboard"
|
|
55
425
|
);
|
|
56
426
|
}
|
|
57
427
|
if (config.appId.trim() === "" || config.appId === "undefined") {
|
|
58
428
|
throw new FollowGateError(
|
|
59
429
|
"[FollowGate] appId is empty or undefined",
|
|
60
430
|
"EMPTY_APP_ID",
|
|
61
|
-
"
|
|
431
|
+
"Check that NEXT_PUBLIC_FOLLOWGATE_APP_ID is set"
|
|
62
432
|
);
|
|
63
433
|
}
|
|
64
434
|
if (!config.apiKey || typeof config.apiKey !== "string") {
|
|
65
435
|
throw new FollowGateError(
|
|
66
436
|
"[FollowGate] Missing or invalid apiKey",
|
|
67
437
|
"INVALID_API_KEY",
|
|
68
|
-
"Get your API Key from https://followgate.app/dashboard
|
|
438
|
+
"Get your API Key from https://followgate.app/dashboard"
|
|
69
439
|
);
|
|
70
440
|
}
|
|
71
441
|
if (config.apiKey.trim() === "" || config.apiKey === "undefined") {
|
|
72
442
|
throw new FollowGateError(
|
|
73
443
|
"[FollowGate] apiKey is empty or undefined",
|
|
74
444
|
"EMPTY_API_KEY",
|
|
75
|
-
"
|
|
445
|
+
"Set NEXT_PUBLIC_FOLLOWGATE_API_KEY and rebuild"
|
|
76
446
|
);
|
|
77
447
|
}
|
|
78
448
|
if (!isValidApiKeyFormat(config.apiKey)) {
|
|
79
449
|
throw new FollowGateError(
|
|
80
|
-
`[FollowGate] Invalid API key format
|
|
450
|
+
`[FollowGate] Invalid API key format`,
|
|
81
451
|
"INVALID_API_KEY_FORMAT",
|
|
82
|
-
'API keys should start with "fg_live_"
|
|
452
|
+
'API keys should start with "fg_live_" or "fg_test_"'
|
|
83
453
|
);
|
|
84
454
|
}
|
|
85
455
|
this.config = {
|
|
86
456
|
...config,
|
|
87
|
-
apiUrl: config.apiUrl || DEFAULT_API_URL
|
|
457
|
+
apiUrl: config.apiUrl || DEFAULT_API_URL,
|
|
458
|
+
theme: config.theme || "dark",
|
|
459
|
+
accentColor: config.accentColor || "#6366f1"
|
|
88
460
|
};
|
|
89
461
|
this.restoreSession();
|
|
90
462
|
if (config.debug) {
|
|
91
463
|
console.log("[FollowGate] Initialized with appId:", config.appId);
|
|
92
|
-
console.log(
|
|
93
|
-
"[FollowGate] API Key:",
|
|
94
|
-
config.apiKey.substring(0, 12) + "..."
|
|
95
|
-
);
|
|
96
464
|
if (this.currentUser) {
|
|
97
465
|
console.log("[FollowGate] Restored user:", this.currentUser.username);
|
|
98
466
|
}
|
|
99
467
|
}
|
|
100
468
|
}
|
|
469
|
+
// ============================================
|
|
470
|
+
// Modal UI Methods
|
|
471
|
+
// ============================================
|
|
472
|
+
/**
|
|
473
|
+
* Show the FollowGate modal
|
|
474
|
+
* If user is already unlocked, calls onComplete immediately
|
|
475
|
+
*/
|
|
476
|
+
show() {
|
|
477
|
+
if (!this.config) {
|
|
478
|
+
throw new Error("[FollowGate] SDK not initialized. Call init() first.");
|
|
479
|
+
}
|
|
480
|
+
if (this.isUnlocked()) {
|
|
481
|
+
if (this.config.debug) {
|
|
482
|
+
console.log("[FollowGate] Already unlocked, skipping modal");
|
|
483
|
+
}
|
|
484
|
+
this.config.onComplete?.();
|
|
485
|
+
return;
|
|
486
|
+
}
|
|
487
|
+
this.injectStyles();
|
|
488
|
+
this.createModal();
|
|
489
|
+
}
|
|
490
|
+
/**
|
|
491
|
+
* Hide the modal
|
|
492
|
+
*/
|
|
493
|
+
hide() {
|
|
494
|
+
if (this.modalElement) {
|
|
495
|
+
this.modalElement.classList.remove("fg-visible");
|
|
496
|
+
setTimeout(() => {
|
|
497
|
+
this.modalElement?.remove();
|
|
498
|
+
this.modalElement = null;
|
|
499
|
+
}, 300);
|
|
500
|
+
}
|
|
501
|
+
}
|
|
502
|
+
injectStyles() {
|
|
503
|
+
if (this.stylesInjected) return;
|
|
504
|
+
if (typeof document === "undefined") return;
|
|
505
|
+
const style = document.createElement("style");
|
|
506
|
+
style.id = "followgate-styles";
|
|
507
|
+
style.textContent = MODAL_STYLES;
|
|
508
|
+
document.head.appendChild(style);
|
|
509
|
+
if (this.config?.accentColor) {
|
|
510
|
+
document.documentElement.style.setProperty("--fg-accent", this.config.accentColor);
|
|
511
|
+
document.documentElement.style.setProperty("--fg-accent-hover", this.config.accentColor);
|
|
512
|
+
}
|
|
513
|
+
this.stylesInjected = true;
|
|
514
|
+
}
|
|
515
|
+
createModal() {
|
|
516
|
+
if (typeof document === "undefined") return;
|
|
517
|
+
const backdrop = document.createElement("div");
|
|
518
|
+
backdrop.className = "fg-modal-backdrop";
|
|
519
|
+
backdrop.innerHTML = `
|
|
520
|
+
<div class="fg-modal">
|
|
521
|
+
<div id="fg-content"></div>
|
|
522
|
+
<div class="fg-footer">
|
|
523
|
+
<p>Powered by <a href="https://followgate.app" target="_blank" rel="noopener">FollowGate</a></p>
|
|
524
|
+
</div>
|
|
525
|
+
</div>
|
|
526
|
+
`;
|
|
527
|
+
document.body.appendChild(backdrop);
|
|
528
|
+
this.modalElement = backdrop;
|
|
529
|
+
requestAnimationFrame(() => {
|
|
530
|
+
backdrop.classList.add("fg-visible");
|
|
531
|
+
});
|
|
532
|
+
if (this.hasUsername()) {
|
|
533
|
+
this.renderFollowStep();
|
|
534
|
+
} else {
|
|
535
|
+
this.renderUsernameStep();
|
|
536
|
+
}
|
|
537
|
+
}
|
|
538
|
+
getContentElement() {
|
|
539
|
+
return document.getElementById("fg-content");
|
|
540
|
+
}
|
|
541
|
+
renderUsernameStep() {
|
|
542
|
+
const content = this.getContentElement();
|
|
543
|
+
if (!content) return;
|
|
544
|
+
content.innerHTML = `
|
|
545
|
+
<div class="fg-icon-box">
|
|
546
|
+
${ICONS.x}
|
|
547
|
+
</div>
|
|
548
|
+
<h2 class="fg-title">Unlock Free Access</h2>
|
|
549
|
+
<p class="fg-subtitle">Enter your X username to get started</p>
|
|
550
|
+
<div class="fg-input-wrapper">
|
|
551
|
+
<span class="fg-input-prefix">@</span>
|
|
552
|
+
<input type="text" class="fg-input" id="fg-username-input" placeholder="your_username" autofocus>
|
|
553
|
+
</div>
|
|
554
|
+
<button class="fg-btn fg-btn-primary" id="fg-username-submit" disabled>
|
|
555
|
+
Continue
|
|
556
|
+
</button>
|
|
557
|
+
<p class="fg-hint">No login required \u2013 just your username</p>
|
|
558
|
+
`;
|
|
559
|
+
const input = document.getElementById("fg-username-input");
|
|
560
|
+
const btn = document.getElementById("fg-username-submit");
|
|
561
|
+
input?.addEventListener("input", () => {
|
|
562
|
+
btn.disabled = !input.value.trim();
|
|
563
|
+
});
|
|
564
|
+
input?.addEventListener("keydown", (e) => {
|
|
565
|
+
if (e.key === "Enter" && input.value.trim()) {
|
|
566
|
+
this.handleUsernameSubmit(input.value.trim());
|
|
567
|
+
}
|
|
568
|
+
});
|
|
569
|
+
btn?.addEventListener("click", () => {
|
|
570
|
+
if (input?.value.trim()) {
|
|
571
|
+
this.handleUsernameSubmit(input.value.trim());
|
|
572
|
+
}
|
|
573
|
+
});
|
|
574
|
+
setTimeout(() => input?.focus(), 100);
|
|
575
|
+
}
|
|
576
|
+
handleUsernameSubmit(username) {
|
|
577
|
+
const normalized = username.replace(/^@/, "");
|
|
578
|
+
this.setUsername(normalized);
|
|
579
|
+
this.renderFollowStep();
|
|
580
|
+
}
|
|
581
|
+
renderFollowStep() {
|
|
582
|
+
const content = this.getContentElement();
|
|
583
|
+
if (!content || !this.config?.twitter) return;
|
|
584
|
+
const handle = this.config.twitter.handle;
|
|
585
|
+
const hasRepost = !!this.config.twitter.tweetId;
|
|
586
|
+
content.innerHTML = `
|
|
587
|
+
${hasRepost ? this.renderStepIndicator(1) : ""}
|
|
588
|
+
<div class="fg-icon-box">
|
|
589
|
+
${ICONS.x}
|
|
590
|
+
</div>
|
|
591
|
+
${this.currentUser ? `
|
|
592
|
+
<div class="fg-user-badge">
|
|
593
|
+
<div class="fg-user-badge-dot"></div>
|
|
594
|
+
<span class="fg-user-badge-text">@${this.currentUser.username}</span>
|
|
595
|
+
</div>
|
|
596
|
+
` : ""}
|
|
597
|
+
<h2 class="fg-title">${hasRepost ? "Step 1: Follow" : "Follow to Continue"}</h2>
|
|
598
|
+
<p class="fg-subtitle" id="fg-follow-subtitle">Follow @${handle} on X</p>
|
|
599
|
+
<div id="fg-follow-actions">
|
|
600
|
+
<button class="fg-btn fg-btn-dark" id="fg-follow-btn">
|
|
601
|
+
${ICONS.x}
|
|
602
|
+
Follow @${handle}
|
|
603
|
+
</button>
|
|
604
|
+
</div>
|
|
605
|
+
<p class="fg-hint">A new window will open. Return here after following.</p>
|
|
606
|
+
`;
|
|
607
|
+
document.getElementById("fg-follow-btn")?.addEventListener("click", () => {
|
|
608
|
+
this.handleFollowClick();
|
|
609
|
+
});
|
|
610
|
+
}
|
|
611
|
+
handleFollowClick() {
|
|
612
|
+
if (!this.config?.twitter) return;
|
|
613
|
+
const handle = this.config.twitter.handle;
|
|
614
|
+
this.openIntent({
|
|
615
|
+
platform: "twitter",
|
|
616
|
+
action: "follow",
|
|
617
|
+
target: handle
|
|
618
|
+
});
|
|
619
|
+
this.showFollowConfirmation();
|
|
620
|
+
}
|
|
621
|
+
showFollowConfirmation() {
|
|
622
|
+
if (!this.config?.twitter) return;
|
|
623
|
+
const handle = this.config.twitter.handle;
|
|
624
|
+
const subtitle = document.getElementById("fg-follow-subtitle");
|
|
625
|
+
const actions = document.getElementById("fg-follow-actions");
|
|
626
|
+
if (subtitle) {
|
|
627
|
+
subtitle.textContent = `Did you follow @${handle}?`;
|
|
628
|
+
}
|
|
629
|
+
if (actions) {
|
|
630
|
+
let seconds = WAIT_TIME_SECONDS;
|
|
631
|
+
actions.innerHTML = `
|
|
632
|
+
<div class="fg-info-box">
|
|
633
|
+
<p>You can close the X window after following</p>
|
|
634
|
+
</div>
|
|
635
|
+
<button class="fg-btn fg-btn-green" id="fg-follow-confirm" disabled>
|
|
636
|
+
<span class="fg-spinner"></span>
|
|
637
|
+
Waiting... (${seconds}s)
|
|
638
|
+
</button>
|
|
639
|
+
<button class="fg-btn fg-btn-secondary" id="fg-follow-retry">
|
|
640
|
+
${ICONS.x}
|
|
641
|
+
Open again
|
|
642
|
+
</button>
|
|
643
|
+
`;
|
|
644
|
+
const confirmBtn = document.getElementById("fg-follow-confirm");
|
|
645
|
+
const retryBtn = document.getElementById("fg-follow-retry");
|
|
646
|
+
const interval = setInterval(() => {
|
|
647
|
+
seconds--;
|
|
648
|
+
if (seconds <= 0) {
|
|
649
|
+
clearInterval(interval);
|
|
650
|
+
if (confirmBtn) {
|
|
651
|
+
confirmBtn.disabled = false;
|
|
652
|
+
confirmBtn.innerHTML = `${ICONS.check} Yes, I followed`;
|
|
653
|
+
}
|
|
654
|
+
} else if (confirmBtn) {
|
|
655
|
+
confirmBtn.innerHTML = `<span class="fg-spinner"></span> Waiting... (${seconds}s)`;
|
|
656
|
+
}
|
|
657
|
+
}, 1e3);
|
|
658
|
+
confirmBtn?.addEventListener("click", () => {
|
|
659
|
+
this.handleFollowConfirm();
|
|
660
|
+
});
|
|
661
|
+
retryBtn?.addEventListener("click", () => {
|
|
662
|
+
this.handleFollowClick();
|
|
663
|
+
});
|
|
664
|
+
}
|
|
665
|
+
}
|
|
666
|
+
async handleFollowConfirm() {
|
|
667
|
+
if (!this.config?.twitter) return;
|
|
668
|
+
const handle = this.config.twitter.handle;
|
|
669
|
+
await this.complete({
|
|
670
|
+
platform: "twitter",
|
|
671
|
+
action: "follow",
|
|
672
|
+
target: handle
|
|
673
|
+
});
|
|
674
|
+
if (this.config.twitter.tweetId) {
|
|
675
|
+
this.renderRepostStep();
|
|
676
|
+
} else {
|
|
677
|
+
this.renderConfirmStep();
|
|
678
|
+
}
|
|
679
|
+
}
|
|
680
|
+
renderRepostStep() {
|
|
681
|
+
const content = this.getContentElement();
|
|
682
|
+
if (!content || !this.config?.twitter?.tweetId) return;
|
|
683
|
+
content.innerHTML = `
|
|
684
|
+
${this.renderStepIndicator(2)}
|
|
685
|
+
<div class="fg-icon-box fg-success">
|
|
686
|
+
${ICONS.repost}
|
|
687
|
+
</div>
|
|
688
|
+
${this.currentUser ? `
|
|
689
|
+
<div class="fg-user-badge">
|
|
690
|
+
<div class="fg-user-badge-dot"></div>
|
|
691
|
+
<span class="fg-user-badge-text">@${this.currentUser.username}</span>
|
|
692
|
+
</div>
|
|
693
|
+
` : ""}
|
|
694
|
+
<h2 class="fg-title">Step 2: Repost</h2>
|
|
695
|
+
<p class="fg-subtitle" id="fg-repost-subtitle">Repost the tweet to continue</p>
|
|
696
|
+
<div id="fg-repost-actions">
|
|
697
|
+
<button class="fg-btn fg-btn-green" id="fg-repost-btn">
|
|
698
|
+
${ICONS.repost}
|
|
699
|
+
Repost tweet
|
|
700
|
+
</button>
|
|
701
|
+
</div>
|
|
702
|
+
<p class="fg-hint">A new window will open. Return here after reposting.</p>
|
|
703
|
+
`;
|
|
704
|
+
document.getElementById("fg-repost-btn")?.addEventListener("click", () => {
|
|
705
|
+
this.handleRepostClick();
|
|
706
|
+
});
|
|
707
|
+
}
|
|
708
|
+
handleRepostClick() {
|
|
709
|
+
if (!this.config?.twitter?.tweetId) return;
|
|
710
|
+
const tweetId = this.config.twitter.tweetId;
|
|
711
|
+
this.openIntent({
|
|
712
|
+
platform: "twitter",
|
|
713
|
+
action: "repost",
|
|
714
|
+
target: tweetId
|
|
715
|
+
});
|
|
716
|
+
this.showRepostConfirmation();
|
|
717
|
+
}
|
|
718
|
+
showRepostConfirmation() {
|
|
719
|
+
const subtitle = document.getElementById("fg-repost-subtitle");
|
|
720
|
+
const actions = document.getElementById("fg-repost-actions");
|
|
721
|
+
if (subtitle) {
|
|
722
|
+
subtitle.textContent = "Did you repost the tweet?";
|
|
723
|
+
}
|
|
724
|
+
if (actions) {
|
|
725
|
+
let seconds = WAIT_TIME_SECONDS;
|
|
726
|
+
actions.innerHTML = `
|
|
727
|
+
<div class="fg-info-box">
|
|
728
|
+
<p>You can close the X window after reposting</p>
|
|
729
|
+
</div>
|
|
730
|
+
<button class="fg-btn fg-btn-green" id="fg-repost-confirm" disabled>
|
|
731
|
+
<span class="fg-spinner"></span>
|
|
732
|
+
Waiting... (${seconds}s)
|
|
733
|
+
</button>
|
|
734
|
+
<button class="fg-btn fg-btn-secondary" id="fg-repost-retry">
|
|
735
|
+
${ICONS.repost}
|
|
736
|
+
Open tweet again
|
|
737
|
+
</button>
|
|
738
|
+
`;
|
|
739
|
+
const confirmBtn = document.getElementById("fg-repost-confirm");
|
|
740
|
+
const retryBtn = document.getElementById("fg-repost-retry");
|
|
741
|
+
const interval = setInterval(() => {
|
|
742
|
+
seconds--;
|
|
743
|
+
if (seconds <= 0) {
|
|
744
|
+
clearInterval(interval);
|
|
745
|
+
if (confirmBtn) {
|
|
746
|
+
confirmBtn.disabled = false;
|
|
747
|
+
confirmBtn.innerHTML = `${ICONS.check} Yes, I reposted`;
|
|
748
|
+
}
|
|
749
|
+
} else if (confirmBtn) {
|
|
750
|
+
confirmBtn.innerHTML = `<span class="fg-spinner"></span> Waiting... (${seconds}s)`;
|
|
751
|
+
}
|
|
752
|
+
}, 1e3);
|
|
753
|
+
confirmBtn?.addEventListener("click", () => {
|
|
754
|
+
this.handleRepostConfirm();
|
|
755
|
+
});
|
|
756
|
+
retryBtn?.addEventListener("click", () => {
|
|
757
|
+
this.handleRepostClick();
|
|
758
|
+
});
|
|
759
|
+
}
|
|
760
|
+
}
|
|
761
|
+
async handleRepostConfirm() {
|
|
762
|
+
if (!this.config?.twitter?.tweetId) return;
|
|
763
|
+
await this.complete({
|
|
764
|
+
platform: "twitter",
|
|
765
|
+
action: "repost",
|
|
766
|
+
target: this.config.twitter.tweetId
|
|
767
|
+
});
|
|
768
|
+
this.renderConfirmStep();
|
|
769
|
+
}
|
|
770
|
+
renderConfirmStep() {
|
|
771
|
+
const content = this.getContentElement();
|
|
772
|
+
if (!content) return;
|
|
773
|
+
content.innerHTML = `
|
|
774
|
+
<h2 class="fg-title">Almost done!</h2>
|
|
775
|
+
<div class="fg-warning-box">
|
|
776
|
+
${ICONS.warning}
|
|
777
|
+
<p><strong>Note:</strong> Access may be revoked if actions are not completed.</p>
|
|
778
|
+
</div>
|
|
779
|
+
<button class="fg-btn fg-btn-green" id="fg-finish-btn">
|
|
780
|
+
${ICONS.check}
|
|
781
|
+
Got it
|
|
782
|
+
</button>
|
|
783
|
+
${this.config?.twitter ? `
|
|
784
|
+
<div class="fg-btn-row">
|
|
785
|
+
<button class="fg-btn fg-btn-secondary" id="fg-redo-follow">
|
|
786
|
+
${ICONS.x}
|
|
787
|
+
Open follow
|
|
788
|
+
</button>
|
|
789
|
+
${this.config.twitter.tweetId ? `
|
|
790
|
+
<button class="fg-btn fg-btn-secondary" id="fg-redo-repost">
|
|
791
|
+
${ICONS.repost}
|
|
792
|
+
Open repost
|
|
793
|
+
</button>
|
|
794
|
+
` : ""}
|
|
795
|
+
</div>
|
|
796
|
+
` : ""}
|
|
797
|
+
`;
|
|
798
|
+
document.getElementById("fg-finish-btn")?.addEventListener("click", () => {
|
|
799
|
+
this.handleFinish();
|
|
800
|
+
});
|
|
801
|
+
document.getElementById("fg-redo-follow")?.addEventListener("click", () => {
|
|
802
|
+
if (this.config?.twitter) {
|
|
803
|
+
this.openIntent({
|
|
804
|
+
platform: "twitter",
|
|
805
|
+
action: "follow",
|
|
806
|
+
target: this.config.twitter.handle
|
|
807
|
+
});
|
|
808
|
+
}
|
|
809
|
+
});
|
|
810
|
+
document.getElementById("fg-redo-repost")?.addEventListener("click", () => {
|
|
811
|
+
if (this.config?.twitter?.tweetId) {
|
|
812
|
+
this.openIntent({
|
|
813
|
+
platform: "twitter",
|
|
814
|
+
action: "repost",
|
|
815
|
+
target: this.config.twitter.tweetId
|
|
816
|
+
});
|
|
817
|
+
}
|
|
818
|
+
});
|
|
819
|
+
}
|
|
820
|
+
async handleFinish() {
|
|
821
|
+
await this.unlock();
|
|
822
|
+
this.hide();
|
|
823
|
+
this.config?.onComplete?.();
|
|
824
|
+
}
|
|
825
|
+
renderStepIndicator(currentStep) {
|
|
826
|
+
return `
|
|
827
|
+
<div class="fg-step-indicator">
|
|
828
|
+
<div class="fg-step-dot ${currentStep > 1 ? "fg-done" : currentStep === 1 ? "fg-active" : "fg-pending"}">
|
|
829
|
+
${currentStep > 1 ? "\u2713" : "1"}
|
|
830
|
+
</div>
|
|
831
|
+
<div class="fg-step-line ${currentStep > 1 ? "fg-done" : ""}"></div>
|
|
832
|
+
<div class="fg-step-dot ${currentStep > 2 ? "fg-done" : currentStep === 2 ? "fg-active" : "fg-pending"}">
|
|
833
|
+
${currentStep > 2 ? "\u2713" : "2"}
|
|
834
|
+
</div>
|
|
835
|
+
</div>
|
|
836
|
+
`;
|
|
837
|
+
}
|
|
838
|
+
// ============================================
|
|
839
|
+
// Core SDK Methods
|
|
840
|
+
// ============================================
|
|
101
841
|
/**
|
|
102
842
|
* Set the user's social username
|
|
103
|
-
* This is the main entry point - no OAuth needed!
|
|
104
843
|
*/
|
|
105
844
|
setUsername(username, platform = "twitter") {
|
|
106
845
|
if (!this.config) {
|
|
@@ -148,27 +887,15 @@ var FollowGateClient = class {
|
|
|
148
887
|
// ============================================
|
|
149
888
|
// Intent URL Methods
|
|
150
889
|
// ============================================
|
|
151
|
-
/**
|
|
152
|
-
* Get follow intent URL for a platform
|
|
153
|
-
*/
|
|
154
890
|
getFollowUrl(platform, target) {
|
|
155
891
|
return this.buildIntentUrl({ platform, action: "follow", target });
|
|
156
892
|
}
|
|
157
|
-
/**
|
|
158
|
-
* Get repost/retweet intent URL for a platform
|
|
159
|
-
*/
|
|
160
893
|
getRepostUrl(platform, target) {
|
|
161
894
|
return this.buildIntentUrl({ platform, action: "repost", target });
|
|
162
895
|
}
|
|
163
|
-
/**
|
|
164
|
-
* Get like intent URL for a platform
|
|
165
|
-
*/
|
|
166
896
|
getLikeUrl(platform, target) {
|
|
167
897
|
return this.buildIntentUrl({ platform, action: "like", target });
|
|
168
898
|
}
|
|
169
|
-
/**
|
|
170
|
-
* Open intent URL in new window
|
|
171
|
-
*/
|
|
172
899
|
async openIntent(options) {
|
|
173
900
|
if (!this.config) {
|
|
174
901
|
throw new Error("[FollowGate] SDK not initialized. Call init() first.");
|
|
@@ -183,18 +910,12 @@ var FollowGateClient = class {
|
|
|
183
910
|
// ============================================
|
|
184
911
|
// Completion Methods
|
|
185
912
|
// ============================================
|
|
186
|
-
/**
|
|
187
|
-
* Mark an action as completed (trust-first)
|
|
188
|
-
* Call this when user confirms they did the action
|
|
189
|
-
*/
|
|
190
913
|
async complete(options) {
|
|
191
914
|
if (!this.config) {
|
|
192
915
|
throw new Error("[FollowGate] SDK not initialized. Call init() first.");
|
|
193
916
|
}
|
|
194
917
|
if (!this.currentUser) {
|
|
195
|
-
throw new Error(
|
|
196
|
-
"[FollowGate] No username set. Call setUsername() first."
|
|
197
|
-
);
|
|
918
|
+
throw new Error("[FollowGate] No username set. Call setUsername() first.");
|
|
198
919
|
}
|
|
199
920
|
const alreadyCompleted = this.completedActions.some(
|
|
200
921
|
(a) => a.platform === options.platform && a.action === options.action && a.target === options.target
|
|
@@ -215,10 +936,6 @@ var FollowGateClient = class {
|
|
|
215
936
|
console.log("[FollowGate] Action completed:", options);
|
|
216
937
|
}
|
|
217
938
|
}
|
|
218
|
-
/**
|
|
219
|
-
* Mark the gate as unlocked
|
|
220
|
-
* Call this when all required actions are done
|
|
221
|
-
*/
|
|
222
939
|
async unlock() {
|
|
223
940
|
if (!this.config) {
|
|
224
941
|
throw new Error("[FollowGate] SDK not initialized. Call init() first.");
|
|
@@ -238,16 +955,10 @@ var FollowGateClient = class {
|
|
|
238
955
|
console.log("[FollowGate] Gate unlocked!");
|
|
239
956
|
}
|
|
240
957
|
}
|
|
241
|
-
/**
|
|
242
|
-
* Check if gate is unlocked
|
|
243
|
-
*/
|
|
244
958
|
isUnlocked() {
|
|
245
959
|
if (typeof localStorage === "undefined") return false;
|
|
246
960
|
return localStorage.getItem("followgate_unlocked") === "true";
|
|
247
961
|
}
|
|
248
|
-
/**
|
|
249
|
-
* Get unlock status with details
|
|
250
|
-
*/
|
|
251
962
|
getUnlockStatus() {
|
|
252
963
|
return {
|
|
253
964
|
unlocked: this.isUnlocked(),
|
|
@@ -255,36 +966,24 @@ var FollowGateClient = class {
|
|
|
255
966
|
completedActions: [...this.completedActions]
|
|
256
967
|
};
|
|
257
968
|
}
|
|
258
|
-
/**
|
|
259
|
-
* Get completed actions
|
|
260
|
-
*/
|
|
261
969
|
getCompletedActions() {
|
|
262
970
|
return [...this.completedActions];
|
|
263
971
|
}
|
|
264
972
|
// ============================================
|
|
265
973
|
// Event System
|
|
266
974
|
// ============================================
|
|
267
|
-
/**
|
|
268
|
-
* Register event listener
|
|
269
|
-
*/
|
|
270
975
|
on(event, callback) {
|
|
271
976
|
if (!this.listeners.has(event)) {
|
|
272
977
|
this.listeners.set(event, /* @__PURE__ */ new Set());
|
|
273
978
|
}
|
|
274
979
|
this.listeners.get(event).add(callback);
|
|
275
980
|
}
|
|
276
|
-
/**
|
|
277
|
-
* Remove event listener
|
|
278
|
-
*/
|
|
279
981
|
off(event, callback) {
|
|
280
982
|
this.listeners.get(event)?.delete(callback);
|
|
281
983
|
}
|
|
282
984
|
// ============================================
|
|
283
985
|
// Private Methods
|
|
284
986
|
// ============================================
|
|
285
|
-
/**
|
|
286
|
-
* Restore session from localStorage
|
|
287
|
-
*/
|
|
288
987
|
restoreSession() {
|
|
289
988
|
if (typeof localStorage === "undefined") return;
|
|
290
989
|
const userJson = localStorage.getItem("followgate_user");
|
|
@@ -304,9 +1003,6 @@ var FollowGateClient = class {
|
|
|
304
1003
|
}
|
|
305
1004
|
}
|
|
306
1005
|
}
|
|
307
|
-
/**
|
|
308
|
-
* Save completed actions to localStorage
|
|
309
|
-
*/
|
|
310
1006
|
saveCompletedActions() {
|
|
311
1007
|
if (typeof localStorage !== "undefined") {
|
|
312
1008
|
localStorage.setItem(
|
|
@@ -315,9 +1011,6 @@ var FollowGateClient = class {
|
|
|
315
1011
|
);
|
|
316
1012
|
}
|
|
317
1013
|
}
|
|
318
|
-
/**
|
|
319
|
-
* Build intent URL for platform
|
|
320
|
-
*/
|
|
321
1014
|
buildIntentUrl(options) {
|
|
322
1015
|
const { platform, action, target } = options;
|
|
323
1016
|
switch (platform) {
|
|
@@ -376,9 +1069,6 @@ var FollowGateClient = class {
|
|
|
376
1069
|
throw new Error(`[FollowGate] Unsupported LinkedIn action: ${action}`);
|
|
377
1070
|
}
|
|
378
1071
|
}
|
|
379
|
-
/**
|
|
380
|
-
* Track analytics event
|
|
381
|
-
*/
|
|
382
1072
|
async trackEvent(event, data) {
|
|
383
1073
|
if (!this.config) return;
|
|
384
1074
|
try {
|