oblien 1.0.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.
@@ -0,0 +1,349 @@
1
+ /**
2
+ * Guest Manager
3
+ * Manages guest users based on IP addresses or custom identifiers
4
+ */
5
+
6
+ import crypto from 'crypto';
7
+ import NodeCache from 'node-cache';
8
+
9
+ export class GuestManager {
10
+ /**
11
+ * @param {Object} options - Configuration options
12
+ * @param {Object} [options.storage] - Custom storage adapter (must implement get, set, delete)
13
+ * @param {number} [options.ttl] - Time to live for guest sessions in seconds (default: 24 hours)
14
+ * @param {Function} [options.onGuestCreated] - Callback when a new guest is created
15
+ */
16
+ constructor(options = {}) {
17
+ this.storage = options.storage || new NodeCacheStorage(options.ttl);
18
+ this.ttl = options.ttl || 24 * 60 * 60; // 24 hours
19
+ this.onGuestCreated = options.onGuestCreated;
20
+ }
21
+
22
+ /**
23
+ * Generate guest ID from IP address
24
+ * @param {string} ip - IP address
25
+ * @returns {string} Guest ID
26
+ */
27
+ generateGuestId(ip) {
28
+ // Hash IP to create consistent guest ID
29
+ const hash = crypto.createHash('sha256').update(ip).digest('hex');
30
+ return `guest_${hash.substring(0, 16)}`;
31
+ }
32
+
33
+ /**
34
+ * Get or create guest user by IP
35
+ * @param {string} ip - IP address
36
+ * @param {Object} [metadata] - Additional metadata to store
37
+ * @returns {Promise<Object>} Guest user object
38
+ */
39
+ async getOrCreateGuest(ip, metadata = {}) {
40
+ const guestId = this.generateGuestId(ip);
41
+
42
+ // Try to get existing guest
43
+ let guest = await this.storage.get(`guest:${guestId}`);
44
+
45
+ if (guest) {
46
+ // Update last seen
47
+ guest.lastSeen = new Date().toISOString();
48
+ await this.storage.set(`guest:${guestId}`, guest, this.ttl);
49
+ return guest;
50
+ }
51
+
52
+ // Create new guest
53
+ guest = {
54
+ id: guestId,
55
+ namespace: guestId, // For rate limiting
56
+ ip: this._maskIP(ip), // Store masked IP for privacy
57
+ isGuest: true,
58
+ createdAt: new Date().toISOString(),
59
+ lastSeen: new Date().toISOString(),
60
+ metadata,
61
+ sessions: [],
62
+ };
63
+
64
+ await this.storage.set(`guest:${guestId}`, guest, this.ttl);
65
+
66
+ // Call callback if provided
67
+ if (this.onGuestCreated) {
68
+ this.onGuestCreated(guest);
69
+ }
70
+
71
+ return guest;
72
+ }
73
+
74
+ /**
75
+ * Get guest by ID
76
+ * @param {string} guestId - Guest ID
77
+ * @returns {Promise<Object|null>} Guest object or null
78
+ */
79
+ async getGuest(guestId) {
80
+ return await this.storage.get(`guest:${guestId}`);
81
+ }
82
+
83
+ /**
84
+ * Update guest metadata
85
+ * @param {string} guestId - Guest ID
86
+ * @param {Object} updates - Fields to update
87
+ * @returns {Promise<Object>} Updated guest object
88
+ */
89
+ async updateGuest(guestId, updates) {
90
+ const guest = await this.storage.get(`guest:${guestId}`);
91
+
92
+ if (!guest) {
93
+ throw new Error(`Guest not found: ${guestId}`);
94
+ }
95
+
96
+ const updated = {
97
+ ...guest,
98
+ ...updates,
99
+ lastSeen: new Date().toISOString(),
100
+ };
101
+
102
+ await this.storage.set(`guest:${guestId}`, updated, this.ttl);
103
+ return updated;
104
+ }
105
+
106
+ /**
107
+ * Add session to guest
108
+ * @param {string} guestId - Guest ID
109
+ * @param {string} sessionId - Session ID
110
+ * @returns {Promise<Object>} Updated guest object
111
+ */
112
+ async addSession(guestId, sessionId) {
113
+ const guest = await this.storage.get(`guest:${guestId}`);
114
+
115
+ if (!guest) {
116
+ throw new Error(`Guest not found: ${guestId}`);
117
+ }
118
+
119
+ if (!guest.sessions.includes(sessionId)) {
120
+ guest.sessions.push(sessionId);
121
+ }
122
+
123
+ guest.lastSeen = new Date().toISOString();
124
+ await this.storage.set(`guest:${guestId}`, guest, this.ttl);
125
+
126
+ return guest;
127
+ }
128
+
129
+ /**
130
+ * Delete guest
131
+ * @param {string} guestId - Guest ID
132
+ * @returns {Promise<boolean>} Success status
133
+ */
134
+ async deleteGuest(guestId) {
135
+ return await this.storage.delete(`guest:${guestId}`);
136
+ }
137
+
138
+ /**
139
+ * Mask IP address for privacy (keep first 2 octets)
140
+ * @private
141
+ */
142
+ _maskIP(ip) {
143
+ if (!ip) return 'unknown';
144
+
145
+ // IPv4
146
+ if (ip.includes('.')) {
147
+ const parts = ip.split('.');
148
+ return `${parts[0]}.${parts[1]}.xxx.xxx`;
149
+ }
150
+
151
+ // IPv6 - keep first 4 groups
152
+ if (ip.includes(':')) {
153
+ const parts = ip.split(':');
154
+ return `${parts.slice(0, 4).join(':')}:xxxx:xxxx:xxxx:xxxx`;
155
+ }
156
+
157
+ return 'unknown';
158
+ }
159
+
160
+ /**
161
+ * Get all active guests (for admin/monitoring)
162
+ * @returns {Promise<Array>} Array of guest objects
163
+ */
164
+ async getAllGuests() {
165
+ return await this.storage.getAll('guest:*');
166
+ }
167
+
168
+ /**
169
+ * Clean up expired guests
170
+ * @returns {Promise<number>} Number of cleaned guests
171
+ */
172
+ async cleanup() {
173
+ const guests = await this.getAllGuests();
174
+ const now = Date.now();
175
+ let cleaned = 0;
176
+
177
+ for (const guest of guests) {
178
+ const lastSeen = new Date(guest.lastSeen).getTime();
179
+ const age = now - lastSeen;
180
+
181
+ if (age > this.ttl * 1000) {
182
+ await this.deleteGuest(guest.id);
183
+ cleaned++;
184
+ }
185
+ }
186
+
187
+ return cleaned;
188
+ }
189
+ }
190
+
191
+ /**
192
+ * NodeCache Storage Adapter (Default)
193
+ * Uses node-cache for automatic expiration and memory management
194
+ */
195
+ class NodeCacheStorage {
196
+ constructor(ttl = 24 * 60 * 60) {
197
+ this.cache = new NodeCache({
198
+ stdTTL: ttl,
199
+ checkperiod: 600, // Check for expired keys every 10 minutes
200
+ useClones: false, // Better performance
201
+ maxKeys: 100000, // Limit memory usage
202
+ });
203
+ }
204
+
205
+ async get(key) {
206
+ return this.cache.get(key) || null;
207
+ }
208
+
209
+ async set(key, value, ttl) {
210
+ return this.cache.set(key, value, ttl || undefined);
211
+ }
212
+
213
+ async delete(key) {
214
+ return this.cache.del(key) > 0;
215
+ }
216
+
217
+ async getAll(pattern) {
218
+ const prefix = pattern.replace('*', '');
219
+ const keys = this.cache.keys();
220
+ const results = [];
221
+
222
+ for (const key of keys) {
223
+ if (key.startsWith(prefix)) {
224
+ const value = this.cache.get(key);
225
+ if (value) results.push(value);
226
+ }
227
+ }
228
+
229
+ return results;
230
+ }
231
+
232
+ /**
233
+ * Get cache statistics
234
+ */
235
+ getStats() {
236
+ return this.cache.getStats();
237
+ }
238
+
239
+ /**
240
+ * Clear all cache
241
+ */
242
+ clear() {
243
+ this.cache.flushAll();
244
+ }
245
+ }
246
+
247
+ /**
248
+ * Simple In-Memory Storage (Fallback)
249
+ * Use NodeCacheStorage or RedisStorage for production
250
+ */
251
+ export class InMemoryStorage {
252
+ constructor() {
253
+ this.store = new Map();
254
+ this.expirations = new Map();
255
+ }
256
+
257
+ async get(key) {
258
+ // Check if expired
259
+ const expiry = this.expirations.get(key);
260
+ if (expiry && Date.now() > expiry) {
261
+ this.store.delete(key);
262
+ this.expirations.delete(key);
263
+ return null;
264
+ }
265
+
266
+ return this.store.get(key) || null;
267
+ }
268
+
269
+ async set(key, value, ttl) {
270
+ this.store.set(key, value);
271
+
272
+ if (ttl) {
273
+ this.expirations.set(key, Date.now() + (ttl * 1000));
274
+ }
275
+
276
+ return true;
277
+ }
278
+
279
+ async delete(key) {
280
+ this.expirations.delete(key);
281
+ return this.store.delete(key);
282
+ }
283
+
284
+ async getAll(pattern) {
285
+ const results = [];
286
+ const prefix = pattern.replace('*', '');
287
+
288
+ for (const [key, value] of this.store.entries()) {
289
+ if (key.startsWith(prefix)) {
290
+ // Check expiration
291
+ const expiry = this.expirations.get(key);
292
+ if (!expiry || Date.now() <= expiry) {
293
+ results.push(value);
294
+ }
295
+ }
296
+ }
297
+
298
+ return results;
299
+ }
300
+ }
301
+
302
+ /**
303
+ * Redis Storage Adapter
304
+ * For production use with Redis
305
+ * Requires 'redis' package: npm install redis
306
+ */
307
+ export class RedisStorage {
308
+ constructor(redisClient) {
309
+ if (!redisClient) {
310
+ throw new Error('Redis client is required');
311
+ }
312
+ this.redis = redisClient;
313
+ }
314
+
315
+ async get(key) {
316
+ const data = await this.redis.get(key);
317
+ return data ? JSON.parse(data) : null;
318
+ }
319
+
320
+ async set(key, value, ttl) {
321
+ const data = JSON.stringify(value);
322
+ if (ttl) {
323
+ await this.redis.setEx(key, ttl, data);
324
+ } else {
325
+ await this.redis.set(key, data);
326
+ }
327
+ return true;
328
+ }
329
+
330
+ async delete(key) {
331
+ await this.redis.del(key);
332
+ return true;
333
+ }
334
+
335
+ async getAll(pattern) {
336
+ const keys = await this.redis.keys(pattern);
337
+ const results = [];
338
+
339
+ for (const key of keys) {
340
+ const data = await this.get(key);
341
+ if (data) results.push(data);
342
+ }
343
+
344
+ return results;
345
+ }
346
+ }
347
+
348
+ export { NodeCacheStorage };
349
+ export default GuestManager;