@buoy-gg/impersonate 1.0.3-beta.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.
Files changed (95) hide show
  1. package/LICENSE +58 -0
  2. package/lib/commonjs/impersonate/components/DataNukeSettings.js +715 -0
  3. package/lib/commonjs/impersonate/components/ImpersonateBanner.js +217 -0
  4. package/lib/commonjs/impersonate/components/ImpersonateHistoryList.js +173 -0
  5. package/lib/commonjs/impersonate/components/ImpersonateModal.js +304 -0
  6. package/lib/commonjs/impersonate/components/ImpersonateStatusBar.js +130 -0
  7. package/lib/commonjs/impersonate/components/UserAvatar.js +146 -0
  8. package/lib/commonjs/impersonate/components/UserCard.js +200 -0
  9. package/lib/commonjs/impersonate/components/UserSearchView.js +227 -0
  10. package/lib/commonjs/impersonate/components/index.js +85 -0
  11. package/lib/commonjs/impersonate/hooks/index.js +64 -0
  12. package/lib/commonjs/impersonate/hooks/useAutoClearAsyncStorage.js +144 -0
  13. package/lib/commonjs/impersonate/hooks/useAutoClearReactQuery.js +155 -0
  14. package/lib/commonjs/impersonate/hooks/useAutoClearRedux.js +188 -0
  15. package/lib/commonjs/impersonate/hooks/useImpersonate.js +215 -0
  16. package/lib/commonjs/impersonate/hooks/useImpersonateHistory.js +56 -0
  17. package/lib/commonjs/impersonate/index.js +49 -0
  18. package/lib/commonjs/impersonate/types/index.js +16 -0
  19. package/lib/commonjs/impersonate/types/types.js +1 -0
  20. package/lib/commonjs/impersonate/utils/impersonateListener.js +280 -0
  21. package/lib/commonjs/impersonate/utils/impersonateStore.js +607 -0
  22. package/lib/commonjs/impersonate/utils/index.js +49 -0
  23. package/lib/commonjs/index.js +118 -0
  24. package/lib/commonjs/package.json +1 -0
  25. package/lib/commonjs/preset.js +214 -0
  26. package/lib/module/impersonate/components/DataNukeSettings.js +710 -0
  27. package/lib/module/impersonate/components/ImpersonateBanner.js +211 -0
  28. package/lib/module/impersonate/components/ImpersonateHistoryList.js +168 -0
  29. package/lib/module/impersonate/components/ImpersonateModal.js +300 -0
  30. package/lib/module/impersonate/components/ImpersonateStatusBar.js +125 -0
  31. package/lib/module/impersonate/components/UserAvatar.js +140 -0
  32. package/lib/module/impersonate/components/UserCard.js +195 -0
  33. package/lib/module/impersonate/components/UserSearchView.js +222 -0
  34. package/lib/module/impersonate/components/index.js +11 -0
  35. package/lib/module/impersonate/hooks/index.js +7 -0
  36. package/lib/module/impersonate/hooks/useAutoClearAsyncStorage.js +140 -0
  37. package/lib/module/impersonate/hooks/useAutoClearReactQuery.js +151 -0
  38. package/lib/module/impersonate/hooks/useAutoClearRedux.js +183 -0
  39. package/lib/module/impersonate/hooks/useImpersonate.js +212 -0
  40. package/lib/module/impersonate/hooks/useImpersonateHistory.js +52 -0
  41. package/lib/module/impersonate/index.js +13 -0
  42. package/lib/module/impersonate/types/index.js +3 -0
  43. package/lib/module/impersonate/types/types.js +1 -0
  44. package/lib/module/impersonate/utils/impersonateListener.js +271 -0
  45. package/lib/module/impersonate/utils/impersonateStore.js +604 -0
  46. package/lib/module/impersonate/utils/index.js +4 -0
  47. package/lib/module/index.js +103 -0
  48. package/lib/module/preset.js +209 -0
  49. package/lib/typescript/impersonate/components/DataNukeSettings.d.ts +37 -0
  50. package/lib/typescript/impersonate/components/DataNukeSettings.d.ts.map +1 -0
  51. package/lib/typescript/impersonate/components/ImpersonateBanner.d.ts +40 -0
  52. package/lib/typescript/impersonate/components/ImpersonateBanner.d.ts.map +1 -0
  53. package/lib/typescript/impersonate/components/ImpersonateHistoryList.d.ts +24 -0
  54. package/lib/typescript/impersonate/components/ImpersonateHistoryList.d.ts.map +1 -0
  55. package/lib/typescript/impersonate/components/ImpersonateModal.d.ts +10 -0
  56. package/lib/typescript/impersonate/components/ImpersonateModal.d.ts.map +1 -0
  57. package/lib/typescript/impersonate/components/ImpersonateStatusBar.d.ts +15 -0
  58. package/lib/typescript/impersonate/components/ImpersonateStatusBar.d.ts.map +1 -0
  59. package/lib/typescript/impersonate/components/UserAvatar.d.ts +32 -0
  60. package/lib/typescript/impersonate/components/UserAvatar.d.ts.map +1 -0
  61. package/lib/typescript/impersonate/components/UserCard.d.ts +28 -0
  62. package/lib/typescript/impersonate/components/UserCard.d.ts.map +1 -0
  63. package/lib/typescript/impersonate/components/UserSearchView.d.ts +31 -0
  64. package/lib/typescript/impersonate/components/UserSearchView.d.ts.map +1 -0
  65. package/lib/typescript/impersonate/components/index.d.ts +16 -0
  66. package/lib/typescript/impersonate/components/index.d.ts.map +1 -0
  67. package/lib/typescript/impersonate/hooks/index.d.ts +11 -0
  68. package/lib/typescript/impersonate/hooks/index.d.ts.map +1 -0
  69. package/lib/typescript/impersonate/hooks/useAutoClearAsyncStorage.d.ts +48 -0
  70. package/lib/typescript/impersonate/hooks/useAutoClearAsyncStorage.d.ts.map +1 -0
  71. package/lib/typescript/impersonate/hooks/useAutoClearReactQuery.d.ts +48 -0
  72. package/lib/typescript/impersonate/hooks/useAutoClearReactQuery.d.ts.map +1 -0
  73. package/lib/typescript/impersonate/hooks/useAutoClearRedux.d.ts +78 -0
  74. package/lib/typescript/impersonate/hooks/useAutoClearRedux.d.ts.map +1 -0
  75. package/lib/typescript/impersonate/hooks/useImpersonate.d.ts +76 -0
  76. package/lib/typescript/impersonate/hooks/useImpersonate.d.ts.map +1 -0
  77. package/lib/typescript/impersonate/hooks/useImpersonateHistory.d.ts +43 -0
  78. package/lib/typescript/impersonate/hooks/useImpersonateHistory.d.ts.map +1 -0
  79. package/lib/typescript/impersonate/index.d.ts +5 -0
  80. package/lib/typescript/impersonate/index.d.ts.map +1 -0
  81. package/lib/typescript/impersonate/types/index.d.ts +2 -0
  82. package/lib/typescript/impersonate/types/index.d.ts.map +1 -0
  83. package/lib/typescript/impersonate/types/types.d.ts +177 -0
  84. package/lib/typescript/impersonate/types/types.d.ts.map +1 -0
  85. package/lib/typescript/impersonate/utils/impersonateListener.d.ts +115 -0
  86. package/lib/typescript/impersonate/utils/impersonateListener.d.ts.map +1 -0
  87. package/lib/typescript/impersonate/utils/impersonateStore.d.ts +151 -0
  88. package/lib/typescript/impersonate/utils/impersonateStore.d.ts.map +1 -0
  89. package/lib/typescript/impersonate/utils/index.d.ts +3 -0
  90. package/lib/typescript/impersonate/utils/index.d.ts.map +1 -0
  91. package/lib/typescript/index.d.ts +80 -0
  92. package/lib/typescript/index.d.ts.map +1 -0
  93. package/lib/typescript/preset.d.ts +71 -0
  94. package/lib/typescript/preset.d.ts.map +1 -0
  95. package/package.json +78 -0
