@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.
- package/LICENSE +58 -0
- package/lib/commonjs/impersonate/components/DataNukeSettings.js +715 -0
- package/lib/commonjs/impersonate/components/ImpersonateBanner.js +217 -0
- package/lib/commonjs/impersonate/components/ImpersonateHistoryList.js +173 -0
- package/lib/commonjs/impersonate/components/ImpersonateModal.js +304 -0
- package/lib/commonjs/impersonate/components/ImpersonateStatusBar.js +130 -0
- package/lib/commonjs/impersonate/components/UserAvatar.js +146 -0
- package/lib/commonjs/impersonate/components/UserCard.js +200 -0
- package/lib/commonjs/impersonate/components/UserSearchView.js +227 -0
- package/lib/commonjs/impersonate/components/index.js +85 -0
- package/lib/commonjs/impersonate/hooks/index.js +64 -0
- package/lib/commonjs/impersonate/hooks/useAutoClearAsyncStorage.js +144 -0
- package/lib/commonjs/impersonate/hooks/useAutoClearReactQuery.js +155 -0
- package/lib/commonjs/impersonate/hooks/useAutoClearRedux.js +188 -0
- package/lib/commonjs/impersonate/hooks/useImpersonate.js +215 -0
- package/lib/commonjs/impersonate/hooks/useImpersonateHistory.js +56 -0
- package/lib/commonjs/impersonate/index.js +49 -0
- package/lib/commonjs/impersonate/types/index.js +16 -0
- package/lib/commonjs/impersonate/types/types.js +1 -0
- package/lib/commonjs/impersonate/utils/impersonateListener.js +280 -0
- package/lib/commonjs/impersonate/utils/impersonateStore.js +607 -0
- package/lib/commonjs/impersonate/utils/index.js +49 -0
- package/lib/commonjs/index.js +118 -0
- package/lib/commonjs/package.json +1 -0
- package/lib/commonjs/preset.js +214 -0
- package/lib/module/impersonate/components/DataNukeSettings.js +710 -0
- package/lib/module/impersonate/components/ImpersonateBanner.js +211 -0
- package/lib/module/impersonate/components/ImpersonateHistoryList.js +168 -0
- package/lib/module/impersonate/components/ImpersonateModal.js +300 -0
- package/lib/module/impersonate/components/ImpersonateStatusBar.js +125 -0
- package/lib/module/impersonate/components/UserAvatar.js +140 -0
- package/lib/module/impersonate/components/UserCard.js +195 -0
- package/lib/module/impersonate/components/UserSearchView.js +222 -0
- package/lib/module/impersonate/components/index.js +11 -0
- package/lib/module/impersonate/hooks/index.js +7 -0
- package/lib/module/impersonate/hooks/useAutoClearAsyncStorage.js +140 -0
- package/lib/module/impersonate/hooks/useAutoClearReactQuery.js +151 -0
- package/lib/module/impersonate/hooks/useAutoClearRedux.js +183 -0
- package/lib/module/impersonate/hooks/useImpersonate.js +212 -0
- package/lib/module/impersonate/hooks/useImpersonateHistory.js +52 -0
- package/lib/module/impersonate/index.js +13 -0
- package/lib/module/impersonate/types/index.js +3 -0
- package/lib/module/impersonate/types/types.js +1 -0
- package/lib/module/impersonate/utils/impersonateListener.js +271 -0
- package/lib/module/impersonate/utils/impersonateStore.js +604 -0
- package/lib/module/impersonate/utils/index.js +4 -0
- package/lib/module/index.js +103 -0
- package/lib/module/preset.js +209 -0
- package/lib/typescript/impersonate/components/DataNukeSettings.d.ts +37 -0
- package/lib/typescript/impersonate/components/DataNukeSettings.d.ts.map +1 -0
- package/lib/typescript/impersonate/components/ImpersonateBanner.d.ts +40 -0
- package/lib/typescript/impersonate/components/ImpersonateBanner.d.ts.map +1 -0
- package/lib/typescript/impersonate/components/ImpersonateHistoryList.d.ts +24 -0
- package/lib/typescript/impersonate/components/ImpersonateHistoryList.d.ts.map +1 -0
- package/lib/typescript/impersonate/components/ImpersonateModal.d.ts +10 -0
- package/lib/typescript/impersonate/components/ImpersonateModal.d.ts.map +1 -0
- package/lib/typescript/impersonate/components/ImpersonateStatusBar.d.ts +15 -0
- package/lib/typescript/impersonate/components/ImpersonateStatusBar.d.ts.map +1 -0
- package/lib/typescript/impersonate/components/UserAvatar.d.ts +32 -0
- package/lib/typescript/impersonate/components/UserAvatar.d.ts.map +1 -0
- package/lib/typescript/impersonate/components/UserCard.d.ts +28 -0
- package/lib/typescript/impersonate/components/UserCard.d.ts.map +1 -0
- package/lib/typescript/impersonate/components/UserSearchView.d.ts +31 -0
- package/lib/typescript/impersonate/components/UserSearchView.d.ts.map +1 -0
- package/lib/typescript/impersonate/components/index.d.ts +16 -0
- package/lib/typescript/impersonate/components/index.d.ts.map +1 -0
- package/lib/typescript/impersonate/hooks/index.d.ts +11 -0
- package/lib/typescript/impersonate/hooks/index.d.ts.map +1 -0
- package/lib/typescript/impersonate/hooks/useAutoClearAsyncStorage.d.ts +48 -0
- package/lib/typescript/impersonate/hooks/useAutoClearAsyncStorage.d.ts.map +1 -0
- package/lib/typescript/impersonate/hooks/useAutoClearReactQuery.d.ts +48 -0
- package/lib/typescript/impersonate/hooks/useAutoClearReactQuery.d.ts.map +1 -0
- package/lib/typescript/impersonate/hooks/useAutoClearRedux.d.ts +78 -0
- package/lib/typescript/impersonate/hooks/useAutoClearRedux.d.ts.map +1 -0
- package/lib/typescript/impersonate/hooks/useImpersonate.d.ts +76 -0
- package/lib/typescript/impersonate/hooks/useImpersonate.d.ts.map +1 -0
- package/lib/typescript/impersonate/hooks/useImpersonateHistory.d.ts +43 -0
- package/lib/typescript/impersonate/hooks/useImpersonateHistory.d.ts.map +1 -0
- package/lib/typescript/impersonate/index.d.ts +5 -0
- package/lib/typescript/impersonate/index.d.ts.map +1 -0
- package/lib/typescript/impersonate/types/index.d.ts +2 -0
- package/lib/typescript/impersonate/types/index.d.ts.map +1 -0
- package/lib/typescript/impersonate/types/types.d.ts +177 -0
- package/lib/typescript/impersonate/types/types.d.ts.map +1 -0
- package/lib/typescript/impersonate/utils/impersonateListener.d.ts +115 -0
- package/lib/typescript/impersonate/utils/impersonateListener.d.ts.map +1 -0
- package/lib/typescript/impersonate/utils/impersonateStore.d.ts +151 -0
- package/lib/typescript/impersonate/utils/impersonateStore.d.ts.map +1 -0
- package/lib/typescript/impersonate/utils/index.d.ts +3 -0
- package/lib/typescript/impersonate/utils/index.d.ts.map +1 -0
- package/lib/typescript/index.d.ts +80 -0
- package/lib/typescript/index.d.ts.map +1 -0
- package/lib/typescript/preset.d.ts +71 -0
- package/lib/typescript/preset.d.ts.map +1 -0
- 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");
|