oblien 1.3.0 → 2.0.1
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/README.md +482 -422
- package/dist/client.d.ts +31 -0
- package/dist/client.d.ts.map +1 -0
- package/dist/client.js +33 -0
- package/dist/client.js.map +1 -0
- package/dist/error.d.ts +29 -0
- package/dist/error.d.ts.map +1 -0
- package/dist/error.js +52 -0
- package/dist/error.js.map +1 -0
- package/dist/http.d.ts +20 -0
- package/dist/http.d.ts.map +1 -0
- package/dist/http.js +108 -0
- package/dist/http.js.map +1 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +10 -0
- package/dist/index.js.map +1 -0
- package/dist/resources/api-access.d.ts +21 -0
- package/dist/resources/api-access.d.ts.map +1 -0
- package/dist/resources/api-access.js +32 -0
- package/dist/resources/api-access.js.map +1 -0
- package/dist/resources/base.d.ts +17 -0
- package/dist/resources/base.d.ts.map +1 -0
- package/dist/resources/base.js +21 -0
- package/dist/resources/base.js.map +1 -0
- package/dist/resources/images.d.ts +11 -0
- package/dist/resources/images.d.ts.map +1 -0
- package/dist/resources/images.js +16 -0
- package/dist/resources/images.js.map +1 -0
- package/dist/resources/lifecycle.d.ts +23 -0
- package/dist/resources/lifecycle.d.ts.map +1 -0
- package/dist/resources/lifecycle.js +32 -0
- package/dist/resources/lifecycle.js.map +1 -0
- package/dist/resources/logs.d.ts +25 -0
- package/dist/resources/logs.d.ts.map +1 -0
- package/dist/resources/logs.js +51 -0
- package/dist/resources/logs.js.map +1 -0
- package/dist/resources/metadata.d.ts +15 -0
- package/dist/resources/metadata.d.ts.map +1 -0
- package/dist/resources/metadata.js +20 -0
- package/dist/resources/metadata.js.map +1 -0
- package/dist/resources/metrics.d.ts +17 -0
- package/dist/resources/metrics.d.ts.map +1 -0
- package/dist/resources/metrics.js +27 -0
- package/dist/resources/metrics.js.map +1 -0
- package/dist/resources/network.d.ts +17 -0
- package/dist/resources/network.d.ts.map +1 -0
- package/dist/resources/network.js +20 -0
- package/dist/resources/network.js.map +1 -0
- package/dist/resources/public-access.d.ts +15 -0
- package/dist/resources/public-access.d.ts.map +1 -0
- package/dist/resources/public-access.js +21 -0
- package/dist/resources/public-access.js.map +1 -0
- package/dist/resources/resources.d.ts +15 -0
- package/dist/resources/resources.d.ts.map +1 -0
- package/dist/resources/resources.js +20 -0
- package/dist/resources/resources.js.map +1 -0
- package/dist/resources/snapshots.d.ts +27 -0
- package/dist/resources/snapshots.d.ts.map +1 -0
- package/dist/resources/snapshots.js +45 -0
- package/dist/resources/snapshots.js.map +1 -0
- package/dist/resources/ssh.d.ts +19 -0
- package/dist/resources/ssh.d.ts.map +1 -0
- package/dist/resources/ssh.js +28 -0
- package/dist/resources/ssh.js.map +1 -0
- package/dist/resources/usage.d.ts +25 -0
- package/dist/resources/usage.d.ts.map +1 -0
- package/dist/resources/usage.js +44 -0
- package/dist/resources/usage.js.map +1 -0
- package/dist/resources/workloads.d.ts +39 -0
- package/dist/resources/workloads.d.ts.map +1 -0
- package/dist/resources/workloads.js +83 -0
- package/dist/resources/workloads.js.map +1 -0
- package/dist/runtime/exec.d.ts +71 -0
- package/dist/runtime/exec.d.ts.map +1 -0
- package/dist/runtime/exec.js +163 -0
- package/dist/runtime/exec.js.map +1 -0
- package/dist/runtime/files.d.ts +39 -0
- package/dist/runtime/files.d.ts.map +1 -0
- package/dist/runtime/files.js +143 -0
- package/dist/runtime/files.js.map +1 -0
- package/dist/runtime/search.d.ts +23 -0
- package/dist/runtime/search.d.ts.map +1 -0
- package/dist/runtime/search.js +65 -0
- package/dist/runtime/search.js.map +1 -0
- package/dist/runtime/terminal.d.ts +29 -0
- package/dist/runtime/terminal.d.ts.map +1 -0
- package/dist/runtime/terminal.js +58 -0
- package/dist/runtime/terminal.js.map +1 -0
- package/dist/runtime/watcher.d.ts +27 -0
- package/dist/runtime/watcher.d.ts.map +1 -0
- package/dist/runtime/watcher.js +53 -0
- package/dist/runtime/watcher.js.map +1 -0
- package/dist/runtime/ws.d.ts +92 -0
- package/dist/runtime/ws.d.ts.map +1 -0
- package/dist/runtime/ws.js +228 -0
- package/dist/runtime/ws.js.map +1 -0
- package/dist/runtime-http.d.ts +35 -0
- package/dist/runtime-http.d.ts.map +1 -0
- package/dist/runtime-http.js +99 -0
- package/dist/runtime-http.js.map +1 -0
- package/dist/runtime.d.ts +77 -0
- package/dist/runtime.d.ts.map +1 -0
- package/dist/runtime.js +97 -0
- package/dist/runtime.js.map +1 -0
- package/dist/types/client.d.ts +7 -0
- package/dist/types/client.d.ts.map +1 -0
- package/dist/types/client.js +3 -0
- package/dist/types/client.js.map +1 -0
- package/dist/types/common.d.ts +13 -0
- package/dist/types/common.d.ts.map +1 -0
- package/dist/types/common.js +3 -0
- package/dist/types/common.js.map +1 -0
- package/dist/types/index.d.ts +8 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +2 -0
- package/dist/types/index.js.map +1 -0
- package/dist/types/network.d.ts +15 -0
- package/dist/types/network.d.ts.map +1 -0
- package/dist/types/network.js +3 -0
- package/dist/types/network.js.map +1 -0
- package/dist/types/resources.d.ts +10 -0
- package/dist/types/resources.d.ts.map +1 -0
- package/dist/types/resources.js +3 -0
- package/dist/types/resources.js.map +1 -0
- package/dist/types/runtime.d.ts +302 -0
- package/dist/types/runtime.d.ts.map +1 -0
- package/dist/types/runtime.js +3 -0
- package/dist/types/runtime.js.map +1 -0
- package/dist/types/workspace-resources.d.ts +186 -0
- package/dist/types/workspace-resources.d.ts.map +1 -0
- package/dist/types/workspace-resources.js +3 -0
- package/dist/types/workspace-resources.js.map +1 -0
- package/dist/types/workspace.d.ts +41 -0
- package/dist/types/workspace.d.ts.map +1 -0
- package/dist/types/workspace.js +3 -0
- package/dist/types/workspace.js.map +1 -0
- package/dist/workspace.d.ts +135 -0
- package/dist/workspace.d.ts.map +1 -0
- package/dist/workspace.js +194 -0
- package/dist/workspace.js.map +1 -0
- package/package.json +30 -69
- package/LICENSE +0 -21
- package/agents.js +0 -14
- package/browser.js +0 -6
- package/cdn.js +0 -6
- package/chat.js +0 -21
- package/credits.js +0 -11
- package/icons.js +0 -11
- package/index.d.ts +0 -986
- package/index.js +0 -63
- package/namespaces.js +0 -12
- package/sandbox.js +0 -12
- package/search.js +0 -11
- package/src/agents/agent.js +0 -229
- package/src/agents/index.js +0 -227
- package/src/agents/settings.js +0 -100
- package/src/agents/tools.js +0 -155
- package/src/browser/index.js +0 -474
- package/src/cdn/index.js +0 -769
- package/src/chat/index.js +0 -724
- package/src/chat/session.js +0 -93
- package/src/client.js +0 -175
- package/src/credits/index.js +0 -492
- package/src/icons/index.js +0 -185
- package/src/namespaces/index.js +0 -236
- package/src/namespaces/namespace.js +0 -274
- package/src/sandbox/core/api/base.js +0 -89
- package/src/sandbox/core/api/database.js +0 -340
- package/src/sandbox/core/api/files.js +0 -141
- package/src/sandbox/core/api/git.js +0 -174
- package/src/sandbox/core/api/search.js +0 -29
- package/src/sandbox/core/api/snapshots.js +0 -132
- package/src/sandbox/core/api/terminal.js +0 -20
- package/src/sandbox/core/auth.js +0 -256
- package/src/sandbox/core/client.js +0 -197
- package/src/sandbox/core/index.js +0 -22
- package/src/sandbox/core/managers/terminal.js +0 -453
- package/src/sandbox/core/managers/watcher.js +0 -197
- package/src/sandbox/core/types.js +0 -92
- package/src/sandbox/core/utils/http.js +0 -89
- package/src/sandbox/core/websocket/connection.js +0 -479
- package/src/sandbox/index.d.ts +0 -542
- package/src/sandbox/index.js +0 -234
- package/src/sandbox/sandbox.js +0 -313
- package/src/search/index.js +0 -206
- package/src/utils/guest-manager.js +0 -454
package/src/search/index.js
DELETED
|
@@ -1,206 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Search Module
|
|
3
|
-
* Web search, content extraction, and research crawling
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
import { OblienClient } from '../client.js';
|
|
7
|
-
|
|
8
|
-
export class OblienSearch {
|
|
9
|
-
/**
|
|
10
|
-
* @param {import('../client.js').OblienClient|Object} clientOrConfig - Oblien client instance or config
|
|
11
|
-
* @param {string} [clientOrConfig.clientId] - Client ID (if not using client instance)
|
|
12
|
-
* @param {string} [clientOrConfig.clientSecret] - Client Secret (if not using client instance)
|
|
13
|
-
* @param {string} [clientOrConfig.baseURL] - Base URL (optional)
|
|
14
|
-
*/
|
|
15
|
-
constructor(clientOrConfig) {
|
|
16
|
-
if (!clientOrConfig) {
|
|
17
|
-
throw new Error('Oblien client or credentials are required');
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
// If it's already a client instance
|
|
21
|
-
if (clientOrConfig.clientId && clientOrConfig.clientSecret && typeof clientOrConfig.get === 'function') {
|
|
22
|
-
this.client = clientOrConfig;
|
|
23
|
-
}
|
|
24
|
-
// If credentials provided, create client
|
|
25
|
-
else if (clientOrConfig.clientId && clientOrConfig.clientSecret) {
|
|
26
|
-
this.client = new OblienClient(clientOrConfig);
|
|
27
|
-
}
|
|
28
|
-
else {
|
|
29
|
-
throw new Error('Either provide OblienClient instance or { clientId, clientSecret }');
|
|
30
|
-
}
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
/**
|
|
34
|
-
* Search the web with multiple queries
|
|
35
|
-
* @param {Object} options - Search options
|
|
36
|
-
* @param {Array<string>} options.queries - Array of search queries (required)
|
|
37
|
-
* @param {boolean} [options.includeAnswers=false] - Include AI-generated answers
|
|
38
|
-
* @param {Object} [options.options] - Additional search options
|
|
39
|
-
* @param {number} [options.options.maxResults] - Max results per query
|
|
40
|
-
* @param {string} [options.options.region] - Search region
|
|
41
|
-
* @param {string} [options.options.timeRange] - Time range filter
|
|
42
|
-
* @returns {Promise<Array>} Search results
|
|
43
|
-
*/
|
|
44
|
-
async search(options) {
|
|
45
|
-
const { queries, includeAnswers = false, options: searchOptions = {} } = options;
|
|
46
|
-
|
|
47
|
-
if (!queries || !Array.isArray(queries) || queries.length === 0) {
|
|
48
|
-
throw new Error('Queries array is required and must not be empty');
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
const response = await this.client.post('search', {
|
|
52
|
-
queries,
|
|
53
|
-
includeAnswers,
|
|
54
|
-
options: searchOptions
|
|
55
|
-
});
|
|
56
|
-
|
|
57
|
-
return response;
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
/**
|
|
61
|
-
* Extract and summarize content from web pages
|
|
62
|
-
* @param {Object} options - Extract options
|
|
63
|
-
* @param {Array<Object>} options.pages - Array of pages to extract (required)
|
|
64
|
-
* @param {string} options.pages[].url - Page URL (required)
|
|
65
|
-
* @param {Array<string>} options.pages[].details - Details to extract (required)
|
|
66
|
-
* @param {string} [options.pages[].summaryLevel='medium'] - Summary level: 'brief' | 'medium' | 'detailed'
|
|
67
|
-
* @param {Object} [options.options] - Additional extraction options
|
|
68
|
-
* @returns {Promise<Object>} Extracted content
|
|
69
|
-
*/
|
|
70
|
-
async extract(options) {
|
|
71
|
-
const { pages, options: extractOptions = {} } = options;
|
|
72
|
-
|
|
73
|
-
if (!pages || !Array.isArray(pages) || pages.length === 0) {
|
|
74
|
-
throw new Error('Pages array is required and must not be empty');
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
// Validate pages
|
|
78
|
-
pages.forEach((page, index) => {
|
|
79
|
-
if (!page.url) {
|
|
80
|
-
throw new Error(`Page at index ${index} is missing 'url'`);
|
|
81
|
-
}
|
|
82
|
-
if (!page.details || !Array.isArray(page.details) || page.details.length === 0) {
|
|
83
|
-
throw new Error(`Page at index ${index} is missing 'details' array`);
|
|
84
|
-
}
|
|
85
|
-
});
|
|
86
|
-
|
|
87
|
-
const response = await this.client.post('search/extract', {
|
|
88
|
-
pages,
|
|
89
|
-
options: extractOptions
|
|
90
|
-
});
|
|
91
|
-
|
|
92
|
-
return response;
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
/**
|
|
96
|
-
* Create deep research report with AI crawling
|
|
97
|
-
* @param {Object} options - Crawl options
|
|
98
|
-
* @param {string} options.instructions - Research instructions (required)
|
|
99
|
-
* @param {Object} [options.options] - Additional crawl options
|
|
100
|
-
* @param {boolean} [options.options.thinking] - Enable thinking mode
|
|
101
|
-
* @param {boolean} [options.options.allow_thinking_callback] - Allow thinking callbacks
|
|
102
|
-
* @param {boolean} [options.options.stream_text] - Stream text responses
|
|
103
|
-
* @param {string} [options.reportType='pdf'] - Report type: 'pdf' | 'markdown' | 'html'
|
|
104
|
-
* @param {Function} [options.onProgress] - Callback for progress updates (streaming)
|
|
105
|
-
* @returns {Promise<Object>} Research report result
|
|
106
|
-
*/
|
|
107
|
-
async crawl(options) {
|
|
108
|
-
const {
|
|
109
|
-
instructions,
|
|
110
|
-
options: crawlOptions = {},
|
|
111
|
-
reportType = 'pdf',
|
|
112
|
-
onProgress = null
|
|
113
|
-
} = options;
|
|
114
|
-
|
|
115
|
-
if (!instructions || typeof instructions !== 'string') {
|
|
116
|
-
throw new Error('Instructions string is required');
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
// If onProgress is provided, we need to handle streaming
|
|
120
|
-
if (onProgress && typeof onProgress === 'function') {
|
|
121
|
-
return this._crawlWithStreaming({
|
|
122
|
-
instructions,
|
|
123
|
-
options: crawlOptions,
|
|
124
|
-
reportType,
|
|
125
|
-
onProgress
|
|
126
|
-
});
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
// Standard JSON response
|
|
130
|
-
const response = await this.client.post('search/crawl', {
|
|
131
|
-
instructions,
|
|
132
|
-
options: crawlOptions,
|
|
133
|
-
responseType: 'json',
|
|
134
|
-
reportType
|
|
135
|
-
});
|
|
136
|
-
|
|
137
|
-
return response;
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
/**
|
|
141
|
-
* Handle streaming crawl with progress callbacks
|
|
142
|
-
* @private
|
|
143
|
-
*/
|
|
144
|
-
async _crawlWithStreaming({ instructions, options, reportType, onProgress }) {
|
|
145
|
-
const url = this.client._buildURL('search/crawl');
|
|
146
|
-
const headers = this.client.getAuthHeaders();
|
|
147
|
-
|
|
148
|
-
const response = await fetch(url, {
|
|
149
|
-
method: 'POST',
|
|
150
|
-
headers,
|
|
151
|
-
body: JSON.stringify({
|
|
152
|
-
instructions,
|
|
153
|
-
options,
|
|
154
|
-
responseType: 'stream',
|
|
155
|
-
reportType
|
|
156
|
-
})
|
|
157
|
-
});
|
|
158
|
-
|
|
159
|
-
if (!response.ok) {
|
|
160
|
-
const error = await response.json();
|
|
161
|
-
throw new Error(error.message || error.error || 'Crawl request failed');
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
// Handle streaming response
|
|
165
|
-
const reader = response.body.getReader();
|
|
166
|
-
const decoder = new TextDecoder();
|
|
167
|
-
let buffer = '';
|
|
168
|
-
let finalResult = null;
|
|
169
|
-
|
|
170
|
-
while (true) {
|
|
171
|
-
const { done, value } = await reader.read();
|
|
172
|
-
|
|
173
|
-
if (done) break;
|
|
174
|
-
|
|
175
|
-
buffer += decoder.decode(value, { stream: true });
|
|
176
|
-
|
|
177
|
-
// Process complete SSE messages
|
|
178
|
-
const lines = buffer.split('\n\n');
|
|
179
|
-
buffer = lines.pop() || ''; // Keep incomplete message in buffer
|
|
180
|
-
|
|
181
|
-
for (const line of lines) {
|
|
182
|
-
if (line.startsWith('data: ')) {
|
|
183
|
-
try {
|
|
184
|
-
const data = JSON.parse(line.slice(6));
|
|
185
|
-
|
|
186
|
-
if (data.type === 'crawl_end') {
|
|
187
|
-
finalResult = data.data;
|
|
188
|
-
} else if (data.type === 'error') {
|
|
189
|
-
throw new Error(data.error);
|
|
190
|
-
} else {
|
|
191
|
-
// Progress update
|
|
192
|
-
onProgress(data);
|
|
193
|
-
}
|
|
194
|
-
} catch (error) {
|
|
195
|
-
console.error('Error parsing SSE data:', error);
|
|
196
|
-
}
|
|
197
|
-
}
|
|
198
|
-
}
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
return finalResult || { success: true };
|
|
202
|
-
}
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
export default OblienSearch;
|
|
206
|
-
|
|
@@ -1,454 +0,0 @@
|
|
|
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
|
-
* Find existing guest by fingerprint or IP (dual-layer identification)
|
|
35
|
-
* @param {string} fingerprint - Client fingerprint
|
|
36
|
-
* @param {string} ip - IP address
|
|
37
|
-
* @returns {Promise<Object|null>} Guest object or null
|
|
38
|
-
*/
|
|
39
|
-
async findExistingGuest(fingerprint, ip) {
|
|
40
|
-
// Layer 1: Try to find by fingerprint first
|
|
41
|
-
const guestIdByFingerprint = await this.storage.get(`fingerprint:${fingerprint}`);
|
|
42
|
-
if (guestIdByFingerprint) {
|
|
43
|
-
const guest = await this.getGuest(guestIdByFingerprint);
|
|
44
|
-
if (guest) {
|
|
45
|
-
// Update IP mapping if it changed
|
|
46
|
-
if (guest.metadata?.ip !== ip) {
|
|
47
|
-
await this._updateMappings(guest.id, fingerprint, ip);
|
|
48
|
-
await this.updateGuest(guest.id, {
|
|
49
|
-
ip: this._maskIP(ip),
|
|
50
|
-
metadata: {
|
|
51
|
-
...guest.metadata,
|
|
52
|
-
ip,
|
|
53
|
-
previousIps: [...(guest.metadata?.previousIps || []), guest.metadata?.ip].filter(Boolean),
|
|
54
|
-
},
|
|
55
|
-
});
|
|
56
|
-
}
|
|
57
|
-
return guest;
|
|
58
|
-
}
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
// Layer 2: Fallback to IP if fingerprint didn't match
|
|
62
|
-
const guestIdByIp = await this.storage.get(`ip:${ip}`);
|
|
63
|
-
if (guestIdByIp) {
|
|
64
|
-
const guest = await this.getGuest(guestIdByIp);
|
|
65
|
-
if (guest) {
|
|
66
|
-
// Update fingerprint mapping
|
|
67
|
-
await this._updateMappings(guest.id, fingerprint, ip);
|
|
68
|
-
await this.updateGuest(guest.id, {
|
|
69
|
-
metadata: {
|
|
70
|
-
...guest.metadata,
|
|
71
|
-
fingerprint,
|
|
72
|
-
previousFingerprints: [...(guest.metadata?.previousFingerprints || []), guest.metadata?.fingerprint].filter(Boolean),
|
|
73
|
-
},
|
|
74
|
-
});
|
|
75
|
-
return guest;
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
return null;
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
/**
|
|
83
|
-
* Update fingerprint and IP mappings
|
|
84
|
-
* @private
|
|
85
|
-
*/
|
|
86
|
-
async _updateMappings(guestId, fingerprint, ip) {
|
|
87
|
-
await this.storage.set(`fingerprint:${fingerprint}`, guestId, this.ttl);
|
|
88
|
-
await this.storage.set(`ip:${ip}`, guestId, this.ttl);
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
/**
|
|
92
|
-
* Get or create guest user by IP and fingerprint (dual-layer identification)
|
|
93
|
-
* Supports both old signature: getOrCreateGuest(ip, metadata)
|
|
94
|
-
* and new signature: getOrCreateGuest(ip, fingerprint, metadata)
|
|
95
|
-
* @param {string} ip - IP address
|
|
96
|
-
* @param {string|Object} fingerprintOrMetadata - Client fingerprint (string) or metadata (object)
|
|
97
|
-
* @param {Object} [metadata] - Additional metadata to store (only if fingerprint is provided)
|
|
98
|
-
* @returns {Promise<Object>} Guest user object
|
|
99
|
-
*/
|
|
100
|
-
async getOrCreateGuest(ip, fingerprintOrMetadata = null, metadata = {}) {
|
|
101
|
-
// Handle backward compatibility: detect if second param is metadata (object) or fingerprint (string)
|
|
102
|
-
let fingerprint = null;
|
|
103
|
-
let finalMetadata = {};
|
|
104
|
-
|
|
105
|
-
if (fingerprintOrMetadata === null || fingerprintOrMetadata === undefined) {
|
|
106
|
-
// No second parameter
|
|
107
|
-
finalMetadata = metadata || {};
|
|
108
|
-
} else if (typeof fingerprintOrMetadata === 'string') {
|
|
109
|
-
// New signature: (ip, fingerprint, metadata)
|
|
110
|
-
fingerprint = fingerprintOrMetadata;
|
|
111
|
-
finalMetadata = metadata || {};
|
|
112
|
-
} else if (typeof fingerprintOrMetadata === 'object') {
|
|
113
|
-
// Old signature: (ip, metadata) - fingerprint is in metadata
|
|
114
|
-
finalMetadata = fingerprintOrMetadata;
|
|
115
|
-
fingerprint = finalMetadata.fingerprint || null;
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
// Try to find existing guest by fingerprint or IP
|
|
119
|
-
if (fingerprint) {
|
|
120
|
-
const existingGuest = await this.findExistingGuest(fingerprint, ip);
|
|
121
|
-
if (existingGuest) {
|
|
122
|
-
// Update last seen
|
|
123
|
-
existingGuest.lastSeen = new Date().toISOString();
|
|
124
|
-
await this.storage.set(`guest:${existingGuest.id}`, existingGuest, this.ttl);
|
|
125
|
-
return existingGuest;
|
|
126
|
-
}
|
|
127
|
-
} else {
|
|
128
|
-
// Try to find by IP only
|
|
129
|
-
const guestIdByIp = await this.storage.get(`ip:${ip}`);
|
|
130
|
-
if (guestIdByIp) {
|
|
131
|
-
const guest = await this.getGuest(guestIdByIp);
|
|
132
|
-
if (guest) {
|
|
133
|
-
guest.lastSeen = new Date().toISOString();
|
|
134
|
-
await this.storage.set(`guest:${guest.id}`, guest, this.ttl);
|
|
135
|
-
return guest;
|
|
136
|
-
}
|
|
137
|
-
}
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
// Create new guest
|
|
141
|
-
const guestId = this.generateGuestId(ip);
|
|
142
|
-
const guest = {
|
|
143
|
-
id: guestId,
|
|
144
|
-
namespace: guestId, // For rate limiting
|
|
145
|
-
ip: this._maskIP(ip), // Store masked IP for privacy
|
|
146
|
-
isGuest: true,
|
|
147
|
-
createdAt: new Date().toISOString(),
|
|
148
|
-
lastSeen: new Date().toISOString(),
|
|
149
|
-
metadata: {
|
|
150
|
-
...finalMetadata,
|
|
151
|
-
ip,
|
|
152
|
-
fingerprint,
|
|
153
|
-
},
|
|
154
|
-
sessions: [],
|
|
155
|
-
};
|
|
156
|
-
|
|
157
|
-
await this.storage.set(`guest:${guestId}`, guest, this.ttl);
|
|
158
|
-
|
|
159
|
-
// Store mappings
|
|
160
|
-
if (fingerprint) {
|
|
161
|
-
await this._updateMappings(guestId, fingerprint, ip);
|
|
162
|
-
} else {
|
|
163
|
-
// Fallback: just store IP mapping if no fingerprint
|
|
164
|
-
await this.storage.set(`ip:${ip}`, guestId, this.ttl);
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
// Call callback if provided
|
|
168
|
-
if (this.onGuestCreated) {
|
|
169
|
-
this.onGuestCreated(guest);
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
return guest;
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
/**
|
|
176
|
-
* Get guest by ID
|
|
177
|
-
* @param {string} guestId - Guest ID
|
|
178
|
-
* @returns {Promise<Object|null>} Guest object or null
|
|
179
|
-
*/
|
|
180
|
-
async getGuest(guestId) {
|
|
181
|
-
try {
|
|
182
|
-
return await this.storage.get(`guest:${guestId}`);
|
|
183
|
-
} catch (error) {
|
|
184
|
-
return null;
|
|
185
|
-
}
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
/**
|
|
189
|
-
* Update guest metadata
|
|
190
|
-
* @param {string} guestId - Guest ID
|
|
191
|
-
* @param {Object} updates - Fields to update
|
|
192
|
-
* @returns {Promise<Object>} Updated guest object
|
|
193
|
-
*/
|
|
194
|
-
async updateGuest(guestId, updates) {
|
|
195
|
-
const guest = await this.storage.get(`guest:${guestId}`);
|
|
196
|
-
|
|
197
|
-
if (!guest) {
|
|
198
|
-
throw new Error(`Guest not found: ${guestId}`);
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
const updated = {
|
|
202
|
-
...guest,
|
|
203
|
-
...updates,
|
|
204
|
-
lastSeen: new Date().toISOString(),
|
|
205
|
-
};
|
|
206
|
-
|
|
207
|
-
await this.storage.set(`guest:${guestId}`, updated, this.ttl);
|
|
208
|
-
return updated;
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
/**
|
|
212
|
-
* Add session to guest
|
|
213
|
-
* @param {string} guestId - Guest ID
|
|
214
|
-
* @param {string} sessionId - Session ID
|
|
215
|
-
* @returns {Promise<Object>} Updated guest object
|
|
216
|
-
*/
|
|
217
|
-
async addSession(guestId, sessionId) {
|
|
218
|
-
const guest = await this.storage.get(`guest:${guestId}`);
|
|
219
|
-
|
|
220
|
-
if (!guest) {
|
|
221
|
-
throw new Error(`Guest not found: ${guestId}`);
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
if (!guest.sessions.includes(sessionId)) {
|
|
225
|
-
guest.sessions.push(sessionId);
|
|
226
|
-
}
|
|
227
|
-
|
|
228
|
-
guest.lastSeen = new Date().toISOString();
|
|
229
|
-
await this.storage.set(`guest:${guestId}`, guest, this.ttl);
|
|
230
|
-
|
|
231
|
-
return guest;
|
|
232
|
-
}
|
|
233
|
-
|
|
234
|
-
/**
|
|
235
|
-
* Delete guest
|
|
236
|
-
* @param {string} guestId - Guest ID
|
|
237
|
-
* @returns {Promise<boolean>} Success status
|
|
238
|
-
*/
|
|
239
|
-
async deleteGuest(guestId) {
|
|
240
|
-
return await this.storage.delete(`guest:${guestId}`);
|
|
241
|
-
}
|
|
242
|
-
|
|
243
|
-
/**
|
|
244
|
-
* Mask IP address for privacy (keep first 2 octets)
|
|
245
|
-
* @private
|
|
246
|
-
*/
|
|
247
|
-
_maskIP(ip) {
|
|
248
|
-
if (!ip) return 'unknown';
|
|
249
|
-
|
|
250
|
-
// IPv4
|
|
251
|
-
if (ip.includes('.')) {
|
|
252
|
-
const parts = ip.split('.');
|
|
253
|
-
return `${parts[0]}.${parts[1]}.xxx.xxx`;
|
|
254
|
-
}
|
|
255
|
-
|
|
256
|
-
// IPv6 - keep first 4 groups
|
|
257
|
-
if (ip.includes(':')) {
|
|
258
|
-
const parts = ip.split(':');
|
|
259
|
-
return `${parts.slice(0, 4).join(':')}:xxxx:xxxx:xxxx:xxxx`;
|
|
260
|
-
}
|
|
261
|
-
|
|
262
|
-
return 'unknown';
|
|
263
|
-
}
|
|
264
|
-
|
|
265
|
-
/**
|
|
266
|
-
* Get all active guests (for admin/monitoring)
|
|
267
|
-
* @returns {Promise<Array>} Array of guest objects
|
|
268
|
-
*/
|
|
269
|
-
async getAllGuests() {
|
|
270
|
-
return await this.storage.getAll('guest:*');
|
|
271
|
-
}
|
|
272
|
-
|
|
273
|
-
/**
|
|
274
|
-
* Clean up expired guests
|
|
275
|
-
* @returns {Promise<number>} Number of cleaned guests
|
|
276
|
-
*/
|
|
277
|
-
async cleanup() {
|
|
278
|
-
const guests = await this.getAllGuests();
|
|
279
|
-
const now = Date.now();
|
|
280
|
-
let cleaned = 0;
|
|
281
|
-
|
|
282
|
-
for (const guest of guests) {
|
|
283
|
-
const lastSeen = new Date(guest.lastSeen).getTime();
|
|
284
|
-
const age = now - lastSeen;
|
|
285
|
-
|
|
286
|
-
if (age > this.ttl * 1000) {
|
|
287
|
-
await this.deleteGuest(guest.id);
|
|
288
|
-
cleaned++;
|
|
289
|
-
}
|
|
290
|
-
}
|
|
291
|
-
|
|
292
|
-
return cleaned;
|
|
293
|
-
}
|
|
294
|
-
}
|
|
295
|
-
|
|
296
|
-
/**
|
|
297
|
-
* NodeCache Storage Adapter (Default)
|
|
298
|
-
* Uses node-cache for automatic expiration and memory management
|
|
299
|
-
*/
|
|
300
|
-
class NodeCacheStorage {
|
|
301
|
-
constructor(ttl = 24 * 60 * 60) {
|
|
302
|
-
this.cache = new NodeCache({
|
|
303
|
-
stdTTL: ttl,
|
|
304
|
-
checkperiod: 600, // Check for expired keys every 10 minutes
|
|
305
|
-
useClones: false, // Better performance
|
|
306
|
-
maxKeys: 100000, // Limit memory usage
|
|
307
|
-
});
|
|
308
|
-
}
|
|
309
|
-
|
|
310
|
-
async get(key) {
|
|
311
|
-
return this.cache.get(key) || null;
|
|
312
|
-
}
|
|
313
|
-
|
|
314
|
-
async set(key, value, ttl) {
|
|
315
|
-
return this.cache.set(key, value, ttl || undefined);
|
|
316
|
-
}
|
|
317
|
-
|
|
318
|
-
async delete(key) {
|
|
319
|
-
return this.cache.del(key) > 0;
|
|
320
|
-
}
|
|
321
|
-
|
|
322
|
-
async getAll(pattern) {
|
|
323
|
-
const prefix = pattern.replace('*', '');
|
|
324
|
-
const keys = this.cache.keys();
|
|
325
|
-
const results = [];
|
|
326
|
-
|
|
327
|
-
for (const key of keys) {
|
|
328
|
-
if (key.startsWith(prefix)) {
|
|
329
|
-
const value = this.cache.get(key);
|
|
330
|
-
if (value) results.push(value);
|
|
331
|
-
}
|
|
332
|
-
}
|
|
333
|
-
|
|
334
|
-
return results;
|
|
335
|
-
}
|
|
336
|
-
|
|
337
|
-
/**
|
|
338
|
-
* Get cache statistics
|
|
339
|
-
*/
|
|
340
|
-
getStats() {
|
|
341
|
-
return this.cache.getStats();
|
|
342
|
-
}
|
|
343
|
-
|
|
344
|
-
/**
|
|
345
|
-
* Clear all cache
|
|
346
|
-
*/
|
|
347
|
-
clear() {
|
|
348
|
-
this.cache.flushAll();
|
|
349
|
-
}
|
|
350
|
-
}
|
|
351
|
-
|
|
352
|
-
/**
|
|
353
|
-
* Simple In-Memory Storage (Fallback)
|
|
354
|
-
* Use NodeCacheStorage or RedisStorage for production
|
|
355
|
-
*/
|
|
356
|
-
export class InMemoryStorage {
|
|
357
|
-
constructor() {
|
|
358
|
-
this.store = new Map();
|
|
359
|
-
this.expirations = new Map();
|
|
360
|
-
}
|
|
361
|
-
|
|
362
|
-
async get(key) {
|
|
363
|
-
// Check if expired
|
|
364
|
-
const expiry = this.expirations.get(key);
|
|
365
|
-
if (expiry && Date.now() > expiry) {
|
|
366
|
-
this.store.delete(key);
|
|
367
|
-
this.expirations.delete(key);
|
|
368
|
-
return null;
|
|
369
|
-
}
|
|
370
|
-
|
|
371
|
-
return this.store.get(key) || null;
|
|
372
|
-
}
|
|
373
|
-
|
|
374
|
-
async set(key, value, ttl) {
|
|
375
|
-
this.store.set(key, value);
|
|
376
|
-
|
|
377
|
-
if (ttl) {
|
|
378
|
-
this.expirations.set(key, Date.now() + (ttl * 1000));
|
|
379
|
-
}
|
|
380
|
-
|
|
381
|
-
return true;
|
|
382
|
-
}
|
|
383
|
-
|
|
384
|
-
async delete(key) {
|
|
385
|
-
this.expirations.delete(key);
|
|
386
|
-
return this.store.delete(key);
|
|
387
|
-
}
|
|
388
|
-
|
|
389
|
-
async getAll(pattern) {
|
|
390
|
-
const results = [];
|
|
391
|
-
const prefix = pattern.replace('*', '');
|
|
392
|
-
|
|
393
|
-
for (const [key, value] of this.store.entries()) {
|
|
394
|
-
if (key.startsWith(prefix)) {
|
|
395
|
-
// Check expiration
|
|
396
|
-
const expiry = this.expirations.get(key);
|
|
397
|
-
if (!expiry || Date.now() <= expiry) {
|
|
398
|
-
results.push(value);
|
|
399
|
-
}
|
|
400
|
-
}
|
|
401
|
-
}
|
|
402
|
-
|
|
403
|
-
return results;
|
|
404
|
-
}
|
|
405
|
-
}
|
|
406
|
-
|
|
407
|
-
/**
|
|
408
|
-
* Redis Storage Adapter
|
|
409
|
-
* For production use with Redis
|
|
410
|
-
* Requires 'redis' package: npm install redis
|
|
411
|
-
*/
|
|
412
|
-
export class RedisStorage {
|
|
413
|
-
constructor(redisClient) {
|
|
414
|
-
if (!redisClient) {
|
|
415
|
-
throw new Error('Redis client is required');
|
|
416
|
-
}
|
|
417
|
-
this.redis = redisClient;
|
|
418
|
-
}
|
|
419
|
-
|
|
420
|
-
async get(key) {
|
|
421
|
-
const data = await this.redis.get(key);
|
|
422
|
-
return data ? JSON.parse(data) : null;
|
|
423
|
-
}
|
|
424
|
-
|
|
425
|
-
async set(key, value, ttl) {
|
|
426
|
-
const data = JSON.stringify(value);
|
|
427
|
-
if (ttl) {
|
|
428
|
-
await this.redis.setex(key, ttl, data);
|
|
429
|
-
} else {
|
|
430
|
-
await this.redis.set(key, data);
|
|
431
|
-
}
|
|
432
|
-
return true;
|
|
433
|
-
}
|
|
434
|
-
|
|
435
|
-
async delete(key) {
|
|
436
|
-
await this.redis.del(key);
|
|
437
|
-
return true;
|
|
438
|
-
}
|
|
439
|
-
|
|
440
|
-
async getAll(pattern) {
|
|
441
|
-
const keys = await this.redis.keys(pattern);
|
|
442
|
-
const results = [];
|
|
443
|
-
|
|
444
|
-
for (const key of keys) {
|
|
445
|
-
const data = await this.get(key);
|
|
446
|
-
if (data) results.push(data);
|
|
447
|
-
}
|
|
448
|
-
|
|
449
|
-
return results;
|
|
450
|
-
}
|
|
451
|
-
}
|
|
452
|
-
|
|
453
|
-
export { NodeCacheStorage };
|
|
454
|
-
export default GuestManager;
|