visibly-cli 0.1.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/bin/visibly.js +36 -0
- package/package.json +20 -0
- package/src/detect.js +91 -0
- package/src/inject-express.js +49 -0
- package/src/inject-next.js +71 -0
- package/src/setup.js +122 -0
- package/src/snippets.js +75 -0
package/bin/visibly.js
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict'
|
|
3
|
+
|
|
4
|
+
const { runSetup } = require('../src/setup')
|
|
5
|
+
|
|
6
|
+
const args = process.argv.slice(2)
|
|
7
|
+
const command = args[0]
|
|
8
|
+
|
|
9
|
+
if (!command || command === 'help' || command === '--help' || command === '-h') {
|
|
10
|
+
console.log(`
|
|
11
|
+
Visibly CLI
|
|
12
|
+
|
|
13
|
+
Commands:
|
|
14
|
+
setup Instrument your server to track AI bot visits
|
|
15
|
+
|
|
16
|
+
Usage:
|
|
17
|
+
npx visibly setup --domain-id <your-domain-id>
|
|
18
|
+
|
|
19
|
+
Options:
|
|
20
|
+
--domain-id Your Visibly domain ID (found in the dashboard)
|
|
21
|
+
--help Show this help message
|
|
22
|
+
`)
|
|
23
|
+
process.exit(0)
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
if (command === 'setup') {
|
|
27
|
+
// Parse --domain-id flag
|
|
28
|
+
const domainIdIdx = args.indexOf('--domain-id')
|
|
29
|
+
const domainId = domainIdIdx !== -1 ? args[domainIdIdx + 1] : null
|
|
30
|
+
|
|
31
|
+
runSetup(domainId)
|
|
32
|
+
process.exit(0)
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
console.error(`\n Unknown command: ${command}\n Run "npx visibly --help" for usage.\n`)
|
|
36
|
+
process.exit(1)
|
package/package.json
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "visibly-cli",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Set up Visibly AI bot tracking on your server with one command",
|
|
5
|
+
"bin": {
|
|
6
|
+
"visibly": "bin/visibly.js"
|
|
7
|
+
},
|
|
8
|
+
"main": "./src/setup.js",
|
|
9
|
+
"engines": {
|
|
10
|
+
"node": ">=18"
|
|
11
|
+
},
|
|
12
|
+
"keywords": [
|
|
13
|
+
"visibly",
|
|
14
|
+
"ai",
|
|
15
|
+
"seo",
|
|
16
|
+
"bot-tracking",
|
|
17
|
+
"llm"
|
|
18
|
+
],
|
|
19
|
+
"license": "MIT"
|
|
20
|
+
}
|
package/src/detect.js
ADDED
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const fs = require('fs')
|
|
4
|
+
const path = require('path')
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Reads package.json from cwd and returns the detected framework.
|
|
8
|
+
* @returns {'next' | 'express' | 'unknown'}
|
|
9
|
+
*/
|
|
10
|
+
function detectFramework(cwd = process.cwd()) {
|
|
11
|
+
const pkgPath = path.join(cwd, 'package.json')
|
|
12
|
+
|
|
13
|
+
if (!fs.existsSync(pkgPath)) {
|
|
14
|
+
return 'unknown'
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
let pkg
|
|
18
|
+
try {
|
|
19
|
+
pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8'))
|
|
20
|
+
} catch {
|
|
21
|
+
return 'unknown'
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const deps = {
|
|
25
|
+
...pkg.dependencies,
|
|
26
|
+
...pkg.devDependencies,
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// Next.js takes precedence — a custom Express server inside a Next.js app
|
|
30
|
+
// is still a Next.js project at its core.
|
|
31
|
+
if (deps['next']) return 'next'
|
|
32
|
+
if (deps['express']) return 'express'
|
|
33
|
+
return 'unknown'
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Finds the middleware file for a Next.js project.
|
|
38
|
+
* Returns the path if it exists, or null if it needs to be created.
|
|
39
|
+
* @returns {{ path: string, exists: boolean }}
|
|
40
|
+
*/
|
|
41
|
+
function findNextMiddleware(cwd = process.cwd()) {
|
|
42
|
+
const candidates = [
|
|
43
|
+
'middleware.ts',
|
|
44
|
+
'middleware.js',
|
|
45
|
+
'src/middleware.ts',
|
|
46
|
+
'src/middleware.js',
|
|
47
|
+
]
|
|
48
|
+
|
|
49
|
+
for (const rel of candidates) {
|
|
50
|
+
const abs = path.join(cwd, rel)
|
|
51
|
+
if (fs.existsSync(abs)) return { path: abs, exists: true }
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// Default creation path: src/middleware.ts if src/ exists, else middleware.ts at root
|
|
55
|
+
const srcDir = path.join(cwd, 'src')
|
|
56
|
+
const defaultPath = fs.existsSync(srcDir)
|
|
57
|
+
? path.join(srcDir, 'middleware.ts')
|
|
58
|
+
: path.join(cwd, 'middleware.ts')
|
|
59
|
+
|
|
60
|
+
return { path: defaultPath, exists: false }
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Finds the Express entry file.
|
|
65
|
+
* @returns {string | null} absolute path, or null if not found
|
|
66
|
+
*/
|
|
67
|
+
function findExpressEntry(cwd = process.cwd()) {
|
|
68
|
+
const candidates = [
|
|
69
|
+
'server.js',
|
|
70
|
+
'server.ts',
|
|
71
|
+
'app.js',
|
|
72
|
+
'app.ts',
|
|
73
|
+
'index.js',
|
|
74
|
+
'index.ts',
|
|
75
|
+
'src/server.js',
|
|
76
|
+
'src/server.ts',
|
|
77
|
+
'src/app.js',
|
|
78
|
+
'src/app.ts',
|
|
79
|
+
'src/index.js',
|
|
80
|
+
'src/index.ts',
|
|
81
|
+
]
|
|
82
|
+
|
|
83
|
+
for (const rel of candidates) {
|
|
84
|
+
const abs = path.join(cwd, rel)
|
|
85
|
+
if (fs.existsSync(abs)) return abs
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
return null
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
module.exports = { detectFramework, findNextMiddleware, findExpressEntry }
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const fs = require('fs')
|
|
4
|
+
const { expressBlock } = require('./snippets')
|
|
5
|
+
|
|
6
|
+
const ALREADY_INJECTED_MARKER = '// Visibly bot tracking'
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Injects the Visibly tracking middleware into an Express entry file.
|
|
10
|
+
*
|
|
11
|
+
* Strategy: find the line where `express()` is called and assigned
|
|
12
|
+
* (e.g. `const app = express()`) and insert the app.use() block
|
|
13
|
+
* immediately after it. Falls back to appending at end of file.
|
|
14
|
+
*/
|
|
15
|
+
function injectIntoExpress(filePath) {
|
|
16
|
+
const source = fs.readFileSync(filePath, 'utf8')
|
|
17
|
+
|
|
18
|
+
// Guard: already injected
|
|
19
|
+
if (source.includes(ALREADY_INJECTED_MARKER)) {
|
|
20
|
+
return { action: 'already_injected', path: filePath }
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const lines = source.split('\n')
|
|
24
|
+
|
|
25
|
+
// Look for the app initialisation line: `= express()` or `express()`
|
|
26
|
+
let insertIdx = -1
|
|
27
|
+
for (let i = 0; i < lines.length; i++) {
|
|
28
|
+
if (/=\s*express\s*\(/.test(lines[i]) || /^const app\s*=/.test(lines[i])) {
|
|
29
|
+
insertIdx = i
|
|
30
|
+
break
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const block = expressBlock()
|
|
35
|
+
|
|
36
|
+
if (insertIdx === -1) {
|
|
37
|
+
// Fallback: append at end of file
|
|
38
|
+
const patched = source.trimEnd() + '\n' + block + '\n'
|
|
39
|
+
fs.writeFileSync(filePath, patched, 'utf8')
|
|
40
|
+
return { action: 'appended', path: filePath, warning: 'Could not find Express app initialisation — snippet appended at end of file. Review placement manually.' }
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// Insert after the found line
|
|
44
|
+
lines.splice(insertIdx + 1, 0, block)
|
|
45
|
+
fs.writeFileSync(filePath, lines.join('\n'), 'utf8')
|
|
46
|
+
return { action: 'injected', path: filePath }
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
module.exports = { injectIntoExpress }
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const fs = require('fs')
|
|
4
|
+
const path = require('path')
|
|
5
|
+
const { trackingBlock, nextMiddlewareTemplate } = require('./snippets')
|
|
6
|
+
|
|
7
|
+
const ALREADY_INJECTED_MARKER = '// Visibly bot tracking'
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Creates a brand-new middleware.ts at the given path.
|
|
11
|
+
* Used when no middleware file exists yet.
|
|
12
|
+
*/
|
|
13
|
+
function createMiddleware(filePath, domainId) {
|
|
14
|
+
fs.mkdirSync(path.dirname(filePath), { recursive: true })
|
|
15
|
+
fs.writeFileSync(filePath, nextMiddlewareTemplate(domainId), 'utf8')
|
|
16
|
+
return { action: 'created', path: filePath }
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Injects the tracking block into an existing middleware file.
|
|
21
|
+
*
|
|
22
|
+
* Strategy: find the last `return NextResponse.next()` or `NextResponse.next()`
|
|
23
|
+
* call and insert the tracking block immediately before it.
|
|
24
|
+
* Uses a line-by-line approach so we don't corrupt the rest of the file.
|
|
25
|
+
*/
|
|
26
|
+
function injectIntoMiddleware(filePath) {
|
|
27
|
+
const source = fs.readFileSync(filePath, 'utf8')
|
|
28
|
+
|
|
29
|
+
// Guard: already injected
|
|
30
|
+
if (source.includes(ALREADY_INJECTED_MARKER)) {
|
|
31
|
+
return { action: 'already_injected', path: filePath }
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const lines = source.split('\n')
|
|
35
|
+
|
|
36
|
+
// Find the last line that contains `return NextResponse.next()`
|
|
37
|
+
// We look from the bottom up so we target the final return in the middleware function.
|
|
38
|
+
let insertIdx = -1
|
|
39
|
+
let indent = ' '
|
|
40
|
+
|
|
41
|
+
for (let i = lines.length - 1; i >= 0; i--) {
|
|
42
|
+
const trimmed = lines[i].trim()
|
|
43
|
+
// Match any return statement that involves NextResponse.next():
|
|
44
|
+
// return NextResponse.next()
|
|
45
|
+
// return response || NextResponse.next()
|
|
46
|
+
// return NextResponse.next({ request })
|
|
47
|
+
if (trimmed.startsWith('return') && trimmed.includes('NextResponse.next(')) {
|
|
48
|
+
insertIdx = i
|
|
49
|
+
// Detect indentation from that line
|
|
50
|
+
const match = lines[i].match(/^(\s+)/)
|
|
51
|
+
if (match) indent = match[1]
|
|
52
|
+
break
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
if (insertIdx === -1) {
|
|
57
|
+
// Fallback: couldn't find a return statement — append to end of file with a warning
|
|
58
|
+
const block = trackingBlock(' ')
|
|
59
|
+
const patched = source.trimEnd() + '\n\n' + block + '\n'
|
|
60
|
+
fs.writeFileSync(filePath, patched, 'utf8')
|
|
61
|
+
return { action: 'appended', path: filePath, warning: 'Could not find "return NextResponse.next()" — snippet appended at end of file. Review placement manually.' }
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// Insert tracking block before the return line
|
|
65
|
+
const block = trackingBlock(indent)
|
|
66
|
+
lines.splice(insertIdx, 0, block)
|
|
67
|
+
fs.writeFileSync(filePath, lines.join('\n'), 'utf8')
|
|
68
|
+
return { action: 'injected', path: filePath }
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
module.exports = { createMiddleware, injectIntoMiddleware }
|
package/src/setup.js
ADDED
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const fs = require('fs')
|
|
4
|
+
const path = require('path')
|
|
5
|
+
const { detectFramework, findNextMiddleware, findExpressEntry } = require('./detect')
|
|
6
|
+
const { createMiddleware, injectIntoMiddleware } = require('./inject-next')
|
|
7
|
+
const { injectIntoExpress } = require('./inject-express')
|
|
8
|
+
const { manualSnippet } = require('./snippets')
|
|
9
|
+
|
|
10
|
+
const ENV_KEY = 'VISIBLY_DOMAIN_ID'
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Writes or updates VISIBLY_DOMAIN_ID in .env.local.
|
|
14
|
+
* Creates the file if it doesn't exist.
|
|
15
|
+
* Updates the existing line if the key is already present.
|
|
16
|
+
*/
|
|
17
|
+
function writeEnvLocal(domainId, cwd = process.cwd()) {
|
|
18
|
+
const envPath = path.join(cwd, '.env.local')
|
|
19
|
+
const line = `${ENV_KEY}=${domainId}`
|
|
20
|
+
|
|
21
|
+
if (!fs.existsSync(envPath)) {
|
|
22
|
+
fs.writeFileSync(envPath, line + '\n', 'utf8')
|
|
23
|
+
return { action: 'created', path: envPath }
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
let contents = fs.readFileSync(envPath, 'utf8')
|
|
27
|
+
|
|
28
|
+
if (contents.includes(ENV_KEY)) {
|
|
29
|
+
// Replace existing line
|
|
30
|
+
contents = contents.replace(new RegExp(`^${ENV_KEY}=.*$`, 'm'), line)
|
|
31
|
+
fs.writeFileSync(envPath, contents, 'utf8')
|
|
32
|
+
return { action: 'updated', path: envPath }
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// Append to end (ensure trailing newline before appending)
|
|
36
|
+
if (!contents.endsWith('\n')) contents += '\n'
|
|
37
|
+
fs.writeFileSync(envPath, contents + line + '\n', 'utf8')
|
|
38
|
+
return { action: 'appended', path: envPath }
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Main setup command.
|
|
43
|
+
* @param {string} domainId
|
|
44
|
+
* @param {{ cwd?: string }} options
|
|
45
|
+
*/
|
|
46
|
+
function runSetup(domainId, { cwd = process.cwd() } = {}) {
|
|
47
|
+
if (!domainId) {
|
|
48
|
+
console.error('\nError: --domain-id is required.\n')
|
|
49
|
+
console.error(' Usage: npx visibly setup --domain-id <your-domain-id>\n')
|
|
50
|
+
process.exit(1)
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
console.log('\n Setting up Visibly bot tracking...\n')
|
|
54
|
+
|
|
55
|
+
const framework = detectFramework(cwd)
|
|
56
|
+
|
|
57
|
+
// ── Next.js ────────────────────────────────────────────────────────────────
|
|
58
|
+
if (framework === 'next') {
|
|
59
|
+
const { path: mwPath, exists } = findNextMiddleware(cwd)
|
|
60
|
+
|
|
61
|
+
let result
|
|
62
|
+
if (exists) {
|
|
63
|
+
result = injectIntoMiddleware(mwPath)
|
|
64
|
+
} else {
|
|
65
|
+
result = createMiddleware(mwPath, domainId)
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const relPath = path.relative(cwd, result.path)
|
|
69
|
+
|
|
70
|
+
if (result.action === 'already_injected') {
|
|
71
|
+
console.log(` ✓ Tracking already present in ${relPath} — no changes made.`)
|
|
72
|
+
} else if (result.action === 'created') {
|
|
73
|
+
console.log(` ✓ Created ${relPath}`)
|
|
74
|
+
} else if (result.action === 'injected') {
|
|
75
|
+
console.log(` ✓ Injected tracking into ${relPath}`)
|
|
76
|
+
} else if (result.action === 'appended') {
|
|
77
|
+
console.log(` ✓ Appended tracking to ${relPath}`)
|
|
78
|
+
if (result.warning) console.log(` ⚠ ${result.warning}`)
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
const envResult = writeEnvLocal(domainId, cwd)
|
|
82
|
+
console.log(` ✓ ${envResult.action === 'created' ? 'Created' : 'Updated'} .env.local with ${ENV_KEY}`)
|
|
83
|
+
|
|
84
|
+
console.log('\n Done! Deploy your site to start recording bot visits.\n')
|
|
85
|
+
return
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// ── Express ────────────────────────────────────────────────────────────────
|
|
89
|
+
if (framework === 'express') {
|
|
90
|
+
const entryPath = findExpressEntry(cwd)
|
|
91
|
+
|
|
92
|
+
if (!entryPath) {
|
|
93
|
+
console.log(' Express detected but entry file not found.')
|
|
94
|
+
console.log(manualSnippet(domainId))
|
|
95
|
+
process.exit(0)
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
const result = injectIntoExpress(entryPath)
|
|
99
|
+
const relPath = path.relative(cwd, result.path)
|
|
100
|
+
|
|
101
|
+
if (result.action === 'already_injected') {
|
|
102
|
+
console.log(` ✓ Tracking already present in ${relPath} — no changes made.`)
|
|
103
|
+
} else if (result.action === 'injected') {
|
|
104
|
+
console.log(` ✓ Injected tracking into ${relPath}`)
|
|
105
|
+
} else if (result.action === 'appended') {
|
|
106
|
+
console.log(` ✓ Appended tracking to ${relPath}`)
|
|
107
|
+
if (result.warning) console.log(` ⚠ ${result.warning}`)
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
const envResult = writeEnvLocal(domainId, cwd)
|
|
111
|
+
console.log(` ✓ ${envResult.action === 'created' ? 'Created' : 'Updated'} .env.local with ${ENV_KEY}`)
|
|
112
|
+
|
|
113
|
+
console.log('\n Done! Deploy your site to start recording bot visits.\n')
|
|
114
|
+
return
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// ── Unknown / fallback ─────────────────────────────────────────────────────
|
|
118
|
+
console.log(' Framework not detected automatically.')
|
|
119
|
+
console.log(manualSnippet(domainId))
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
module.exports = { runSetup }
|
package/src/snippets.js
ADDED
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const BOT_PATTERN = 'GPTBot|ClaudeBot|anthropic-ai|PerplexityBot|Google-Extended|ChatGPT-User'
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* The 4-line tracking block injected into existing middleware files.
|
|
7
|
+
* Indented with 2 spaces to match typical Next.js middleware style.
|
|
8
|
+
*/
|
|
9
|
+
function trackingBlock(indent = ' ') {
|
|
10
|
+
return [
|
|
11
|
+
`${indent}// Visibly bot tracking — added by npx visibly setup`,
|
|
12
|
+
`${indent}const _vua = request.headers.get('user-agent') ?? ''`,
|
|
13
|
+
`${indent}if (/${BOT_PATTERN}/.test(_vua))`,
|
|
14
|
+
`${indent} fetch(\`https://visibly.io/api/bot-visit/\${process.env.VISIBLY_DOMAIN_ID}\`, { method: 'POST', headers: { 'user-agent': _vua } })`,
|
|
15
|
+
`${indent}// end Visibly`,
|
|
16
|
+
].join('\n')
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Full middleware.ts template — used when no middleware.ts exists yet.
|
|
21
|
+
*/
|
|
22
|
+
function nextMiddlewareTemplate(domainId) {
|
|
23
|
+
return `import { NextRequest, NextResponse } from 'next/server'
|
|
24
|
+
|
|
25
|
+
export function middleware(request: NextRequest) {
|
|
26
|
+
// Visibly bot tracking — added by npx visibly setup
|
|
27
|
+
const _vua = request.headers.get('user-agent') ?? ''
|
|
28
|
+
if (/${BOT_PATTERN}/.test(_vua))
|
|
29
|
+
fetch(\`https://visibly.io/api/bot-visit/${domainId}\`, { method: 'POST', headers: { 'user-agent': _vua } })
|
|
30
|
+
// end Visibly
|
|
31
|
+
|
|
32
|
+
return NextResponse.next()
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export const config = {
|
|
36
|
+
matcher: ['/((?!_next/static|_next/image|favicon.ico).*)'],
|
|
37
|
+
}
|
|
38
|
+
`
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Express middleware block injected after app initialisation.
|
|
43
|
+
*/
|
|
44
|
+
function expressBlock() {
|
|
45
|
+
return `
|
|
46
|
+
// Visibly bot tracking — added by npx visibly setup
|
|
47
|
+
app.use((req, _res, next) => {
|
|
48
|
+
const _vua = req.headers['user-agent'] || ''
|
|
49
|
+
if (/${BOT_PATTERN}/.test(_vua))
|
|
50
|
+
fetch(\`https://visibly.io/api/bot-visit/\${process.env.VISIBLY_DOMAIN_ID}\`, { method: 'POST', headers: { 'user-agent': _vua } })
|
|
51
|
+
next()
|
|
52
|
+
})
|
|
53
|
+
// end Visibly`
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Manual snippet printed when framework is not detected.
|
|
58
|
+
*/
|
|
59
|
+
function manualSnippet(domainId) {
|
|
60
|
+
return `
|
|
61
|
+
Framework not detected automatically. Add this to your server's request handler:
|
|
62
|
+
|
|
63
|
+
// Fire-and-forget — no await, no latency impact
|
|
64
|
+
const ua = <incoming user-agent header>
|
|
65
|
+
if (/${BOT_PATTERN}/.test(ua))
|
|
66
|
+
fetch('https://visibly.io/api/bot-visit/${domainId}', {
|
|
67
|
+
method: 'POST',
|
|
68
|
+
headers: { 'user-agent': ua }
|
|
69
|
+
})
|
|
70
|
+
|
|
71
|
+
Your domain ID is already embedded above — no further configuration needed.
|
|
72
|
+
`
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
module.exports = { trackingBlock, nextMiddlewareTemplate, expressBlock, manualSnippet }
|