@reactll/connect 1.0.1 → 1.1.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 +28 -2
- package/dist/server.d.ts +25 -0
- package/dist/server.js +41 -1
- package/dist/token.d.ts +1 -1
- package/dist/token.js +1 -1
- package/package.json +15 -2
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
# Reactll Connect for Next.js
|
|
1
|
+
# Reactll Connect for Next.js and Node.js
|
|
2
2
|
|
|
3
|
-
Connects a Next.js site to its [Reactll](https://reactll.com) dashboard: visits, calls, WhatsApp and
|
|
3
|
+
Connects a Next.js or any Node.js site (Express, Fastify, Nuxt, Remix, Astro, Hono…) to its [Reactll](https://reactll.com) dashboard: visits, calls, WhatsApp and
|
|
4
4
|
directions clicks, form leads and site health. Built and maintained by **Reactor Technology**.
|
|
5
5
|
|
|
6
6
|
## Install
|
|
@@ -54,6 +54,32 @@ export async function contact(formData: FormData) {
|
|
|
54
54
|
The script stamps every form with the page view id (`_rc_pv`), so Reactll ties the lead to the visit it
|
|
55
55
|
came from. `submitLead(form, fields, options)` takes a plain object instead. Neither ever throws.
|
|
56
56
|
|
|
57
|
+
## Any other Node.js app
|
|
58
|
+
|
|
59
|
+
The same package, without Next.js or React (they are optional):
|
|
60
|
+
|
|
61
|
+
```js
|
|
62
|
+
import { scriptTag, expressHealth, submitLead } from '@reactll/connect/server';
|
|
63
|
+
|
|
64
|
+
app.use(expressHealth()); // Express / Connect / node:http — GET /api/reactll/health
|
|
65
|
+
res.render('page', { reactll: scriptTag() }); // put {{{ reactll }}} / <%- reactll %> in the <head>
|
|
66
|
+
|
|
67
|
+
// after your form was validated and saved:
|
|
68
|
+
await submitLead('Contact', { name, email, message }, { pv: req.body._rc_pv, page: req.get('referer') });
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
Frameworks on web `Request`/`Response` (Hono, Remix, Nuxt/h3, Astro, Bun, Deno): route
|
|
72
|
+
`GET /api/reactll/health` to `health(request)` from `@reactll/connect/server`.
|
|
73
|
+
|
|
74
|
+
## Content-Security-Policy
|
|
75
|
+
|
|
76
|
+
If the site sends a CSP header, allow Reactll Connect in it, or the browser blocks the script and its beacons:
|
|
77
|
+
|
|
78
|
+
```
|
|
79
|
+
script-src … https://connect.reactll.com
|
|
80
|
+
connect-src … https://connect.reactll.com
|
|
81
|
+
```
|
|
82
|
+
|
|
57
83
|
## License
|
|
58
84
|
|
|
59
85
|
MIT © Reactor Technology
|
package/dist/server.d.ts
CHANGED
|
@@ -21,6 +21,11 @@ export declare function submitLead(form: string, fields: Fields, options?: LeadO
|
|
|
21
21
|
export declare function submitLeadFromFormData(form: string, data: FormData, options?: LeadOptions & {
|
|
22
22
|
headers?: Headers;
|
|
23
23
|
}): Promise<boolean>;
|
|
24
|
+
/**
|
|
25
|
+
* The script tag for any server-rendered page (Express/EJS, Handlebars, Fastify, Nuxt, Astro…), or ''
|
|
26
|
+
* when not connected. Put it in the <head>. Only the public site key is in it.
|
|
27
|
+
*/
|
|
28
|
+
export declare function scriptTag(token?: string): string;
|
|
24
29
|
/**
|
|
25
30
|
* Hourly health check, asked by Reactll (Next.js has no scheduler of its own). Export it from
|
|
26
31
|
* app/api/reactll/health/route.ts:
|
|
@@ -31,3 +36,23 @@ export declare function submitLeadFromFormData(form: string, data: FormData, opt
|
|
|
31
36
|
*/
|
|
32
37
|
export declare function GET(request: Request): Promise<Response>;
|
|
33
38
|
export declare const dynamic = "force-dynamic";
|
|
39
|
+
/** The health answer as plain data (platform and versions), for any framework. */
|
|
40
|
+
export declare function health(request: Request): Promise<Response>;
|
|
41
|
+
type NodeReq = {
|
|
42
|
+
method?: string;
|
|
43
|
+
url?: string;
|
|
44
|
+
originalUrl?: string;
|
|
45
|
+
headers: Record<string, string | string[] | undefined>;
|
|
46
|
+
};
|
|
47
|
+
type NodeRes = {
|
|
48
|
+
statusCode: number;
|
|
49
|
+
setHeader(name: string, value: string): unknown;
|
|
50
|
+
end(body?: string): unknown;
|
|
51
|
+
};
|
|
52
|
+
/**
|
|
53
|
+
* Express / Connect (and plain node:http) middleware answering Reactll's hourly health question at
|
|
54
|
+
* GET /api/reactll/health. Other requests pass through.
|
|
55
|
+
*
|
|
56
|
+
* app.use(expressHealth());
|
|
57
|
+
*/
|
|
58
|
+
export declare function expressHealth(path?: string): (req: NodeReq, res: NodeRes, next?: () => void) => Promise<void>;
|
package/dist/server.js
CHANGED
|
@@ -65,6 +65,14 @@ export async function submitLeadFromFormData(form, data, options = {}) {
|
|
|
65
65
|
const pv = typeof data.get('_rc_pv') === 'string' ? data.get('_rc_pv') : null;
|
|
66
66
|
return submitLead(form, fields, { ...options, pv: options.pv ?? pv, page: options.page ?? options.headers?.get('referer') ?? null });
|
|
67
67
|
}
|
|
68
|
+
/**
|
|
69
|
+
* The script tag for any server-rendered page (Express/EJS, Handlebars, Fastify, Nuxt, Astro…), or ''
|
|
70
|
+
* when not connected. Put it in the <head>. Only the public site key is in it.
|
|
71
|
+
*/
|
|
72
|
+
export function scriptTag(token) {
|
|
73
|
+
const c = credentials(token);
|
|
74
|
+
return c ? `<script src="${c.u}/c.js" data-site="${c.k}" defer></script>` : '';
|
|
75
|
+
}
|
|
68
76
|
function versionOf(pkg) {
|
|
69
77
|
try {
|
|
70
78
|
return createRequire(import.meta.url)(`${pkg}/package.json`).version;
|
|
@@ -93,7 +101,7 @@ export async function GET(request) {
|
|
|
93
101
|
return Response.json({ error: 'unauthorized' }, { status: 401 });
|
|
94
102
|
}
|
|
95
103
|
return Response.json({
|
|
96
|
-
platform: 'next',
|
|
104
|
+
platform: versionOf('next') ? 'next' : 'node',
|
|
97
105
|
version: VERSION,
|
|
98
106
|
runtime: Object.fromEntries(Object.entries({
|
|
99
107
|
next: versionOf('next'),
|
|
@@ -101,7 +109,39 @@ export async function GET(request) {
|
|
|
101
109
|
node: process.version.replace(/^v/, ''),
|
|
102
110
|
environment: process.env.VERCEL_ENV ?? process.env.NODE_ENV,
|
|
103
111
|
host: process.env.VERCEL ? 'Vercel' : undefined,
|
|
112
|
+
// Reactll counts the site's days on this clock (UTC is read as "not set"; set TZ to override).
|
|
113
|
+
timezone: process.env.TZ || Intl.DateTimeFormat().resolvedOptions().timeZone,
|
|
104
114
|
}).filter(([, v]) => v)),
|
|
105
115
|
}, { headers: { 'Cache-Control': 'no-store' } });
|
|
106
116
|
}
|
|
107
117
|
export const dynamic = 'force-dynamic';
|
|
118
|
+
/** The health answer as plain data (platform and versions), for any framework. */
|
|
119
|
+
export async function health(request) {
|
|
120
|
+
return GET(request);
|
|
121
|
+
}
|
|
122
|
+
/**
|
|
123
|
+
* Express / Connect (and plain node:http) middleware answering Reactll's hourly health question at
|
|
124
|
+
* GET /api/reactll/health. Other requests pass through.
|
|
125
|
+
*
|
|
126
|
+
* app.use(expressHealth());
|
|
127
|
+
*/
|
|
128
|
+
export function expressHealth(path = '/api/reactll/health') {
|
|
129
|
+
return async (req, res, next) => {
|
|
130
|
+
const url = (req.originalUrl ?? req.url ?? '').split('?')[0];
|
|
131
|
+
if ((req.method ?? 'GET') !== 'GET' || url !== path) {
|
|
132
|
+
next?.();
|
|
133
|
+
return;
|
|
134
|
+
}
|
|
135
|
+
const header = (name) => {
|
|
136
|
+
const v = req.headers[name];
|
|
137
|
+
return Array.isArray(v) ? v[0] : (v ?? '');
|
|
138
|
+
};
|
|
139
|
+
const answer = await GET(new Request('http://localhost' + path, {
|
|
140
|
+
headers: { 'x-reactll-timestamp': header('x-reactll-timestamp'), 'x-reactll-signature': header('x-reactll-signature') },
|
|
141
|
+
}));
|
|
142
|
+
res.statusCode = answer.status;
|
|
143
|
+
res.setHeader('Content-Type', 'application/json');
|
|
144
|
+
res.setHeader('Cache-Control', 'no-store');
|
|
145
|
+
res.end(await answer.text());
|
|
146
|
+
};
|
|
147
|
+
}
|
package/dist/token.d.ts
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* endpoint, the site's public key and its secret. Only the endpoint and the public key ever reach
|
|
6
6
|
* the browser.
|
|
7
7
|
*/
|
|
8
|
-
export declare const VERSION = "1.
|
|
8
|
+
export declare const VERSION = "1.1.1";
|
|
9
9
|
export interface Credentials {
|
|
10
10
|
/** Endpoint, e.g. https://connect.reactll.com/v1 */
|
|
11
11
|
u: string;
|
package/dist/token.js
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* endpoint, the site's public key and its secret. Only the endpoint and the public key ever reach
|
|
6
6
|
* the browser.
|
|
7
7
|
*/
|
|
8
|
-
export const VERSION = '1.
|
|
8
|
+
export const VERSION = '1.1.1';
|
|
9
9
|
export function credentials(token = process.env.REACTLL_CONNECT_TOKEN) {
|
|
10
10
|
const raw = (token ?? '').trim();
|
|
11
11
|
if (!raw.startsWith('rcc_'))
|
package/package.json
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@reactll/connect",
|
|
3
|
-
"version": "1.
|
|
4
|
-
"description": "Reactll Connect for Next.js: visits, clicks, form leads and site health for your Reactll dashboard.",
|
|
3
|
+
"version": "1.1.1",
|
|
4
|
+
"description": "Reactll Connect for Next.js and any Node.js app: visits, clicks, form leads and site health for your Reactll dashboard.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"nextjs",
|
|
7
|
+
"nodejs",
|
|
8
|
+
"express",
|
|
7
9
|
"analytics",
|
|
8
10
|
"leads",
|
|
9
11
|
"reactll"
|
|
@@ -47,5 +49,16 @@
|
|
|
47
49
|
"next": "^15.5.0",
|
|
48
50
|
"react": "^19.0.0",
|
|
49
51
|
"typescript": "^5.6.0"
|
|
52
|
+
},
|
|
53
|
+
"peerDependenciesMeta": {
|
|
54
|
+
"next": {
|
|
55
|
+
"optional": true
|
|
56
|
+
},
|
|
57
|
+
"react": {
|
|
58
|
+
"optional": true
|
|
59
|
+
}
|
|
60
|
+
},
|
|
61
|
+
"engines": {
|
|
62
|
+
"node": ">=18"
|
|
50
63
|
}
|
|
51
64
|
}
|