site-vigil-visitors 0.1.3 → 0.3.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/README.md CHANGED
@@ -23,16 +23,58 @@ For React projects:
23
23
  npm install react site-vigil-visitors
24
24
  ```
25
25
 
26
+ ### Automated setup (React)
27
+
28
+ After installing, run:
29
+
30
+ ```bash
31
+ npx site-vigil-visitors init
32
+ ```
33
+
34
+ This prompts for your site ID, then adds the import and the `<SiteVigil />`
35
+ component to your app entry file (`src/App.tsx`, `src/App.jsx`, `src/main.tsx`,
36
+ or `src/main.jsx`). To skip the prompt:
37
+
38
+ ```bash
39
+ npx site-vigil-visitors init --site-id=my-site
40
+ ```
41
+
42
+ Running `init` again is safe — it does nothing if the snippet is already there.
43
+
44
+ > npm runs install scripts non-interactively, so the setup cannot prompt for a
45
+ > site ID during `npm install` itself. Always run `init` as a separate step.
46
+
47
+ ## Uninstalling
48
+
49
+ Run the removal command **before** uninstalling the package (it needs the
50
+ package to still be installed):
51
+
52
+ ```bash
53
+ npx site-vigil-visitors remove
54
+ npm uninstall site-vigil-visitors
55
+ ```
56
+
57
+ `remove` scans every file under `src/` and strips all tracking code: import
58
+ statements referencing this package and any `<SiteVigil />` elements (single- or
59
+ multi-line, either quote style), including the legacy `<Tracking />` name used
60
+ before v0.3.0. If a file still references the package in a
61
+ way it cannot safely remove (for example custom `createTracker` usage), it
62
+ lists that file for manual review.
63
+
64
+ > npm 7+ no longer runs `preuninstall` scripts, so the package cannot clean up
65
+ > automatically when you run `npm uninstall` — the `remove` command is the
66
+ > supported way to do it.
67
+
26
68
  ## Quick start
27
69
 
28
70
  ### React
29
71
 
30
72
  ```tsx
31
- import { Tracking } from 'site-vigil-visitors/react'
73
+ import { SiteVigil } from 'site-vigil-visitors/react'
32
74
 
