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