@@ -0,0 +1,607 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.impersonateStore = void 0;
7
+ var _impersonateListener = require("./impersonateListener");
8
+ /**
9
+ * Impersonate Store - State Management with Persistence
10
+ *
11
+ * Singleton store for managing impersonation state. Uses a subscription
12
+ * pattern compatible with React's useSyncExternalStore.
13
+ */
14
+
15
+ // =============================================================================
16
+ // CONSTANTS
17
+ // =============================================================================
18
+
19
+ const STORAGE_KEY = "@buoy/impersonate/state";
20
+ const MAX_HISTORY = 10;
21
+ const DEFAULT_DATA_NUKE_SETTINGS = {
22
+ reactQuery: true,
23
+ redux: true,
24
+ asyncStorage: false,
25
+ // Dangerous - default off
26
+ mmkv: false // Dangerous - default off
27
+ };
28
+ const DEFAULT_STATE = {
29
+ isActive: false,
30
+ isPaused: false,
31
+ currentUser: null,
32
+ headerKey: "x-impersonate-user-id",
33
+ ignorePatterns: [],
34
+ dataNukeSettings: DEFAULT_DATA_NUKE_SETTINGS,
35
+ showBanner: true,
36
+ history: []
37
+ };
38
+
39
+ // =============================================================================
40
+ // TYPES
41
+ // =============================================================================
42
+
43
+ // Guard to prevent double-execution of data nuking
44
+ let isNuking = false;
45
+
46
+ // =============================================================================
47
+ // SIMPLE STORAGE ABSTRACTION
48
+ // =============================================================================
49
+
50
+ // Use a simple localStorage-like interface for persistence
51
+ // This works on both web and native (via polyfills)
52
+ const storage = {
53
+ getItem: key => {
54
+ try {
55
+ if (typeof localStorage !== "undefined") {
56
+ return localStorage.getItem(key);
57
+ }
58
+ // React Native: use AsyncStorage if available
59
+ // For now, return null (state won't persist until async storage is loaded)
60
+ return null;
61
+ } catch {
62
+ return null;
63
+ }
64
+ },
65
+ setItem: (key, value) => {
66
+ try {
67
+ if (typeof localStorage !== "undefined") {
68
+ localStorage.setItem(key, value);
69
+ }
70
+ // React Native: handled separately via async methods
71
+ } catch {
72
+ // Ignore storage errors
73
+ }
74
+ },
75
+ removeItem: key => {
76
+ try {
77
+ if (typeof localStorage !== "undefined") {
78
+ localStorage.removeItem(key);
79
+ }
80
+ } catch {
81
+ // Ignore
82
+ }
83
+ }
84
+ };
85
+
86
+ // =============================================================================
87
+ // STORE CLASS
88
+ // =============================================================================
89
+
90
+ /**
91
+ * Singleton store for impersonation state
92
+ *
93
+ * Features:
94
+ * - Subscription pattern for React integration
95
+ * - Persistence of settings and history
96
+ * - Data nuking on impersonation change
97
+ * - Automatic sync with impersonateListener
98
+ */
99
+ class ImpersonateStore {
100
+ state = {
101
+ ...DEFAULT_STATE
102
+ };
103
+ listeners = new Set();
104
+ nukeCallbacks = {};
105
+ isInitialized = false;
106
+ asyncStorageRef = null;
107
+ storageReadyPromise = null;
108
+ resolveStorageReady = null;
109
+ developerDefaults = null;
110
+ constructor() {
111
+ // Try to load persisted state synchronously
112
+ this.loadFromStorage();
113
+
114
+ // Check if localStorage is available (web) - if so, storage is ready
115
+ if (typeof localStorage !== "undefined") {
116
+ this.storageReadyPromise = Promise.resolve();
117
+ } else {
118
+ // React Native - wait for AsyncStorage to be set up
119
+ this.storageReadyPromise = new Promise(resolve => {
120
+ this.resolveStorageReady = resolve;
121
+ });
122
+ }
123
+ }
124
+
125
+ // ===========================================================================
126
+ // INITIALIZATION
127
+ // ===========================================================================
128
+
129
+ /**
130
+ * Load persisted state from storage (sync)
131
+ */
132
+ loadFromStorage() {
133
+ try {
134
+ const stored = storage.getItem(STORAGE_KEY);
135
+ const effectiveDefaults = this.getEffectiveDefaults();
136
+ if (stored) {
137
+ const parsed = JSON.parse(stored);
138
+ this.state = {
139
+ ...DEFAULT_STATE,
140
+ // Use effective defaults (developer > hardcoded) as fallback
141
+ headerKey: parsed.headerKey ?? effectiveDefaults.headerKey,
142
+ ignorePatterns: parsed.ignorePatterns ?? DEFAULT_STATE.ignorePatterns,
143
+ dataNukeSettings: {
144
+ ...effectiveDefaults.dataNukeSettings,
145
+ ...parsed.dataNukeSettings
146
+ },
147
+ showBanner: parsed.showBanner ?? effectiveDefaults.showBanner,
148
+ history: parsed.history ?? [],
149
+ // Restore active session for persistence across reloads
150
+ isActive: parsed.isActive ?? false,
151
+ isPaused: parsed.isPaused ?? false,
152
+ currentUser: parsed.currentUser ?? null
153
+ };
154
+
155
+ // If we restored an active session, sync with listener
156
+ if (this.state.isActive && this.state.currentUser) {
157
+ this.syncWithListener();
158
+ }
159
+ } else {
160
+ // No persisted state - use effective defaults
161
+ this.state = {
162
+ ...DEFAULT_STATE,
163
+ headerKey: effectiveDefaults.headerKey,
164
+ dataNukeSettings: effectiveDefaults.dataNukeSettings,
165
+ showBanner: effectiveDefaults.showBanner
166
+ };
167
+ }
168
+ this.isInitialized = true;
169
+ } catch {
170
+ this.isInitialized = true;
171
+ }
172
+ }
173
+
174
+ /**
175
+ * Set async storage reference for persistence (call before initializeAsync)
176
+ * This ensures all writes use AsyncStorage even before load completes
177
+ */
178
+ setAsyncStorage(asyncStorage) {
179
+ this.asyncStorageRef = asyncStorage;
180
+ // Resolve the storage ready promise so persist() can proceed
181
+ if (this.resolveStorageReady) {
182
+ this.resolveStorageReady();
183
+ this.resolveStorageReady = null;
184
+ }
185
+ }
186
+
187
+ /**
188
+ * Set developer-provided defaults
189
+ * These override hardcoded defaults but are overridden by persisted values
190
+ * Call this before the component mounts (in createImpersonateTool)
191
+ */
192
+ setDeveloperDefaults(defaults) {
193
+ this.developerDefaults = defaults;
194
+
195
+ // Apply defaults to current state if not already initialized from storage
196
+ // Priority: persisted > developer defaults > hardcoded defaults
197
+ const effectiveDefaults = this.getEffectiveDefaults();
198
+
199
+ // Only apply if we haven't loaded persisted values yet
200
+ if (!this.isInitialized) {
201
+ this.state = {
202
+ ...this.state,
203
+ headerKey: effectiveDefaults.headerKey,
204
+ dataNukeSettings: effectiveDefaults.dataNukeSettings,
205
+ showBanner: effectiveDefaults.showBanner
206
+ };
207
+ }
208
+ }
209
+
210
+ /**
211
+ * Get effective defaults (developer defaults merged with hardcoded defaults)
212
+ */
213
+ getEffectiveDefaults() {
214
+ return {
215
+ headerKey: this.developerDefaults?.headerKey ?? DEFAULT_STATE.headerKey,
216
+ dataNukeSettings: {
217
+ ...DEFAULT_DATA_NUKE_SETTINGS,
218
+ ...this.developerDefaults?.dataNukeSettings
219
+ },
220
+ showBanner: this.developerDefaults?.showBanner ?? DEFAULT_STATE.showBanner
221
+ };
222
+ }
223
+
224
+ /**
225
+ * Get developer defaults (for use in UI to show what the "default" values are)
226
+ */
227
+ getDeveloperDefaults() {
228
+ return this.developerDefaults;
229
+ }
230
+
231
+ /**
232
+ * Initialize with async storage (for React Native)
233
+ * Call this in useEffect to load from AsyncStorage
234
+ */
235
+ async initializeAsync(asyncStorage) {
236
+ if (!asyncStorage) return;
237
+
238
+ // Store reference for future persistence (in case not already set)
239
+ this.asyncStorageRef = asyncStorage;
240
+ try {
241
+ const stored = await asyncStorage.getItem(STORAGE_KEY);
242
+ console.log("[ImpersonateStore] initializeAsync - stored:", stored);
243
+ if (stored) {
244
+ const parsed = JSON.parse(stored);
245
+ console.log("[ImpersonateStore] initializeAsync - parsed dataNukeSettings:", parsed.dataNukeSettings);
246
+ this.state = {
247
+ ...this.state,
248
+ headerKey: parsed.headerKey ?? this.state.headerKey,
249
+ ignorePatterns: parsed.ignorePatterns ?? this.state.ignorePatterns,
250
+ dataNukeSettings: {
251
+ ...this.state.dataNukeSettings,
252
+ ...parsed.dataNukeSettings
253
+ },
254
+ showBanner: parsed.showBanner ?? this.state.showBanner,
255
+ history: parsed.history ?? this.state.history,
256
+ // Restore active session
257
+ isActive: parsed.isActive ?? this.state.isActive,
258
+ isPaused: parsed.isPaused ?? this.state.isPaused,
259
+ currentUser: parsed.currentUser ?? this.state.currentUser
260
+ };
261
+ console.log("[ImpersonateStore] initializeAsync - new dataNukeSettings:", this.state.dataNukeSettings);
262
+
263
+ // If we restored an active session, sync with listener
264
+ if (this.state.isActive && this.state.currentUser) {
265
+ this.syncWithListener();
266
+ }
267
+ this.notify();
268
+ }
269
+ } catch (e) {
270
+ console.log("[ImpersonateStore] initializeAsync error:", e);
271
+ }
272
+ }
273
+
274
+ /**
275
+ * Register callbacks for data nuking
276
+ */
277
+ registerNukeCallbacks(callbacks) {
278
+ this.nukeCallbacks = {
279
+ ...this.nukeCallbacks,
280
+ ...callbacks
281
+ };
282
+ }
283
+
284
+ // ===========================================================================
285
+ // STATE ACCESS
286
+ // ===========================================================================
287
+
288
+ /**
289
+ * Get current state (creates a copy)
290
+ */
291
+ getState() {
292
+ return {
293
+ ...this.state
294
+ };
295
+ }
296
+
297
+ /**
298
+ * Get snapshot for useSyncExternalStore
299
+ * Returns the same reference if state hasn't changed
300
+ */
301
+ getSnapshot = () => {
302
+ return this.state;
303
+ };
304
+
305
+ /**
306
+ * Subscribe to state changes
307
+ * Returns unsubscribe function
308
+ */
309
+ subscribe = listener => {
310
+ this.listeners.add(listener);
311
+ return () => this.listeners.delete(listener);
312
+ };
313
+
314
+ // ===========================================================================
315
+ // IMPERSONATION ACTIONS
316
+ // ===========================================================================
317
+
318
+ /**
319
+ * Start impersonating a user
320
+ *
321
+ * This will:
322
+ * 1. Execute data nuking based on settings
323
+ * 2. Update state with new user
324
+ * 3. Add to history
325
+ * 4. Sync with impersonateListener
326
+ * 5. Persist settings
327
+ */
328
+ async startImpersonation(user) {
329
+ // Execute data nuking BEFORE switching
330
+ await this.executeDataNuke();
331
+
332
+ // Create history entry with timestamp
333
+ const historyEntry = {
334
+ user,
335
+ lastUsedAt: new Date().toISOString()
336
+ };
337
+
338
+ // Update history (remove duplicate, add to front)
339
+ const newHistory = [historyEntry, ...this.state.history.filter(entry => entry.user.id !== user.id)].slice(0, MAX_HISTORY);
340
+
341
+ // Update state
342
+ this.state = {
343
+ ...this.state,
344
+ isActive: true,
345
+ currentUser: user,
346
+ history: newHistory
347
+ };
348
+
349
+ // Sync with listener
350
+ this.syncWithListener();
351
+
352
+ // Persist
353
+ await this.persist();
354
+
355
+ // Notify subscribers
356
+ this.notify();
357
+ }
358
+
359
+ /**
360
+ * Stop impersonating
361
+ *
362
+ * This will:
363
+ * 1. Execute data nuking based on settings
364
+ * 2. Clear impersonation state
365
+ * 3. Sync with impersonateListener
366
+ */
367
+ async stopImpersonation() {
368
+ // Execute data nuking
369
+ await this.executeDataNuke();
370
+
371
+ // Update state
372
+ this.state = {
373
+ ...this.state,
374
+ isActive: false,
375
+ isPaused: false,
376
+ currentUser: null
377
+ };
378
+
379
+ // Sync with listener
380
+ this.syncWithListener();
381
+
382
+ // Persist
383
+ await this.persist();
384
+
385
+ // Notify
386
+ this.notify();
387
+ }
388
+
389
+ /**
390
+ * Pause impersonation (temporarily stop injecting headers)
391
+ */
392
+ async pauseImpersonation() {
393
+ if (!this.state.isActive || this.state.isPaused) return;
394
+ this.state = {
395
+ ...this.state,
396
+ isPaused: true
397
+ };
398
+
399
+ // Sync with listener (will stop injecting headers)
400
+ this.syncWithListener();
401
+
402
+ // Persist
403
+ await this.persist();
404
+
405
+ // Notify
406
+ this.notify();
407
+ }
408
+
409
+ /**
410
+ * Resume impersonation (start injecting headers again)
411
+ */
412
+ async resumeImpersonation() {
413
+ if (!this.state.isActive || !this.state.isPaused) return;
414
+ this.state = {
415
+ ...this.state,
416
+ isPaused: false
417
+ };
418
+
419
+ // Sync with listener (will resume injecting headers)
420
+ this.syncWithListener();
421
+
422
+ // Persist
423
+ await this.persist();
424
+
425
+ // Notify
426
+ this.notify();
427
+ }
428
+
429
+ /**
430
+ * Quick switch to a user from history
431
+ */
432
+ async quickSwitch(user) {
433
+ return this.startImpersonation(user);
434
+ }
435
+
436
+ // ===========================================================================
437
+ // SETTINGS
438
+ // ===========================================================================
439
+
440
+ /**
441
+ * Update settings (header key, ignore patterns, data nuke settings, show banner)
442
+ */
443
+ async updateSettings(settings) {
444
+ console.log("[ImpersonateStore] updateSettings called with:", settings);
445
+ console.log("[ImpersonateStore] Current dataNukeSettings:", this.state.dataNukeSettings);
446
+ this.state = {
447
+ ...this.state,
448
+ headerKey: settings.headerKey ?? this.state.headerKey,
449
+ ignorePatterns: settings.ignorePatterns ?? this.state.ignorePatterns,
450
+ showBanner: settings.showBanner ?? this.state.showBanner,
451
+ dataNukeSettings: settings.dataNukeSettings ? {
452
+ ...this.state.dataNukeSettings,
453
+ ...settings.dataNukeSettings
454
+ } : this.state.dataNukeSettings
455
+ };
456
+ console.log("[ImpersonateStore] New dataNukeSettings:", this.state.dataNukeSettings);
457
+
458
+ // Sync header key with listener
459
+ if (settings.headerKey || settings.ignorePatterns) {
460
+ this.syncWithListener();
461
+ }
462
+ await this.persist();
463
+ this.notify();
464
+ }
465
+
466
+ // ===========================================================================
467
+ // HISTORY
468
+ // ===========================================================================
469
+
470
+ /**
471
+ * Remove a user from history
472
+ */
473
+ async removeFromHistory(userId) {
474
+ this.state = {
475
+ ...this.state,
476
+ history: this.state.history.filter(entry => entry.user.id !== userId)
477
+ };
478
+ await this.persist();
479
+ this.notify();
480
+ }
481
+
482
+ /**
483
+ * Clear all history
484
+ */
485
+ async clearHistory() {
486
+ this.state = {
487
+ ...this.state,
488
+ history: []
489
+ };
490
+ await this.persist();
491
+ this.notify();
492
+ }
493
+
494
+ // ===========================================================================
495
+ // PRIVATE METHODS
496
+ // ===========================================================================
497
+
498
+ /**
499
+ * Execute data nuking based on current settings
500
+ */
501
+ async executeDataNuke() {
502
+ // Guard against double-execution (can happen with React Strict Mode)
503
+ if (isNuking) {
504
+ return;
505
+ }
506
+ isNuking = true;
507
+ try {
508
+ const {
509
+ dataNukeSettings
510
+ } = this.state;
511
+ const promises = [];
512
+ if (dataNukeSettings.reactQuery && this.nukeCallbacks.reactQuery) {
513
+ promises.push(Promise.resolve(this.nukeCallbacks.reactQuery()));
514
+ }
515
+ if (dataNukeSettings.redux && this.nukeCallbacks.redux) {
516
+ promises.push(Promise.resolve(this.nukeCallbacks.redux()));
517
+ }
518
+ if (dataNukeSettings.asyncStorage && this.nukeCallbacks.asyncStorage) {
519
+ promises.push(Promise.resolve(this.nukeCallbacks.asyncStorage()));
520
+ }
521
+ if (dataNukeSettings.mmkv && this.nukeCallbacks.mmkv) {
522
+ promises.push(Promise.resolve(this.nukeCallbacks.mmkv()));
523
+ }
524
+
525
+ // Wait for all, but don't fail if some error
526
+ await Promise.allSettled(promises);
527
+ } finally {
528
+ // Reset flag after a short delay to allow next legitimate call
529
+ setTimeout(() => {
530
+ isNuking = false;
531
+ }, 100);
532
+ }
533
+ }
534
+
535
+ /**
536
+ * Sync current state with the impersonateListener
537
+ */
538
+ syncWithListener() {
539
+ // When paused, don't inject headers (pass null userId)
540
+ const shouldInject = this.state.isActive && !this.state.isPaused;
541
+ (0, _impersonateListener.setImpersonateConfig)({
542
+ headerKey: this.state.headerKey,
543
+ userId: shouldInject ? this.state.currentUser?.id ?? null : null,
544
+ ignorePatterns: this.state.ignorePatterns.map(p => new RegExp(p))
545
+ });
546
+
547
+ // Ensure listener is started
548
+ if (!(0, _impersonateListener.impersonateListener)().isListening) {
549
+ (0, _impersonateListener.impersonateListener)().startListening();
550
+ }
551
+ }
552
+
553
+ /**
554
+ * Persist state to storage
555
+ */
556
+ async persist() {
557
+ // Wait for storage to be ready (AsyncStorage setup on React Native)
558
+ if (this.storageReadyPromise) {
559
+ await this.storageReadyPromise;
560
+ }
561
+ const toStore = {
562
+ headerKey: this.state.headerKey,
563
+ ignorePatterns: this.state.ignorePatterns,
564
+ dataNukeSettings: this.state.dataNukeSettings,
565
+ showBanner: this.state.showBanner,
566
+ history: this.state.history,
567
+ // Persist active session for continuity across reloads
568
+ isActive: this.state.isActive,
569
+ isPaused: this.state.isPaused,
570
+ currentUser: this.state.currentUser
571
+ };
572
+ const serialized = JSON.stringify(toStore);
573
+ console.log("[ImpersonateStore] Persisting dataNukeSettings:", this.state.dataNukeSettings);
574
+ try {
575
+ // Prefer AsyncStorage for React Native
576
+ if (this.asyncStorageRef) {
577
+ await this.asyncStorageRef.setItem(STORAGE_KEY, serialized);
578
+ console.log("[ImpersonateStore] Persisted to AsyncStorage");
579
+ } else {
580
+ // Fall back to localStorage (web)
581
+ storage.setItem(STORAGE_KEY, serialized);
582
+ console.log("[ImpersonateStore] Persisted to localStorage");
583
+ }
584
+ } catch (e) {
585
+ console.log("[ImpersonateStore] Persist error:", e);
586
+ }
587
+ }
588
+
589
+ /**
590
+ * Notify all listeners of state change
591
+ */
592
+ notify() {
593
+ this.listeners.forEach(listener => {
594
+ try {
595
+ listener(this.state);
596
+ } catch {
597
+ // Ignore listener errors
598
+ }
599
+ });
600
+ }
601
+ }
602
+
603
+ // =============================================================================
604
+ // SINGLETON EXPORT
605
+ // =============================================================================
606
+
607
+ const impersonateStore = exports.impersonateStore = new ImpersonateStore();
@@ -0,0 +1,49 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ Object.defineProperty(exports, "getImpersonatedUserId", {
7
+ enumerable: true,
8
+ get: function () {
9
+ return _impersonateListener.getImpersonatedUserId;
10
+ }
11
+ });
12
+ Object.defineProperty(exports, "impersonateListener", {
13
+ enumerable: true,
14
+ get: function () {
15
+ return _impersonateListener.impersonateListener;
16
+ }
17
+ });
18
+ Object.defineProperty(exports, "impersonateStore", {
19
+ enumerable: true,
20
+ get: function () {
21
+ return _impersonateStore.impersonateStore;
22
+ }
23
+ });
24
+ Object.defineProperty(exports, "isImpersonating", {
25
+ enumerable: true,
26
+ get: function () {
27
+ return _impersonateListener.isImpersonating;
28
+ }
29
+ });
30
+ Object.defineProperty(exports, "setImpersonateConfig", {
31
+ enumerable: true,
32
+ get: function () {
33
+ return _impersonateListener.setImpersonateConfig;
34
+ }
35
+ });
36
+ Object.defineProperty(exports, "startImpersonateListener", {
37
+ enumerable: true,
38
+ get: function () {
39
+ return _impersonateListener.startImpersonateListener;
40
+ }
41
+ });
42
+ Object.defineProperty(exports, "stopImpersonateListener", {
43
+ enumerable: true,
44
+ get: function () {
45
+ return _impersonateListener.stopImpersonateListener;
46
+ }
47
+ });
48
+ var _impersonateListener = require("./impersonateListener");
49
+ var _impersonateStore = require("./impersonateStore");