@realtimex/realtimex-alchemy 1.0.44 → 1.0.46

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/CHANGELOG.md CHANGED
@@ -5,6 +5,18 @@ All notable changes to this project will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [1.0.46] - 2026-01-26
9
+
10
+ ### Fixed
11
+ - **Browser Mining**: Fixed Safari history extraction query and timestamp conversion (CFAbsoluteTime).
12
+ - **Browser Mining**: Updated Chrome/Edge timestamp logic to use BigInt for high-precision microsecond conversion, resolving potential checkpoint drift.
13
+ - **Sync Logic**: Switched history extraction order to Ascending (oldest first) to ensure checkpoints correctly advance from the start date.
14
+
15
+ ## [1.0.45] - 2026-01-26
16
+
17
+ ### Security
18
+ - **Migration Tool**: Enforced Access Token authentication for the `POST /api/migrate` endpoint and `migrate.sh` script. Database passwords are no longer accepted as a security hardening measure.
19
+
8
20
  ## [1.0.44] - 2026-01-26
9
21
 
10
22
  ### Added
package/dist/api/index.js CHANGED
@@ -30,10 +30,13 @@ app.get('/health', (req, res) => {
30
30
  });
31
31
  // Run database migrations (SSE stream)
32
32
  app.post('/api/migrate', (req, res) => {
33
- const { projectId, dbPassword, accessToken } = req.body;
33
+ const { projectId, accessToken } = req.body;
34
34
  if (!projectId) {
35
35
  return res.status(400).json({ error: 'Project ID is required' });
36
36
  }
37
+ if (!accessToken) {
38
+ return res.status(400).json({ error: 'Access token is required' });
39
+ }
37
40
  // Set up SSE for streaming output
38
41
  res.setHeader('Content-Type', 'text/event-stream');
39
42
  res.setHeader('Cache-Control', 'no-cache');
@@ -59,22 +62,15 @@ app.post('/api/migrate', (req, res) => {
59
62
  }
60
63
  sendEvent('info', `Found script at: ${scriptPath}`);
61
64
  sendEvent('info', `Working directory: ${projectRoot}`);
62
- // Prepare environment - support both access token and database password
65
+ // Prepare environment with access token for Supabase CLI
63
66
  const env = {
64
67
  ...process.env,
65
68
  SUPABASE_PROJECT_ID: projectId,
69
+ SUPABASE_ACCESS_TOKEN: accessToken,
66
70
  // Ensure PATH includes common locations for supabase CLI
67
71
  PATH: `${process.env.PATH}:/usr/local/bin:/opt/homebrew/bin:${projectRoot}/node_modules/.bin`
68
72
  };
69
- // Access token is preferred for non-interactive auth
70
- if (accessToken) {
71
- env.SUPABASE_ACCESS_TOKEN = accessToken;
72
- sendEvent('info', 'Using access token for authentication');
73
- }
74
- if (dbPassword) {
75
- env.SUPABASE_DB_PASSWORD = dbPassword;
76
- sendEvent('info', 'Using database password for authentication');
77
- }
73
+ sendEvent('info', 'Using access token for authentication');
78
74
  // Track process state
79
75
  let processCompleted = false;
80
76
  // Spawn the migration script in its own process group
@@ -6,9 +6,9 @@ import { CONFIG } from '../config/index.js';
6
6
  import { ProcessingEventService } from './ProcessingEventService.js';
7
7
  import { UrlNormalizer } from '../utils/UrlNormalizer.js';
8
8
  export class MinerService {
9
- // Timestamp conversion constants
10
- static WEBKIT_EPOCH_OFFSET_MS = 11644473600000; // Milliseconds between 1601-01-01 (WebKit epoch) and 1970-01-01 (Unix epoch)
11
- static SAFARI_EPOCH_OFFSET_SEC = 978307200; // Seconds between 1970-01-01 (Unix epoch) and 2001-01-01 (Safari epoch)
9
+ // Timestamp conversion constants (using BigInt for precision)
10
+ static WEBKIT_EPOCH_OFFSET_MS = 11644473600000n; // Milliseconds between 1601-01-01 and 1970-01-01
11
+ static SAFARI_EPOCH_OFFSET_SEC = 978307200; // Seconds between 1970-01-01 and 2001-01-01
12
12
  static SANITY_CHECK_THRESHOLD = 3000000000000; // Timestamp threshold for Year ~2065, used to detect invalid/raw format timestamps
13
13
  processingEvents = ProcessingEventService.getInstance();
14
14
  debugMode = false;
@@ -155,20 +155,24 @@ export class MinerService {
155
155
  SELECT url, title, visit_count, last_visit_date as last_visit_time
156
156
  FROM moz_places
157
157
  WHERE last_visit_date > ? AND url LIKE 'http%'
158
- ORDER BY last_visit_date DESC
158
+ ORDER BY last_visit_date ASC
159
159
  LIMIT ?
160
160
  `;
161
161
  }
162
162
  else {
163
163
  // Chrome, Edge, Brave, Arc, Safari (usually)
164
164
  if (source.browser === 'safari') {
165
- // Safari uses Core Data timestamp (seconds since 2001-01-01)
166
- // Not fully implemented yet, but keeping placeholder
165
+ // Safari: Join visits and items
167
166
  query = `
168
- SELECT url, title, visit_count, last_visit_time
169
- FROM history_items
170
- WHERE last_visit_time > ?
171
- ORDER BY last_visit_time DESC
167
+ SELECT
168
+ i.url,
169
+ i.title,
170
+ i.visit_count,
171
+ v.visit_time as last_visit_time
172
+ FROM history_visits v
173
+ JOIN history_items i ON v.history_item = i.id
174
+ WHERE v.visit_time > ?
175
+ ORDER BY v.visit_time ASC
172
176
  LIMIT ?
173
177
  `;
174
178
  }
@@ -177,7 +181,7 @@ export class MinerService {
177
181
  SELECT url, title, visit_count, last_visit_time
178
182
  FROM urls
179
183
  WHERE last_visit_time > ?
180
- ORDER BY last_visit_time DESC
184
+ ORDER BY last_visit_time ASC
181
185
  LIMIT ?
182
186
  `;
183
187
  }
@@ -239,8 +243,10 @@ export class MinerService {
239
243
  if (skippedDuplicates > 0 || skippedNonContent > 0 || skippedBlacklist > 0) {
240
244
  this.debug(`URL Filtering: ${skippedDuplicates} duplicates, ${skippedNonContent} non-content, ${skippedBlacklist} blacklisted`);
241
245
  }
242
- if (entries.length > 0) {
243
- const newestTime = Math.max(...entries.map(e => e.last_visit_time));
246
+ if (rows.length > 0) {
247
+ // Since we order ASC, the last row is the latest time in this batch
248
+ const lastRow = rows[rows.length - 1];
249
+ const newestTime = this.toUnixMs(lastRow.last_visit_time, source.browser);
244
250
  await this.saveCheckpoint(source.path, newestTime, supabase, userId);
245
251
  // Also update last_sync_checkpoint in settings for global tracking
246
252
  if (userId && settings) {
@@ -261,29 +267,36 @@ export class MinerService {
261
267
  return Date.now();
262
268
  if (browser === 'firefox') {
263
269
  // Firefox: Microseconds -> Milliseconds
264
- return Math.floor(timestamp / 1000);
270
+ return Number(BigInt(timestamp) / 1000n);
265
271
  }
266
272
  else if (browser === 'safari') {
267
- // Safari: Seconds since 2001-01-01 -> Unix Ms
268
- return Math.floor((timestamp + MinerService.SAFARI_EPOCH_OFFSET_SEC) * 1000);
273
+ // Safari: Seconds (float) since 2001 -> Unix Ms
274
+ // Safari uses floats (CFAbsoluteTime), keep as Number
275
+ return Math.floor((Number(timestamp) + MinerService.SAFARI_EPOCH_OFFSET_SEC) * 1000);
269
276
  }
270
277
  else {
271
- // Chrome/Webkit: Microseconds since 1601-01-01 -> Unix Ms
272
- return Math.floor((timestamp / 1000) - MinerService.WEBKIT_EPOCH_OFFSET_MS);
278
+ // Chrome/Webkit: Microseconds since 1601 -> Unix Ms
279
+ const ts = BigInt(timestamp);
280
+ const microDiff = ts - (MinerService.WEBKIT_EPOCH_OFFSET_MS * 1000n);
281
+ return Number(microDiff / 1000n);
273
282
  }
274
283
  }
275
284
  fromUnixMs(unixMs, browser) {
276
285
  if (!unixMs)
277
- return 0; // Default to beginning of time
286
+ return 0;
278
287
  if (browser === 'firefox') {
279
- return unixMs * 1000;
288
+ // Firefox: Milliseconds -> Microseconds (BigInt)
289
+ return BigInt(unixMs) * 1000n;
280
290
  }
281
291
  else if (browser === 'safari') {
292
+ // Safari: Unix Ms -> Seconds since 2001 (Float)
282
293
  return (unixMs / 1000) - MinerService.SAFARI_EPOCH_OFFSET_SEC;
283
294
  }
284
295
  else {
285
- // Chrome/Webkit
286
- return (unixMs + MinerService.WEBKIT_EPOCH_OFFSET_MS) * 1000;
296
+ // Chrome/Webkit: Unix Ms -> Microseconds since 1601 (BigInt)
297
+ // (UnixMs + OffsetMs) * 1000 = Microseconds
298
+ const unixBig = BigInt(unixMs);
299
+ return (unixBig + MinerService.WEBKIT_EPOCH_OFFSET_MS) * 1000n;
287
300
  }
288
301
  }
289
302
  async getCheckpoint(browser, supabase) {