@chronicle.app/chrome 0.4.0 → 0.5.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.
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Record } from '@chronicle.app/etl';
|
|
1
|
+
import { Record, type Survey, type SurveyOptions } from '@chronicle.app/etl';
|
|
2
2
|
import { SqliteExtractor } from '@chronicle.app/etl-sqlite';
|
|
3
3
|
import ChromeTransformer from './ChromeTransformer.js';
|
|
4
4
|
/** A link followed or a form submitted, from one page to the visit's. */
|
|
@@ -59,5 +59,7 @@ export declare class ChromeExtractor extends SqliteExtractor<typeof ChromeExtrac
|
|
|
59
59
|
private navigation;
|
|
60
60
|
private visit;
|
|
61
61
|
determineCount(): Promise<number | null>;
|
|
62
|
+
/** The visits a run reads and when, with how many pages they were to. */
|
|
63
|
+
survey(options?: SurveyOptions): Promise<Survey | null>;
|
|
62
64
|
}
|
|
63
65
|
//# sourceMappingURL=ChromeExtractor.d.ts.map
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { constants as fsConstants, copyFileSync, mkdtempSync, rmSync, statSync } from 'node:fs';
|
|
2
2
|
import { homedir, tmpdir } from 'node:os';
|
|
3
3
|
import { dirname, join } from 'node:path';
|
|
4
|
-
import { EXIT_CODES, ExtractorError, delay } from '@chronicle.app/etl';
|
|
5
|
-
import { SqliteExtractor, allRows, chromeToUnixMsSql, getRow, iterateRows, timeRangeConditions, unixMsToChromeTimestamp, } from '@chronicle.app/etl-sqlite';
|
|
4
|
+
import { EXIT_CODES, ExtractorError, delay, } from '@chronicle.app/etl';
|
|
5
|
+
import { SqliteExtractor, allRows, chromeToUnixMsSql, countsByMonth, getRow, iterateRows, monthSql, timeRangeConditions, unixMsToChromeTimestamp, } from '@chronicle.app/etl-sqlite';
|
|
6
6
|
import { z } from 'zod';
|
|
7
7
|
import ChromeTransformer from './ChromeTransformer.js';
|
|
8
8
|
import { readChromeAccount } from './profile.js';
|
|
@@ -251,6 +251,30 @@ export class ChromeExtractor extends SqliteExtractor {
|
|
|
251
251
|
return null;
|
|
252
252
|
}
|
|
253
253
|
}
|
|
254
|
+
/** The visits a run reads and when, with how many pages they were to. */
|
|
255
|
+
async survey(options = {}) {
|
|
256
|
+
if (!this.db)
|
|
257
|
+
return null;
|
|
258
|
+
const { where, values } = this.visitFilter();
|
|
259
|
+
const row = this.db
|
|
260
|
+
.prepare(`SELECT COUNT(*) AS n, MIN(${visitTime}) AS first, MAX(${visitTime}) AS last,
|
|
261
|
+
COUNT(DISTINCT urls.id) AS pages ${ChromeExtractor.FROM} WHERE ${where}`)
|
|
262
|
+
.get(...values);
|
|
263
|
+
const byMonth = options.byMonth &&
|
|
264
|
+
countsByMonth(this.db
|
|
265
|
+
.prepare(`SELECT ${monthSql(`${visitTime} / 1000`)} AS month, COUNT(*) AS n
|
|
266
|
+
${ChromeExtractor.FROM} WHERE ${where} GROUP BY month`)
|
|
267
|
+
.all(...values));
|
|
268
|
+
return {
|
|
269
|
+
views: {
|
|
270
|
+
count: row.n,
|
|
271
|
+
...(row.first !== null && { earliest: new Date(row.first) }),
|
|
272
|
+
...(row.last !== null && { latest: new Date(row.last) }),
|
|
273
|
+
...(byMonth && { byMonth }),
|
|
274
|
+
measures: [{ key: 'pages', label: 'pages', value: row.pages }],
|
|
275
|
+
},
|
|
276
|
+
};
|
|
277
|
+
}
|
|
254
278
|
}
|
|
255
279
|
/**
|
|
256
280
|
* History's size and modification time while it holds only saved changes,
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { Locator, type Candidate, type LocationMatch } from '@chronicle.app/etl';
|
|
2
|
+
/**
|
|
3
|
+
* Chrome's profiles, named as its profile menu names them, with the account
|
|
4
|
+
* each is signed in to. The profile list in `Local State` decides which
|
|
5
|
+
* folders are profiles, since Chrome keeps others beside them, like
|
|
6
|
+
* `System Profile`, that no one browses in.
|
|
7
|
+
*/
|
|
8
|
+
export declare class ChromeLocator extends Locator {
|
|
9
|
+
static source: string;
|
|
10
|
+
static strategy: string;
|
|
11
|
+
locate(matches: LocationMatch[]): AsyncGenerator<Candidate>;
|
|
12
|
+
}
|
|
13
|
+
//# sourceMappingURL=ChromeLocator.d.ts.map
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { readFileSync } from 'node:fs';
|
|
2
|
+
import { dirname, join } from 'node:path';
|
|
3
|
+
import { Locator } from '@chronicle.app/etl';
|
|
4
|
+
import { readChromeAccount } from './profile.js';
|
|
5
|
+
/** Chrome's profiles by folder, with the name each shows in Chrome's profile menu. */
|
|
6
|
+
function readProfiles(localStatePath) {
|
|
7
|
+
try {
|
|
8
|
+
const profiles = JSON.parse(readFileSync(localStatePath, 'utf8'))?.profile?.info_cache;
|
|
9
|
+
return profiles && typeof profiles === 'object' ? profiles : null;
|
|
10
|
+
}
|
|
11
|
+
catch {
|
|
12
|
+
return null;
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Chrome's profiles, named as its profile menu names them, with the account
|
|
17
|
+
* each is signed in to. The profile list in `Local State` decides which
|
|
18
|
+
* folders are profiles, since Chrome keeps others beside them, like
|
|
19
|
+
* `System Profile`, that no one browses in.
|
|
20
|
+
*/
|
|
21
|
+
export class ChromeLocator extends Locator {
|
|
22
|
+
static source = 'chrome';
|
|
23
|
+
static strategy = 'app-db';
|
|
24
|
+
async *locate(matches) {
|
|
25
|
+
for (const match of matches) {
|
|
26
|
+
const profile = String(match.options.profile);
|
|
27
|
+
const profileDir = dirname(match.path);
|
|
28
|
+
const profiles = readProfiles(join(dirname(profileDir), 'Local State'));
|
|
29
|
+
if (profiles && !profiles[profile])
|
|
30
|
+
continue;
|
|
31
|
+
const name = profiles?.[profile]?.name || profile;
|
|
32
|
+
const account = readChromeAccount(join(profileDir, 'Preferences'));
|
|
33
|
+
yield {
|
|
34
|
+
label: account ? `${name} (${account.email})` : name,
|
|
35
|
+
config: { profile },
|
|
36
|
+
path: match.path,
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
}
|
package/dist/index.d.ts
CHANGED
package/dist/index.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@chronicle.app/chrome",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.0",
|
|
4
4
|
"main": "./dist/index.js",
|
|
5
5
|
"types": "./dist/index.d.ts",
|
|
6
6
|
"files": [
|
|
@@ -20,9 +20,9 @@
|
|
|
20
20
|
"type": "module",
|
|
21
21
|
"description": "Chronicle Chrome browsing history plugin",
|
|
22
22
|
"devDependencies": {
|
|
23
|
-
"@chronicle.app/etl": "0.
|
|
24
|
-
"@chronicle.app/schema": "0.
|
|
25
|
-
"@chronicle.app/tsconfig": "0.
|
|
23
|
+
"@chronicle.app/etl": "0.5.0",
|
|
24
|
+
"@chronicle.app/schema": "0.5.0",
|
|
25
|
+
"@chronicle.app/tsconfig": "0.5.0",
|
|
26
26
|
"@types/node": "^22.10.1",
|
|
27
27
|
"typescript": "^5.7.2"
|
|
28
28
|
},
|
|
@@ -37,8 +37,8 @@
|
|
|
37
37
|
"registry": "https://registry.npmjs.org/"
|
|
38
38
|
},
|
|
39
39
|
"dependencies": {
|
|
40
|
-
"@chronicle.app/etl-sqlite": "0.
|
|
41
|
-
"@chronicle.app/google": "0.
|
|
40
|
+
"@chronicle.app/etl-sqlite": "0.5.0",
|
|
41
|
+
"@chronicle.app/google": "0.5.0",
|
|
42
42
|
"zod": "^3.24.1"
|
|
43
43
|
},
|
|
44
44
|
"peerDependencies": {
|
|
@@ -64,13 +64,30 @@
|
|
|
64
64
|
"recordTypes": [
|
|
65
65
|
"views"
|
|
66
66
|
],
|
|
67
|
-
"default": true
|
|
67
|
+
"default": true,
|
|
68
|
+
"locations": [
|
|
69
|
+
{
|
|
70
|
+
"path": "~/Library/Application Support/Google/Chrome/{profile}/History",
|
|
71
|
+
"platforms": [
|
|
72
|
+
"darwin"
|
|
73
|
+
]
|
|
74
|
+
},
|
|
75
|
+
{
|
|
76
|
+
"path": "~/.config/google-chrome/{profile}/History",
|
|
77
|
+
"platforms": [
|
|
78
|
+
"linux"
|
|
79
|
+
]
|
|
80
|
+
}
|
|
81
|
+
]
|
|
68
82
|
}
|
|
69
83
|
},
|
|
70
84
|
"platforms": [
|
|
71
85
|
"darwin",
|
|
72
86
|
"linux",
|
|
73
87
|
"win32"
|
|
88
|
+
],
|
|
89
|
+
"requires": [
|
|
90
|
+
"full-disk-access"
|
|
74
91
|
]
|
|
75
92
|
}
|
|
76
93
|
}
|