33
75
  export function App() {
34
76
  return (
35
- <Tracking
77
+ <SiteVigil
36
78
  siteId="my-site"
37
79
  endpoint="https://t.site-vigil.com"
38
80
  />
@@ -70,12 +112,12 @@ tracker.trackEvent('cta_click', { label: 'Register' })
70
112
  ## React usage
71
113
 
72
114
  ```tsx
73
- import { Tracking } from 'site-vigil-visitors/react'
115
+ import { SiteVigil } from 'site-vigil-visitors/react'
74
116
 
75
117
  export function App() {
76
118
  return (
77
119
  <>
78
- <Tracking
120
+ <SiteVigil
79
121
  siteId="my-site"
80
122
  endpoint="https://t.site-vigil.com"
81
123
  />
@@ -90,12 +132,12 @@ export function App() {
90
132
  Here is a minimal Vite + React example using the package:
91
133
 
92
134
  ```tsx
93
- import { Tracking } from 'site-vigil-visitors/react'
135
+ import { SiteVigil } from 'site-vigil-visitors/react'
94
136
 
95
137
  export default function App() {
96
138
  return (
97
139
  <div>
98
- <Tracking
140
+ <SiteVigil
99
141
  siteId="demo-site"
100
142
  endpoint="https://t.site-vigil.com"
101
143
  />
@@ -133,11 +175,11 @@ Use the React wrapper inside a client component:
133
175
  ```tsx
134
176
  'use client'
135
177
 
136
- import { Tracking } from 'site-vigil-visitors/react'
178
+ import { SiteVigil } from 'site-vigil-visitors/react'
137
179
 
138
180
  export default function TrackingClient() {
139
181
  return (
140
- <Tracking
182
+ <SiteVigil
141
183
  siteId="next-app"
142
184
  endpoint="https://t.site-vigil.com"
143
185
  />
package/bin/cli.mjs ADDED
@@ -0,0 +1,284 @@
1
+ #!/usr/bin/env node
2
+ import { createInterface } from 'node:readline'
3
+ import { readFile, writeFile, access, readdir } from 'node:fs/promises'
4
+ import { join, relative } from 'node:path'
5
+
6
+ const ENDPOINT = 'https://t.site-vigil.com'
7
+ const PACKAGE = 'site-vigil-visitors'
8
+
9
+ const CANDIDATES = [
10
+ 'src/App.tsx',
11
+ 'src/App.jsx',
12
+ 'src/main.tsx',
13
+ 'src/main.jsx',
14
+ ]
15
+
16
+ const SOURCE_EXTENSIONS = ['.tsx', '.jsx', '.ts', '.js']
17
+
18
+ // ---------------------------------------------------------------------------
19
+ // shared helpers
20
+ // ---------------------------------------------------------------------------
21
+
22
+ async function findAppFile(cwd) {
23
+ for (const candidate of CANDIDATES) {
24
+ try {
25
+ await access(join(cwd, candidate))
26
+ return { path: join(cwd, candidate), rel: candidate }
27
+ } catch {}
28
+ }
29
+ return null
30
+ }
31
+
32
+ async function collectSourceFiles(dir, results = []) {
33
+ let entries
34
+ try {
35
+ entries = await readdir(dir, { withFileTypes: true })
36
+ } catch {
37
+ return results
38
+ }
39
+ for (const entry of entries) {
40
+ if (entry.name === 'node_modules' || entry.name.startsWith('.')) continue
41
+ const full = join(dir, entry.name)
42
+ if (entry.isDirectory()) {
43
+ await collectSourceFiles(full, results)
44
+ } else if (SOURCE_EXTENSIONS.some(ext => entry.name.endsWith(ext))) {
45
+ results.push(full)
46
+ }
47
+ }
48
+ return results
49
+ }
50
+
51
+ // ---------------------------------------------------------------------------
52
+ // init
53
+ // ---------------------------------------------------------------------------
54
+
55
+ async function promptSiteId() {
56
+ const arg = process.argv.find(a => a.startsWith('--site-id='))
57
+ if (arg) return arg.slice('--site-id='.length).trim()
58
+
59
+ const rl = createInterface({ input: process.stdin, output: process.stdout })
60
+ return new Promise(resolve => {
61
+ rl.question('Enter your site ID (e.g. my-website): ', answer => {
62
+ rl.close()
63
+ resolve(answer.trim())
64
+ })
65
+ })
66
+ }
67
+
68
+ function detectQuote(content) {
69
+ const double = (content.match(/from "/g) || []).length
70
+ const single = (content.match(/from '/g) || []).length
71
+ return double > single ? '"' : "'"
72
+ }
73
+
74
+ function patchImports(content) {
75
+ if (new RegExp(`from ["']${PACKAGE}/react["']`).test(content)) return content
76
+ const matches = [...content.matchAll(/^import .+$/gm)]
77
+ if (!matches.length) return content
78
+ const q = detectQuote(content)
79
+ const last = matches.at(-1)
80
+ const pos = last.index + last[0].length
81
+ return (
82
+ content.slice(0, pos) +
83
+ `\nimport { SiteVigil } from ${q}${PACKAGE}/react${q}` +
84
+ content.slice(pos)
85
+ )
86
+ }
87
+
88
+ function hasComponent(content) {
89
+ // <Tracking> is the pre-0.3.0 component name
90
+ return /<(?:SiteVigil|Tracking)[\s/>]/.test(content)
91
+ }
92
+
93
+ function patchJsx(content, siteId) {
94
+ if (hasComponent(content)) return content
95
+
96
+ // Insert <Tracking /> as the last child of the file's final JSX closing tag —
97
+ // in a typical app entry file that is the root element of the main return.
98
+ const closers = [...content.matchAll(/^([ \t]*)<\/(?:[A-Za-z][\w.]*)?>[ \t]*\r?$/gm)]
99
+ if (!closers.length) return null
100
+
101
+ const last = closers.at(-1)
102
+ const closeIndent = last[1]
103
+
104
+ // Indent like the preceding sibling line when it sits deeper than the
105
+ // closing tag, otherwise one level further in than the closing tag.
106
+ const prevLine = content.slice(0, last.index).split('\n').filter(l => l.trim()).at(-1) ?? ''
107
+ const prevIndent = prevLine.match(/^[ \t]*/)[0]
108
+ const indent = prevIndent.length > closeIndent.length ? prevIndent : closeIndent + ' '
109
+
110
+ const trackingLine = `${indent}<SiteVigil siteId="${siteId}" endpoint="${ENDPOINT}" />\n`
111
+ return content.slice(0, last.index) + trackingLine + content.slice(last.index)
112
+ }
113
+
114
+ function printManual(siteId) {
115
+ console.log(`Add this import to your app entry file:
116
+
117
+ import { SiteVigil } from '${PACKAGE}/react'
118
+
119
+ Then add the component inside your JSX tree:
120
+
121
+ <SiteVigil
122
+ siteId="${siteId}"
123
+ endpoint="${ENDPOINT}"
124
+ />
125
+ `)
126
+ }
127
+
128
+ async function init() {
129
+ console.log(`${PACKAGE} init\n`)
130
+
131
+ const siteId = await promptSiteId()
132
+ if (!siteId) {
133
+ console.error('Error: site ID is required.')
134
+ process.exit(1)
135
+ }
136
+
137
+ const found = await findAppFile(process.cwd())
138
+ if (!found) {
139
+ console.log('Could not find src/App.tsx or src/main.tsx. Add manually:\n')
140
+ printManual(siteId)
141
+ return
142
+ }
143
+
144
+ console.log(`Found: ${found.rel}`)
145
+
146
+ let content = await readFile(found.path, 'utf-8')
147
+
148
+ if (hasComponent(content)) {
149
+ console.log(`\n<SiteVigil /> is already present in ${found.rel}. Nothing to do.`)
150
+ return
151
+ }
152
+
153
+ content = patchImports(content)
154
+ const patched = patchJsx(content, siteId)
155
+
156
+ if (!patched) {
157
+ console.log('Could not automatically patch JSX. Add manually:\n')
158
+ printManual(siteId)
159
+ return
160
+ }
161
+
162
+ await writeFile(found.path, patched, 'utf-8')
163
+
164
+ console.log(`
165
+ Done! Added <SiteVigil /> to ${found.rel}.
166
+
167
+ To verify:
168
+ 1. Start your dev server
169
+ 2. Open DevTools -> Network, filter for "t.site-vigil.com"
170
+ 3. Reload - you should see a POST with status 200
171
+ 4. Check your dashboard at https://app.site-vigil.com - a visit for "${siteId}" should be recorded
172
+ `)
173
+ }
174
+
175
+ // ---------------------------------------------------------------------------
176
+ // remove
177
+ // ---------------------------------------------------------------------------
178
+
179
+ function stripTracking(content) {
180
+ let result = content
181
+
182
+ // import lines referencing the package (any specifier, either quote style)
183
+ result = result.replace(
184
+ new RegExp(`^[ \\t]*import\\s[^\\n]*["']${PACKAGE}(?:/[^"']*)?["'][^\\n]*\\r?\\n?`, 'gm'),
185
+ ''
186
+ )
187
+
188
+ // <Tracking> is the pre-0.3.0 component name; keep stripping it so remove
189
+ // also cleans up sites integrated with older versions.
190
+ const NAMES = /SiteVigil|Tracking/
191
+
192
+ // <SiteVigil ... /> on its own line(s), self-closing (possibly multi-line)
193
+ result = result.replace(
194
+ new RegExp(`^[ \\t]*<(?:${NAMES.source})\\b[\\s\\S]*?/>[ \\t]*\\r?\\n?`, 'gm'), '')
195
+
196
+ // <SiteVigil>...</SiteVigil> with children, on its own line(s)
197
+ result = result.replace(
198
+ new RegExp(`^[ \\t]*<(${NAMES.source})\\b[\\s\\S]*?</\\1>[ \\t]*\\r?\\n?`, 'gm'), '')
199
+
200
+ // any inline occurrences that were not at the start of a line
201
+ result = result.replace(new RegExp(`<(?:${NAMES.source})\\b[\\s\\S]*?/>`, 'g'), '')
202
+ result = result.replace(new RegExp(`<(${NAMES.source})\\b[\\s\\S]*?</\\1>`, 'g'), '')
203
+
204
+ return result
205
+ }
206
+
207
+ async function remove() {
208
+ console.log(`${PACKAGE} remove\n`)
209
+
210
+ const cwd = process.cwd()
211
+ const files = await collectSourceFiles(join(cwd, 'src'))
212
+
213
+ if (!files.length) {
214
+ console.log('No source files found under src/. Nothing to do.')
215
+ return
216
+ }
217
+
218
+ const changed = []
219
+ const needsReview = []
220
+
221
+ for (const file of files) {
222
+ const content = await readFile(file, 'utf-8')
223
+ if (!content.includes(PACKAGE) && !hasComponent(content)) continue
224
+
225
+ const stripped = stripTracking(content)
226
+ if (stripped !== content) {
227
+ await writeFile(file, stripped, 'utf-8')
228
+ changed.push(relative(cwd, file))
229
+ }
230
+
231
+ if (/site-vigil|createTracker|<(?:SiteVigil|Tracking)\b/.test(stripped)) {
232
+ needsReview.push(relative(cwd, file))
233
+ }
234
+ }
235
+
236
+ if (changed.length) {
237
+ console.log('Removed tracking code from:')
238
+ for (const rel of changed) console.log(` - ${rel}`)
239
+ } else {
240
+ console.log('No tracking code found in src/.')
241
+ }
242
+
243
+ if (needsReview.length) {
244
+ console.log('\nWarning: these files still reference the package and need manual review:')
245
+ for (const rel of needsReview) console.log(` - ${rel}`)
246
+ }
247
+
248
+ console.log(`
249
+ Now remove the package itself:
250
+
251
+ npm uninstall ${PACKAGE}
252
+ `)
253
+ }
254
+
255
+ // ---------------------------------------------------------------------------
256
+ // entry point
257
+ // ---------------------------------------------------------------------------
258
+
259
+ function printUsage() {
260
+ console.log(`Usage: npx ${PACKAGE} <command>
261
+
262
+ Commands:
263
+ init Add the <Tracking /> snippet to your app (prompts for a site ID,
264
+ or pass --site-id=my-site)
265
+ remove Strip all tracking code from src/ (run BEFORE npm uninstall)
266
+ `)
267
+ }
268
+
269
+ const command = process.argv[2] && !process.argv[2].startsWith('--')
270
+ ? process.argv[2]
271
+ : 'init'
272
+
273
+ const run = { init, remove, uninstall: remove }[command]
274
+
275
+ if (!run) {
276
+ console.error(`Unknown command: ${command}\n`)
277
+ printUsage()
278
+ process.exit(1)
279
+ }
280
+
281
+ run().catch(err => {
282
+ console.error(err.message)
283
+ process.exit(1)
284
+ })
@@ -1 +1 @@
1
- {"version":3,"file":"core.d.ts","sourceRoot":"","sources":["../src/core.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,aAAa;IAC5B,MAAM,EAAE,MAAM,CAAA;IACd,QAAQ,EAAE,MAAM,CAAA;IAChB,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,iBAAiB,CAAC,EAAE,OAAO,CAAA;CAC5B;AAED,MAAM,MAAM,aAAa,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;AAEnD,MAAM,WAAW,OAAO;IACtB,KAAK,EAAE,MAAM,IAAI,CAAA;IACjB,OAAO,EAAE,MAAM,IAAI,CAAA;IACnB,aAAa,EAAE,MAAM,IAAI,CAAA;IACzB,UAAU,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,aAAa,KAAK,IAAI,CAAA;CAC/D;AAYD,wBAAgB,aAAa,CAAC,EAC5B,MAAM,EACN,QAAQ,EACR,UAA6B,EAC7B,iBAAwB,GACzB,EAAE,aAAa,GAAG,OAAO,CA8DzB"}
1
+ {"version":3,"file":"core.d.ts","sourceRoot":"","sources":["../src/core.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,aAAa;IAC5B,MAAM,EAAE,MAAM,CAAA;IACd,QAAQ,EAAE,MAAM,CAAA;IAChB,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,iBAAiB,CAAC,EAAE,OAAO,CAAA;CAC5B;AAED,MAAM,MAAM,aAAa,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;AAEnD,MAAM,WAAW,OAAO;IACtB,KAAK,EAAE,MAAM,IAAI,CAAA;IACjB,OAAO,EAAE,MAAM,IAAI,CAAA;IACnB,aAAa,EAAE,MAAM,IAAI,CAAA;IACzB,UAAU,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,aAAa,KAAK,IAAI,CAAA;CAC/D;AAYD,wBAAgB,aAAa,CAAC,EAC5B,MAAM,EACN,QAAQ,EACR,UAA6B,EAC7B,iBAAwB,GACzB,EAAE,aAAa,GAAG,OAAO,CA6EzB"}
package/dist/core.js CHANGED
@@ -32,24 +32,39 @@ export function createTracker({ siteId, endpoint, sessionKey = 'tithij_session',
32
32
  ...metadata,
33
33
  };
34
34
  const body = JSON.stringify(payload);
35
+ if (typeof navigator.sendBeacon === 'function') {
36
+ const blob = new Blob([body], { type: 'application/json' });
37
+ if (navigator.sendBeacon(endpoint, blob))
38
+ return;
39
+ }
35
40
  void fetch(endpoint, {
36
41
  method: 'POST',
42
+ mode: 'cors',
43
+ credentials: 'omit',
37
44
  headers: { 'Content-Type': 'application/json' },
38
45
  body,
39
46
  keepalive: true,
40
47
  }).catch(() => { });
41
48
  };
42
- const handleBeforeUnload = () => send('leave');
49
+ const handlePageLeave = () => send('leave');
50
+ const handleVisibilityChange = () => {
51
+ if (document.visibilityState === 'hidden')
52
+ handlePageLeave();
53
+ };
43
54
  return {
44
55
  start() {
45
56
  send('visit');
46
57
  if (includeLeaveEvent && isBrowser) {
47
- window.addEventListener('beforeunload', handleBeforeUnload);
58
+ window.addEventListener('beforeunload', handlePageLeave);
59
+ window.addEventListener('pagehide', handlePageLeave);
60
+ document.addEventListener('visibilitychange', handleVisibilityChange);
48
61
  }
49
62
  },
50
63
  destroy() {
51
64
  if (includeLeaveEvent && isBrowser) {
52
- window.removeEventListener('beforeunload', handleBeforeUnload);
65
+ window.removeEventListener('beforeunload', handlePageLeave);
66
+ window.removeEventListener('pagehide', handlePageLeave);
67
+ document.removeEventListener('visibilitychange', handleVisibilityChange);
53
68
  }
54
69
  },
55
70
  trackPageView() {
package/dist/core.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"core.js","sourceRoot":"","sources":["../src/core.ts"],"names":[],"mappings":"AAgBA,SAAS,eAAe;IACtB,MAAM,UAAU,GAAG,UAAU,CAAC,MAAM,EAAE,UAAU,EAAE,IAAI,CAAC,UAAU,CAAC,MAAM,CAAC,CAAA;IAEzE,IAAI,UAAU,EAAE,CAAC;QACf,OAAO,UAAU,EAAE,CAAA;IACrB,CAAC;IAED,OAAO,WAAW,IAAI,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,IAAI,CAAC,GAAG,EAAE,EAAE,CAAA;AACvE,CAAC;AAED,MAAM,UAAU,aAAa,CAAC,EAC5B,MAAM,EACN,QAAQ,EACR,UAAU,GAAG,gBAAgB,EAC7B,iBAAiB,GAAG,IAAI,GACV;IACd,MAAM,SAAS,GACb,OAAO,MAAM,KAAK,WAAW;QAC7B,OAAO,QAAQ,KAAK,WAAW;QAC/B,OAAO,SAAS,KAAK,WAAW,CAAA;IAElC,MAAM,YAAY,GAAG,GAAG,EAAE;QACxB,IAAI,CAAC,SAAS;YAAE,OAAO,QAAQ,CAAA;QAE/B,MAAM,QAAQ,GAAG,MAAM,CAAC,cAAc,CAAC,OAAO,CAAC,UAAU,CAAC,CAAA;QAC1D,IAAI,QAAQ;YAAE,OAAO,QAAQ,CAAA;QAE7B,MAAM,EAAE,GAAG,eAAe,EAAE,CAAA;QAC5B,MAAM,CAAC,cAAc,CAAC,OAAO,CAAC,UAAU,EAAE,EAAE,CAAC,CAAA;QAC7C,OAAO,EAAE,CAAA;IACX,CAAC,CAAA;IAED,MAAM,IAAI,GAAG,CAAC,MAAc,EAAE,WAA0B,EAAE,EAAE,EAAE;QAC5D,IAAI,CAAC,SAAS;YAAE,OAAM;QAEtB,MAAM,OAAO,GAAG;YACd,MAAM;YACN,SAAS,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;YACnC,IAAI,EAAE,MAAM,CAAC,QAAQ,CAAC,QAAQ;YAC9B,QAAQ,EAAE,QAAQ,CAAC,QAAQ;YAC3B,MAAM;YACN,SAAS,EAAE,YAAY,EAAE;YACzB,GAAG,QAAQ;SACZ,CAAA;QAED,MAAM,IAAI,GAAG,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,CAAA;QAEpC,KAAK,KAAK,CAAC,QAAQ,EAAE;YACnB,MAAM,EAAE,MAAM;YACd,OAAO,EAAE,EAAE,cAAc,EAAE,kBAAkB,EAAE;YAC/C,IAAI;YACJ,SAAS,EAAE,IAAI;SAChB,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,GAAE,CAAC,CAAC,CAAA;IACpB,CAAC,CAAA;IAED,MAAM,kBAAkB,GAAG,GAAG,EAAE,CAAC,IAAI,CAAC,OAAO,CAAC,CAAA;IAE9C,OAAO;QACL,KAAK;YACH,IAAI,CAAC,OAAO,CAAC,CAAA;YAEb,IAAI,iBAAiB,IAAI,SAAS,EAAE,CAAC;gBACnC,MAAM,CAAC,gBAAgB,CAAC,cAAc,EAAE,kBAAkB,CAAC,CAAA;YAC7D,CAAC;QACH,CAAC;QACD,OAAO;YACL,IAAI,iBAAiB,IAAI,SAAS,EAAE,CAAC;gBACnC,MAAM,CAAC,mBAAmB,CAAC,cAAc,EAAE,kBAAkB,CAAC,CAAA;YAChE,CAAC;QACH,CAAC;QACD,aAAa;YACX,IAAI,CAAC,OAAO,CAAC,CAAA;QACf,CAAC;QACD,UAAU,CAAC,MAAc,EAAE,WAA0B,EAAE;YACrD,IAAI,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAA;QACxB,CAAC;KACF,CAAA;AACH,CAAC"}
1
+ {"version":3,"file":"core.js","sourceRoot":"","sources":["../src/core.ts"],"names":[],"mappings":"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"}
package/dist/react.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { type TrackerConfig } from './core.js';
2
- export interface TrackingProps extends TrackerConfig {
2
+ export interface SiteVigilProps extends TrackerConfig {
3
3
  }
4
- export declare function Tracking({ siteId, endpoint, sessionKey, includeLeaveEvent, }: TrackingProps): null;
4
+ export declare function SiteVigil({ siteId, endpoint, sessionKey, includeLeaveEvent, }: SiteVigilProps): null;
5
5
  //# sourceMappingURL=react.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"react.d.ts","sourceRoot":"","sources":["../src/react.tsx"],"names":[],"mappings":"AACA,OAAO,EAAiB,KAAK,aAAa,EAAE,MAAM,WAAW,CAAA;AAE7D,MAAM,WAAW,aAAc,SAAQ,aAAa;CAAG;AAEvD,wBAAgB,QAAQ,CAAC,EACvB,MAAM,EACN,QAAQ,EACR,UAAU,EACV,iBAAiB,GAClB,EAAE,aAAa,QAiBf"}
1
+ {"version":3,"file":"react.d.ts","sourceRoot":"","sources":["../src/react.tsx"],"names":[],"mappings":"AACA,OAAO,EAAiB,KAAK,aAAa,EAAE,MAAM,WAAW,CAAA;AAE7D,MAAM,WAAW,cAAe,SAAQ,aAAa;CAAG;AAExD,wBAAgB,SAAS,CAAC,EACxB,MAAM,EACN,QAAQ,EACR,UAAU,EACV,iBAAiB,GAClB,EAAE,cAAc,QAiBhB"}
package/dist/react.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import { useEffect } from 'react';
2
2
  import { createTracker } from './core.js';
3
- export function Tracking({ siteId, endpoint, sessionKey, includeLeaveEvent, }) {
3
+ export function SiteVigil({ siteId, endpoint, sessionKey, includeLeaveEvent, }) {
4
4
  useEffect(() => {
5
5
  const tracker = createTracker({
6
6
  siteId,
package/dist/react.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"react.js","sourceRoot":"","sources":["../src/react.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,OAAO,CAAA;AACjC,OAAO,EAAE,aAAa,EAAsB,MAAM,WAAW,CAAA;AAI7D,MAAM,UAAU,QAAQ,CAAC,EACvB,MAAM,EACN,QAAQ,EACR,UAAU,EACV,iBAAiB,GACH;IACd,SAAS,CAAC,GAAG,EAAE;QACb,MAAM,OAAO,GAAG,aAAa,CAAC;YAC5B,MAAM;YACN,QAAQ;YACR,UAAU;YACV,iBAAiB;SAClB,CAAC,CAAA;QAEF,OAAO,CAAC,KAAK,EAAE,CAAA;QAEf,OAAO,GAAG,EAAE;YACV,OAAO,CAAC,OAAO,EAAE,CAAA;QACnB,CAAC,CAAA;IACH,CAAC,EAAE,CAAC,MAAM,EAAE,QAAQ,EAAE,UAAU,EAAE,iBAAiB,CAAC,CAAC,CAAA;IAErD,OAAO,IAAI,CAAA;AACb,CAAC"}
1
+ {"version":3,"file":"react.js","sourceRoot":"","sources":["../src/react.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,OAAO,CAAA;AACjC,OAAO,EAAE,aAAa,EAAsB,MAAM,WAAW,CAAA;AAI7D,MAAM,UAAU,SAAS,CAAC,EACxB,MAAM,EACN,QAAQ,EACR,UAAU,EACV,iBAAiB,GACF;IACf,SAAS,CAAC,GAAG,EAAE;QACb,MAAM,OAAO,GAAG,aAAa,CAAC;YAC5B,MAAM;YACN,QAAQ;YACR,UAAU;YACV,iBAAiB;SAClB,CAAC,CAAA;QAEF,OAAO,CAAC,KAAK,EAAE,CAAA;QAEf,OAAO,GAAG,EAAE;YACV,OAAO,CAAC,OAAO,EAAE,CAAA;QACnB,CAAC,CAAA;IACH,CAAC,EAAE,CAAC,MAAM,EAAE,QAAQ,EAAE,UAAU,EAAE,iBAAiB,CAAC,CAAC,CAAA;IAErD,OAAO,IAAI,CAAA;AACb,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "site-vigil-visitors",
3
- "version": "0.1.3",
3
+ "version": "0.3.0",
4
4
  "description": "Lightweight browser visit tracking with optional React integration.",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -19,7 +19,7 @@
19
19
  }
20
20
  },
21
21
  "bin": {
22
- "site-vigil-visitors": "bin/init.mjs"
22
+ "site-vigil-visitors": "bin/cli.mjs"
23
23
  },
24
24
  "files": [
25
25
  "dist",
package/bin/init.mjs DELETED
@@ -1,124 +0,0 @@
1
- #!/usr/bin/env node
2
- import { createInterface } from 'node:readline'
3
- import { readFile, writeFile, access } from 'node:fs/promises'
4
- import { join } from 'node:path'
5
-
6
- const ENDPOINT = 'https://t.site-vigil.com'
7
-
8
- const CANDIDATES = [
9
- 'src/App.tsx',
10
- 'src/App.jsx',
11
- 'src/main.tsx',
12
- 'src/main.jsx',
13
- ]
14
-
15
- async function findAppFile(cwd) {
16
- for (const candidate of CANDIDATES) {
17
- try {
18
- await access(join(cwd, candidate))
19
- return { path: join(cwd, candidate), rel: candidate }
20
- } catch {}
21
- }
22
- return null
23
- }
24
-
25
- async function promptSiteId() {
26
- const arg = process.argv.find(a => a.startsWith('--site-id='))
27
- if (arg) return arg.slice('--site-id='.length).trim()
28
-
29
- const rl = createInterface({ input: process.stdin, output: process.stdout })
30
- return new Promise(resolve => {
31
- rl.question('Enter your site ID (e.g. my-website): ', answer => {
32
- rl.close()
33
- resolve(answer.trim())
34
- })
35
- })
36
- }
37
-
38
- function patchImports(content) {
39
- if (content.includes("from 'site-vigil-visitors/react'")) return content
40
- const matches = [...content.matchAll(/^import .+$/gm)]
41
- if (!matches.length) return content
42
- const last = matches.at(-1)
43
- const pos = last.index + last[0].length
44
- return (
45
- content.slice(0, pos) +
46
- "\nimport { Tracking } from 'site-vigil-visitors/react'" +
47
- content.slice(pos)
48
- )
49
- }
50
-
51
- function patchJsx(content, siteId) {
52
- if (content.includes('<Tracking ')) return content
53
-
54
- // Match the first non-self-closing JSX opening tag (capital-letter component) and
55
- // insert <Tracking /> as its first child, matching the sibling's indentation.
56
- const firstParentTag = /^([ \t]+<[A-Z][^\n]*[^/]>)([ \t]*\n)([ \t]+<)/m
57
- const m = content.match(firstParentTag)
58
- if (!m) return null
59
-
60
- const indent = m[3].match(/^([ \t]+)/)[1]
61
- const trackingLine = `${indent}<Tracking siteId="${siteId}" endpoint="${ENDPOINT}" />\n`
62
- return content.replace(firstParentTag, `$1$2${trackingLine}$3`)
63
- }
64
-
65
- function printManual(siteId) {
66
- console.log(`Add this import to your app entry file:
67
-
68
- import { Tracking } from 'site-vigil-visitors/react'
69
-
70
- Then add the component inside your JSX tree:
71
-
72
- <Tracking
73
- siteId="${siteId}"
74
- endpoint="${ENDPOINT}"
75
- />
76
- `)
77
- }
78
-
79
- async function main() {
80
- console.log('site-vigil-visitors init\n')
81
-
82
- const siteId = await promptSiteId()
83
- if (!siteId) {
84
- console.error('Error: site ID is required.')
85
- process.exit(1)
86
- }
87
-
88
- const found = await findAppFile(process.cwd())
89
- if (!found) {
90
- console.log('Could not find src/App.tsx or src/main.tsx. Add manually:\n')
91
- printManual(siteId)
92
- return
93
- }
94
-
95
- console.log(`Found: ${found.rel}`)
96
-
97
- let content = await readFile(found.path, 'utf-8')
98
- content = patchImports(content)
99
- const patched = patchJsx(content, siteId)
100
-
101
- if (!patched) {
102
- console.log('Could not automatically patch JSX. Add manually:\n')
103
- printManual(siteId)
104
- return
105
- }
106
-
107
- await writeFile(found.path, patched, 'utf-8')
108
-
109
- console.log(`
110
- Done! Added <Tracking /> to ${found.rel}.
111
-
112
- To verify:
113
- 1. Start your dev server
114
- 2. Open DevTools → Network, filter for "t.site-vigil.com"
115
- 3. Reload — you should see a POST with status 200
116
-
117
- Note: make sure "${siteId}" is registered in your Supabase websites table (tracking_id column).
118
- `)
119
- }
120
-
121
- main().catch(err => {
122
- console.error(err.message)
123
- process.exit(1)
124
- })