@xcript-dev/next 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/dist/chunk-Y6FXYEAI.mjs +10 -0
- package/dist/client.d.mts +37 -0
- package/dist/client.d.ts +37 -0
- package/dist/client.js +81 -0
- package/dist/client.mjs +62 -0
- package/dist/index.d.mts +56 -0
- package/dist/index.d.ts +56 -0
- package/dist/index.js +128 -0
- package/dist/index.mjs +107 -0
- package/dist/server.d.mts +51 -0
- package/dist/server.d.ts +51 -0
- package/dist/server.js +60 -0
- package/dist/server.mjs +35 -0
- package/package.json +63 -0
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
|
|
2
|
+
get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
|
|
3
|
+
}) : x)(function(x) {
|
|
4
|
+
if (typeof require !== "undefined") return require.apply(this, arguments);
|
|
5
|
+
throw Error('Dynamic require of "' + x + '" is not supported');
|
|
6
|
+
});
|
|
7
|
+
|
|
8
|
+
export {
|
|
9
|
+
__require
|
|
10
|
+
};
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
2
|
+
import { ReactNode } from 'react';
|
|
3
|
+
|
|
4
|
+
interface LicenseState {
|
|
5
|
+
/** Whether the license is valid. */
|
|
6
|
+
isValid: boolean;
|
|
7
|
+
/** Whether validation is in progress. */
|
|
8
|
+
isLoading: boolean;
|
|
9
|
+
/** Dynamic config from the Xcript dashboard. */
|
|
10
|
+
config: Record<string, string>;
|
|
11
|
+
/** Error message if validation failed. */
|
|
12
|
+
error: string | null;
|
|
13
|
+
}
|
|
14
|
+
interface XcriptProviderProps {
|
|
15
|
+
children: ReactNode;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Context provider that reads the license validation state from
|
|
19
|
+
* the middleware cookie. No client-side API calls needed.
|
|
20
|
+
*
|
|
21
|
+
* Wrap your app layout with this provider, then use `useLicense()`
|
|
22
|
+
* in any client component.
|
|
23
|
+
*/
|
|
24
|
+
declare function XcriptProvider({ children }: XcriptProviderProps): react_jsx_runtime.JSX.Element;
|
|
25
|
+
/**
|
|
26
|
+
* React hook to access the current license state.
|
|
27
|
+
*
|
|
28
|
+
* Must be used within a `<XcriptProvider>`.
|
|
29
|
+
*
|
|
30
|
+
* @example
|
|
31
|
+
* ```tsx
|
|
32
|
+
* const { isValid, config, isLoading } = useLicense()
|
|
33
|
+
* ```
|
|
34
|
+
*/
|
|
35
|
+
declare function useLicense(): LicenseState;
|
|
36
|
+
|
|
37
|
+
export { XcriptProvider, useLicense };
|
package/dist/client.d.ts
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
2
|
+
import { ReactNode } from 'react';
|
|
3
|
+
|
|
4
|
+
interface LicenseState {
|
|
5
|
+
/** Whether the license is valid. */
|
|
6
|
+
isValid: boolean;
|
|
7
|
+
/** Whether validation is in progress. */
|
|
8
|
+
isLoading: boolean;
|
|
9
|
+
/** Dynamic config from the Xcript dashboard. */
|
|
10
|
+
config: Record<string, string>;
|
|
11
|
+
/** Error message if validation failed. */
|
|
12
|
+
error: string | null;
|
|
13
|
+
}
|
|
14
|
+
interface XcriptProviderProps {
|
|
15
|
+
children: ReactNode;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Context provider that reads the license validation state from
|
|
19
|
+
* the middleware cookie. No client-side API calls needed.
|
|
20
|
+
*
|
|
21
|
+
* Wrap your app layout with this provider, then use `useLicense()`
|
|
22
|
+
* in any client component.
|
|
23
|
+
*/
|
|
24
|
+
declare function XcriptProvider({ children }: XcriptProviderProps): react_jsx_runtime.JSX.Element;
|
|
25
|
+
/**
|
|
26
|
+
* React hook to access the current license state.
|
|
27
|
+
*
|
|
28
|
+
* Must be used within a `<XcriptProvider>`.
|
|
29
|
+
*
|
|
30
|
+
* @example
|
|
31
|
+
* ```tsx
|
|
32
|
+
* const { isValid, config, isLoading } = useLicense()
|
|
33
|
+
* ```
|
|
34
|
+
*/
|
|
35
|
+
declare function useLicense(): LicenseState;
|
|
36
|
+
|
|
37
|
+
export { XcriptProvider, useLicense };
|
package/dist/client.js
ADDED
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
"use client";
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
7
|
+
var __export = (target, all) => {
|
|
8
|
+
for (var name in all)
|
|
9
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
10
|
+
};
|
|
11
|
+
var __copyProps = (to, from, except, desc) => {
|
|
12
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
13
|
+
for (let key of __getOwnPropNames(from))
|
|
14
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
15
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
16
|
+
}
|
|
17
|
+
return to;
|
|
18
|
+
};
|
|
19
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
20
|
+
|
|
21
|
+
// src/client.tsx
|
|
22
|
+
var client_exports = {};
|
|
23
|
+
__export(client_exports, {
|
|
24
|
+
XcriptProvider: () => XcriptProvider,
|
|
25
|
+
useLicense: () => useLicense
|
|
26
|
+
});
|
|
27
|
+
module.exports = __toCommonJS(client_exports);
|
|
28
|
+
var import_react = require("react");
|
|
29
|
+
var import_jsx_runtime = require("react/jsx-runtime");
|
|
30
|
+
var XcriptContext = (0, import_react.createContext)({
|
|
31
|
+
isValid: false,
|
|
32
|
+
isLoading: true,
|
|
33
|
+
config: {},
|
|
34
|
+
error: null
|
|
35
|
+
});
|
|
36
|
+
var COOKIE_NAME = "__xcript_status";
|
|
37
|
+
function XcriptProvider({ children }) {
|
|
38
|
+
const [state, setState] = (0, import_react.useState)({
|
|
39
|
+
isValid: false,
|
|
40
|
+
isLoading: true,
|
|
41
|
+
config: {},
|
|
42
|
+
error: null
|
|
43
|
+
});
|
|
44
|
+
(0, import_react.useEffect)(() => {
|
|
45
|
+
try {
|
|
46
|
+
const cookies = document.cookie.split(";").reduce((acc, c) => {
|
|
47
|
+
const [key, ...rest] = c.trim().split("=");
|
|
48
|
+
acc[key] = decodeURIComponent(rest.join("="));
|
|
49
|
+
return acc;
|
|
50
|
+
}, {});
|
|
51
|
+
const raw = cookies[COOKIE_NAME];
|
|
52
|
+
if (raw) {
|
|
53
|
+
const parsed = JSON.parse(raw);
|
|
54
|
+
setState({
|
|
55
|
+
isValid: !!parsed.valid,
|
|
56
|
+
isLoading: false,
|
|
57
|
+
config: parsed.config || {},
|
|
58
|
+
error: null
|
|
59
|
+
});
|
|
60
|
+
} else {
|
|
61
|
+
setState((prev) => ({ ...prev, isLoading: false }));
|
|
62
|
+
}
|
|
63
|
+
} catch {
|
|
64
|
+
setState({
|
|
65
|
+
isValid: false,
|
|
66
|
+
isLoading: false,
|
|
67
|
+
config: {},
|
|
68
|
+
error: "Failed to read license state"
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
}, []);
|
|
72
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(XcriptContext.Provider, { value: state, children });
|
|
73
|
+
}
|
|
74
|
+
function useLicense() {
|
|
75
|
+
return (0, import_react.useContext)(XcriptContext);
|
|
76
|
+
}
|
|
77
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
78
|
+
0 && (module.exports = {
|
|
79
|
+
XcriptProvider,
|
|
80
|
+
useLicense
|
|
81
|
+
});
|
package/dist/client.mjs
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import "./chunk-Y6FXYEAI.mjs";
|
|
3
|
+
|
|
4
|
+
// src/client.tsx
|
|
5
|
+
import {
|
|
6
|
+
createContext,
|
|
7
|
+
useContext,
|
|
8
|
+
useEffect,
|
|
9
|
+
useState
|
|
10
|
+
} from "react";
|
|
11
|
+
import { jsx } from "react/jsx-runtime";
|
|
12
|
+
var XcriptContext = createContext({
|
|
13
|
+
isValid: false,
|
|
14
|
+
isLoading: true,
|
|
15
|
+
config: {},
|
|
16
|
+
error: null
|
|
17
|
+
});
|
|
18
|
+
var COOKIE_NAME = "__xcript_status";
|
|
19
|
+
function XcriptProvider({ children }) {
|
|
20
|
+
const [state, setState] = useState({
|
|
21
|
+
isValid: false,
|
|
22
|
+
isLoading: true,
|
|
23
|
+
config: {},
|
|
24
|
+
error: null
|
|
25
|
+
});
|
|
26
|
+
useEffect(() => {
|
|
27
|
+
try {
|
|
28
|
+
const cookies = document.cookie.split(";").reduce((acc, c) => {
|
|
29
|
+
const [key, ...rest] = c.trim().split("=");
|
|
30
|
+
acc[key] = decodeURIComponent(rest.join("="));
|
|
31
|
+
return acc;
|
|
32
|
+
}, {});
|
|
33
|
+
const raw = cookies[COOKIE_NAME];
|
|
34
|
+
if (raw) {
|
|
35
|
+
const parsed = JSON.parse(raw);
|
|
36
|
+
setState({
|
|
37
|
+
isValid: !!parsed.valid,
|
|
38
|
+
isLoading: false,
|
|
39
|
+
config: parsed.config || {},
|
|
40
|
+
error: null
|
|
41
|
+
});
|
|
42
|
+
} else {
|
|
43
|
+
setState((prev) => ({ ...prev, isLoading: false }));
|
|
44
|
+
}
|
|
45
|
+
} catch {
|
|
46
|
+
setState({
|
|
47
|
+
isValid: false,
|
|
48
|
+
isLoading: false,
|
|
49
|
+
config: {},
|
|
50
|
+
error: "Failed to read license state"
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
}, []);
|
|
54
|
+
return /* @__PURE__ */ jsx(XcriptContext.Provider, { value: state, children });
|
|
55
|
+
}
|
|
56
|
+
function useLicense() {
|
|
57
|
+
return useContext(XcriptContext);
|
|
58
|
+
}
|
|
59
|
+
export {
|
|
60
|
+
XcriptProvider,
|
|
61
|
+
useLicense
|
|
62
|
+
};
|
package/dist/index.d.mts
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { NextRequest, NextResponse } from 'next/server';
|
|
2
|
+
export { ValidationResponse, XcriptConfig } from '@xcript-dev/sdk';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* @xcript/next — Next.js middleware for license validation.
|
|
6
|
+
*
|
|
7
|
+
* @example
|
|
8
|
+
* ```ts
|
|
9
|
+
* // middleware.ts
|
|
10
|
+
* import { withXcript } from '@xcript/next'
|
|
11
|
+
*
|
|
12
|
+
* export default withXcript({
|
|
13
|
+
* apiKey: process.env.XCRIPT_API_KEY!,
|
|
14
|
+
* licenseKey: process.env.XCRIPT_LICENSE_KEY!,
|
|
15
|
+
* protectedRoutes: ['/dashboard/*', '/api/*'],
|
|
16
|
+
* onInvalid: '/license-expired',
|
|
17
|
+
* })
|
|
18
|
+
*
|
|
19
|
+
* export const config = {
|
|
20
|
+
* matcher: ['/dashboard/:path*', '/api/:path*'],
|
|
21
|
+
* }
|
|
22
|
+
* ```
|
|
23
|
+
*/
|
|
24
|
+
|
|
25
|
+
interface XcriptMiddlewareConfig {
|
|
26
|
+
/** Your Xcript API key (starts with "xk_"). */
|
|
27
|
+
apiKey: string;
|
|
28
|
+
/** The license key to validate. */
|
|
29
|
+
licenseKey: string;
|
|
30
|
+
/** Ed25519 public key for signature verification (hex). Optional. */
|
|
31
|
+
publicKey?: string;
|
|
32
|
+
/** API base URL. Defaults to "https://api.xcript.dev". */
|
|
33
|
+
baseUrl?: string;
|
|
34
|
+
/**
|
|
35
|
+
* Glob patterns for routes that require a valid license.
|
|
36
|
+
* Use '*' as wildcard. Example: ['/dashboard/*', '/api/*']
|
|
37
|
+
*/
|
|
38
|
+
protectedRoutes?: string[];
|
|
39
|
+
/** Route to redirect to when the license is invalid. Default: returns 403. */
|
|
40
|
+
onInvalid?: string;
|
|
41
|
+
/**
|
|
42
|
+
* How often to re-validate (in seconds).
|
|
43
|
+
* During this window, the cached result is trusted.
|
|
44
|
+
* Default: 300 (5 minutes).
|
|
45
|
+
*/
|
|
46
|
+
revalidateInterval?: number;
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Create a Next.js middleware that validates the Xcript license.
|
|
50
|
+
*
|
|
51
|
+
* The middleware validates once, stores the result in a cookie,
|
|
52
|
+
* and re-validates after `revalidateInterval` seconds.
|
|
53
|
+
*/
|
|
54
|
+
declare function withXcript(config: XcriptMiddlewareConfig): (request: NextRequest) => Promise<NextResponse<unknown>>;
|
|
55
|
+
|
|
56
|
+
export { withXcript };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { NextRequest, NextResponse } from 'next/server';
|
|
2
|
+
export { ValidationResponse, XcriptConfig } from '@xcript-dev/sdk';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* @xcript/next — Next.js middleware for license validation.
|
|
6
|
+
*
|
|
7
|
+
* @example
|
|
8
|
+
* ```ts
|
|
9
|
+
* // middleware.ts
|
|
10
|
+
* import { withXcript } from '@xcript/next'
|
|
11
|
+
*
|
|
12
|
+
* export default withXcript({
|
|
13
|
+
* apiKey: process.env.XCRIPT_API_KEY!,
|
|
14
|
+
* licenseKey: process.env.XCRIPT_LICENSE_KEY!,
|
|
15
|
+
* protectedRoutes: ['/dashboard/*', '/api/*'],
|
|
16
|
+
* onInvalid: '/license-expired',
|
|
17
|
+
* })
|
|
18
|
+
*
|
|
19
|
+
* export const config = {
|
|
20
|
+
* matcher: ['/dashboard/:path*', '/api/:path*'],
|
|
21
|
+
* }
|
|
22
|
+
* ```
|
|
23
|
+
*/
|
|
24
|
+
|
|
25
|
+
interface XcriptMiddlewareConfig {
|
|
26
|
+
/** Your Xcript API key (starts with "xk_"). */
|
|
27
|
+
apiKey: string;
|
|
28
|
+
/** The license key to validate. */
|
|
29
|
+
licenseKey: string;
|
|
30
|
+
/** Ed25519 public key for signature verification (hex). Optional. */
|
|
31
|
+
publicKey?: string;
|
|
32
|
+
/** API base URL. Defaults to "https://api.xcript.dev". */
|
|
33
|
+
baseUrl?: string;
|
|
34
|
+
/**
|
|
35
|
+
* Glob patterns for routes that require a valid license.
|
|
36
|
+
* Use '*' as wildcard. Example: ['/dashboard/*', '/api/*']
|
|
37
|
+
*/
|
|
38
|
+
protectedRoutes?: string[];
|
|
39
|
+
/** Route to redirect to when the license is invalid. Default: returns 403. */
|
|
40
|
+
onInvalid?: string;
|
|
41
|
+
/**
|
|
42
|
+
* How often to re-validate (in seconds).
|
|
43
|
+
* During this window, the cached result is trusted.
|
|
44
|
+
* Default: 300 (5 minutes).
|
|
45
|
+
*/
|
|
46
|
+
revalidateInterval?: number;
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Create a Next.js middleware that validates the Xcript license.
|
|
50
|
+
*
|
|
51
|
+
* The middleware validates once, stores the result in a cookie,
|
|
52
|
+
* and re-validates after `revalidateInterval` seconds.
|
|
53
|
+
*/
|
|
54
|
+
declare function withXcript(config: XcriptMiddlewareConfig): (request: NextRequest) => Promise<NextResponse<unknown>>;
|
|
55
|
+
|
|
56
|
+
export { withXcript };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/index.ts
|
|
21
|
+
var index_exports = {};
|
|
22
|
+
__export(index_exports, {
|
|
23
|
+
withXcript: () => withXcript
|
|
24
|
+
});
|
|
25
|
+
module.exports = __toCommonJS(index_exports);
|
|
26
|
+
var import_server = require("next/server");
|
|
27
|
+
var COOKIE_NAME = "__xcript_status";
|
|
28
|
+
var DEFAULT_REVALIDATE = 300;
|
|
29
|
+
function withXcript(config) {
|
|
30
|
+
const baseUrl = (config.baseUrl || "https://api.xcript.dev").replace(/\/+$/, "");
|
|
31
|
+
const revalidate = config.revalidateInterval ?? DEFAULT_REVALIDATE;
|
|
32
|
+
return async function middleware(request) {
|
|
33
|
+
const { pathname } = request.nextUrl;
|
|
34
|
+
if (config.protectedRoutes && !isProtected(pathname, config.protectedRoutes)) {
|
|
35
|
+
return import_server.NextResponse.next();
|
|
36
|
+
}
|
|
37
|
+
const cached = request.cookies.get(COOKIE_NAME)?.value;
|
|
38
|
+
if (cached) {
|
|
39
|
+
try {
|
|
40
|
+
const parsed = JSON.parse(cached);
|
|
41
|
+
const age = (Date.now() - parsed.timestamp) / 1e3;
|
|
42
|
+
if (age < revalidate && parsed.valid) {
|
|
43
|
+
return import_server.NextResponse.next();
|
|
44
|
+
}
|
|
45
|
+
} catch {
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
try {
|
|
49
|
+
const res = await fetch(`${baseUrl}/api/v1/validate`, {
|
|
50
|
+
method: "POST",
|
|
51
|
+
headers: {
|
|
52
|
+
"Content-Type": "application/json",
|
|
53
|
+
"X-Api-Key": config.apiKey
|
|
54
|
+
},
|
|
55
|
+
body: JSON.stringify({
|
|
56
|
+
license_key: config.licenseKey,
|
|
57
|
+
machine_id: `nextjs-${hostname()}`
|
|
58
|
+
})
|
|
59
|
+
});
|
|
60
|
+
if (!res.ok) {
|
|
61
|
+
return handleInvalid(request, config);
|
|
62
|
+
}
|
|
63
|
+
const data = await res.json();
|
|
64
|
+
if (!data.valid) {
|
|
65
|
+
return handleInvalid(request, config);
|
|
66
|
+
}
|
|
67
|
+
const response = import_server.NextResponse.next();
|
|
68
|
+
response.cookies.set(COOKIE_NAME, JSON.stringify({
|
|
69
|
+
valid: true,
|
|
70
|
+
timestamp: Date.now(),
|
|
71
|
+
config: data.config || {}
|
|
72
|
+
}), {
|
|
73
|
+
httpOnly: true,
|
|
74
|
+
secure: process.env.NODE_ENV === "production",
|
|
75
|
+
sameSite: "lax",
|
|
76
|
+
maxAge: revalidate,
|
|
77
|
+
path: "/"
|
|
78
|
+
});
|
|
79
|
+
return response;
|
|
80
|
+
} catch {
|
|
81
|
+
if (cached) {
|
|
82
|
+
try {
|
|
83
|
+
const parsed = JSON.parse(cached);
|
|
84
|
+
if (parsed.valid) {
|
|
85
|
+
return import_server.NextResponse.next();
|
|
86
|
+
}
|
|
87
|
+
} catch {
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
return handleInvalid(request, config);
|
|
91
|
+
}
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
function handleInvalid(request, config) {
|
|
95
|
+
if (config.onInvalid) {
|
|
96
|
+
const url = request.nextUrl.clone();
|
|
97
|
+
url.pathname = config.onInvalid;
|
|
98
|
+
const response = import_server.NextResponse.redirect(url);
|
|
99
|
+
response.cookies.delete(COOKIE_NAME);
|
|
100
|
+
return response;
|
|
101
|
+
}
|
|
102
|
+
return new import_server.NextResponse(
|
|
103
|
+
JSON.stringify({ error: "License validation failed" }),
|
|
104
|
+
{
|
|
105
|
+
status: 403,
|
|
106
|
+
headers: { "Content-Type": "application/json" }
|
|
107
|
+
}
|
|
108
|
+
);
|
|
109
|
+
}
|
|
110
|
+
function isProtected(pathname, patterns) {
|
|
111
|
+
return patterns.some((pattern) => {
|
|
112
|
+
const regex = new RegExp(
|
|
113
|
+
"^" + pattern.replace(/\*/g, ".*").replace(/\//g, "\\/") + "$"
|
|
114
|
+
);
|
|
115
|
+
return regex.test(pathname);
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
function hostname() {
|
|
119
|
+
try {
|
|
120
|
+
return require("os").hostname();
|
|
121
|
+
} catch {
|
|
122
|
+
return "unknown";
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
126
|
+
0 && (module.exports = {
|
|
127
|
+
withXcript
|
|
128
|
+
});
|
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import {
|
|
2
|
+
__require
|
|
3
|
+
} from "./chunk-Y6FXYEAI.mjs";
|
|
4
|
+
|
|
5
|
+
// src/index.ts
|
|
6
|
+
import { NextResponse } from "next/server";
|
|
7
|
+
var COOKIE_NAME = "__xcript_status";
|
|
8
|
+
var DEFAULT_REVALIDATE = 300;
|
|
9
|
+
function withXcript(config) {
|
|
10
|
+
const baseUrl = (config.baseUrl || "https://api.xcript.dev").replace(/\/+$/, "");
|
|
11
|
+
const revalidate = config.revalidateInterval ?? DEFAULT_REVALIDATE;
|
|
12
|
+
return async function middleware(request) {
|
|
13
|
+
const { pathname } = request.nextUrl;
|
|
14
|
+
if (config.protectedRoutes && !isProtected(pathname, config.protectedRoutes)) {
|
|
15
|
+
return NextResponse.next();
|
|
16
|
+
}
|
|
17
|
+
const cached = request.cookies.get(COOKIE_NAME)?.value;
|
|
18
|
+
if (cached) {
|
|
19
|
+
try {
|
|
20
|
+
const parsed = JSON.parse(cached);
|
|
21
|
+
const age = (Date.now() - parsed.timestamp) / 1e3;
|
|
22
|
+
if (age < revalidate && parsed.valid) {
|
|
23
|
+
return NextResponse.next();
|
|
24
|
+
}
|
|
25
|
+
} catch {
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
try {
|
|
29
|
+
const res = await fetch(`${baseUrl}/api/v1/validate`, {
|
|
30
|
+
method: "POST",
|
|
31
|
+
headers: {
|
|
32
|
+
"Content-Type": "application/json",
|
|
33
|
+
"X-Api-Key": config.apiKey
|
|
34
|
+
},
|
|
35
|
+
body: JSON.stringify({
|
|
36
|
+
license_key: config.licenseKey,
|
|
37
|
+
machine_id: `nextjs-${hostname()}`
|
|
38
|
+
})
|
|
39
|
+
});
|
|
40
|
+
if (!res.ok) {
|
|
41
|
+
return handleInvalid(request, config);
|
|
42
|
+
}
|
|
43
|
+
const data = await res.json();
|
|
44
|
+
if (!data.valid) {
|
|
45
|
+
return handleInvalid(request, config);
|
|
46
|
+
}
|
|
47
|
+
const response = NextResponse.next();
|
|
48
|
+
response.cookies.set(COOKIE_NAME, JSON.stringify({
|
|
49
|
+
valid: true,
|
|
50
|
+
timestamp: Date.now(),
|
|
51
|
+
config: data.config || {}
|
|
52
|
+
}), {
|
|
53
|
+
httpOnly: true,
|
|
54
|
+
secure: process.env.NODE_ENV === "production",
|
|
55
|
+
sameSite: "lax",
|
|
56
|
+
maxAge: revalidate,
|
|
57
|
+
path: "/"
|
|
58
|
+
});
|
|
59
|
+
return response;
|
|
60
|
+
} catch {
|
|
61
|
+
if (cached) {
|
|
62
|
+
try {
|
|
63
|
+
const parsed = JSON.parse(cached);
|
|
64
|
+
if (parsed.valid) {
|
|
65
|
+
return NextResponse.next();
|
|
66
|
+
}
|
|
67
|
+
} catch {
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
return handleInvalid(request, config);
|
|
71
|
+
}
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
function handleInvalid(request, config) {
|
|
75
|
+
if (config.onInvalid) {
|
|
76
|
+
const url = request.nextUrl.clone();
|
|
77
|
+
url.pathname = config.onInvalid;
|
|
78
|
+
const response = NextResponse.redirect(url);
|
|
79
|
+
response.cookies.delete(COOKIE_NAME);
|
|
80
|
+
return response;
|
|
81
|
+
}
|
|
82
|
+
return new NextResponse(
|
|
83
|
+
JSON.stringify({ error: "License validation failed" }),
|
|
84
|
+
{
|
|
85
|
+
status: 403,
|
|
86
|
+
headers: { "Content-Type": "application/json" }
|
|
87
|
+
}
|
|
88
|
+
);
|
|
89
|
+
}
|
|
90
|
+
function isProtected(pathname, patterns) {
|
|
91
|
+
return patterns.some((pattern) => {
|
|
92
|
+
const regex = new RegExp(
|
|
93
|
+
"^" + pattern.replace(/\*/g, ".*").replace(/\//g, "\\/") + "$"
|
|
94
|
+
);
|
|
95
|
+
return regex.test(pathname);
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
function hostname() {
|
|
99
|
+
try {
|
|
100
|
+
return __require("os").hostname();
|
|
101
|
+
} catch {
|
|
102
|
+
return "unknown";
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
export {
|
|
106
|
+
withXcript
|
|
107
|
+
};
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @xcript/next/server — Server-side utilities for license validation.
|
|
3
|
+
*
|
|
4
|
+
* @example
|
|
5
|
+
* ```ts
|
|
6
|
+
* // Server Action or Route Handler
|
|
7
|
+
* import { requireLicense, getLicense } from '@xcript/next/server'
|
|
8
|
+
*
|
|
9
|
+
* // Throws if license is invalid
|
|
10
|
+
* export async function POST() {
|
|
11
|
+
* const license = await requireLicense()
|
|
12
|
+
* // license.config.max_users → "50"
|
|
13
|
+
* }
|
|
14
|
+
*
|
|
15
|
+
* // Returns null if invalid (no throw)
|
|
16
|
+
* export async function GET() {
|
|
17
|
+
* const license = await getLicense()
|
|
18
|
+
* if (!license) return Response.json({ error: 'No license' }, { status: 403 })
|
|
19
|
+
* }
|
|
20
|
+
* ```
|
|
21
|
+
*/
|
|
22
|
+
interface LicenseData {
|
|
23
|
+
/** Whether the license is valid. */
|
|
24
|
+
valid: boolean;
|
|
25
|
+
/** Dynamic config from the Xcript dashboard. */
|
|
26
|
+
config: Record<string, string>;
|
|
27
|
+
/** When the validation was performed (epoch ms). */
|
|
28
|
+
timestamp: number;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Get the current license state from the middleware cookie.
|
|
32
|
+
* Returns null if no valid license is found.
|
|
33
|
+
*
|
|
34
|
+
* Use this in Server Components, Server Actions, or Route Handlers.
|
|
35
|
+
*/
|
|
36
|
+
declare function getLicense(): Promise<LicenseData | null>;
|
|
37
|
+
/**
|
|
38
|
+
* Require a valid license. Throws an error if the license is invalid.
|
|
39
|
+
*
|
|
40
|
+
* Use this at the top of protected Server Actions or Route Handlers.
|
|
41
|
+
*
|
|
42
|
+
* @throws Error if the license is not valid
|
|
43
|
+
*/
|
|
44
|
+
declare function requireLicense(): Promise<LicenseData>;
|
|
45
|
+
/**
|
|
46
|
+
* Get a specific config value from the license.
|
|
47
|
+
* Returns undefined if the license is invalid or the key doesn't exist.
|
|
48
|
+
*/
|
|
49
|
+
declare function getLicenseConfig(key: string): Promise<string | undefined>;
|
|
50
|
+
|
|
51
|
+
export { getLicense, getLicenseConfig, requireLicense };
|
package/dist/server.d.ts
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @xcript/next/server — Server-side utilities for license validation.
|
|
3
|
+
*
|
|
4
|
+
* @example
|
|
5
|
+
* ```ts
|
|
6
|
+
* // Server Action or Route Handler
|
|
7
|
+
* import { requireLicense, getLicense } from '@xcript/next/server'
|
|
8
|
+
*
|
|
9
|
+
* // Throws if license is invalid
|
|
10
|
+
* export async function POST() {
|
|
11
|
+
* const license = await requireLicense()
|
|
12
|
+
* // license.config.max_users → "50"
|
|
13
|
+
* }
|
|
14
|
+
*
|
|
15
|
+
* // Returns null if invalid (no throw)
|
|
16
|
+
* export async function GET() {
|
|
17
|
+
* const license = await getLicense()
|
|
18
|
+
* if (!license) return Response.json({ error: 'No license' }, { status: 403 })
|
|
19
|
+
* }
|
|
20
|
+
* ```
|
|
21
|
+
*/
|
|
22
|
+
interface LicenseData {
|
|
23
|
+
/** Whether the license is valid. */
|
|
24
|
+
valid: boolean;
|
|
25
|
+
/** Dynamic config from the Xcript dashboard. */
|
|
26
|
+
config: Record<string, string>;
|
|
27
|
+
/** When the validation was performed (epoch ms). */
|
|
28
|
+
timestamp: number;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Get the current license state from the middleware cookie.
|
|
32
|
+
* Returns null if no valid license is found.
|
|
33
|
+
*
|
|
34
|
+
* Use this in Server Components, Server Actions, or Route Handlers.
|
|
35
|
+
*/
|
|
36
|
+
declare function getLicense(): Promise<LicenseData | null>;
|
|
37
|
+
/**
|
|
38
|
+
* Require a valid license. Throws an error if the license is invalid.
|
|
39
|
+
*
|
|
40
|
+
* Use this at the top of protected Server Actions or Route Handlers.
|
|
41
|
+
*
|
|
42
|
+
* @throws Error if the license is not valid
|
|
43
|
+
*/
|
|
44
|
+
declare function requireLicense(): Promise<LicenseData>;
|
|
45
|
+
/**
|
|
46
|
+
* Get a specific config value from the license.
|
|
47
|
+
* Returns undefined if the license is invalid or the key doesn't exist.
|
|
48
|
+
*/
|
|
49
|
+
declare function getLicenseConfig(key: string): Promise<string | undefined>;
|
|
50
|
+
|
|
51
|
+
export { getLicense, getLicenseConfig, requireLicense };
|
package/dist/server.js
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/server.ts
|
|
21
|
+
var server_exports = {};
|
|
22
|
+
__export(server_exports, {
|
|
23
|
+
getLicense: () => getLicense,
|
|
24
|
+
getLicenseConfig: () => getLicenseConfig,
|
|
25
|
+
requireLicense: () => requireLicense
|
|
26
|
+
});
|
|
27
|
+
module.exports = __toCommonJS(server_exports);
|
|
28
|
+
var import_headers = require("next/headers");
|
|
29
|
+
var COOKIE_NAME = "__xcript_status";
|
|
30
|
+
async function getLicense() {
|
|
31
|
+
try {
|
|
32
|
+
const cookieStore = await (0, import_headers.cookies)();
|
|
33
|
+
const raw = cookieStore.get(COOKIE_NAME)?.value;
|
|
34
|
+
if (!raw) return null;
|
|
35
|
+
const parsed = JSON.parse(raw);
|
|
36
|
+
if (!parsed.valid) return null;
|
|
37
|
+
return parsed;
|
|
38
|
+
} catch {
|
|
39
|
+
return null;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
async function requireLicense() {
|
|
43
|
+
const license = await getLicense();
|
|
44
|
+
if (!license) {
|
|
45
|
+
throw new Error(
|
|
46
|
+
"Xcript: License validation required. Ensure withXcript() middleware is configured."
|
|
47
|
+
);
|
|
48
|
+
}
|
|
49
|
+
return license;
|
|
50
|
+
}
|
|
51
|
+
async function getLicenseConfig(key) {
|
|
52
|
+
const license = await getLicense();
|
|
53
|
+
return license?.config[key];
|
|
54
|
+
}
|
|
55
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
56
|
+
0 && (module.exports = {
|
|
57
|
+
getLicense,
|
|
58
|
+
getLicenseConfig,
|
|
59
|
+
requireLicense
|
|
60
|
+
});
|
package/dist/server.mjs
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import "./chunk-Y6FXYEAI.mjs";
|
|
2
|
+
|
|
3
|
+
// src/server.ts
|
|
4
|
+
import { cookies } from "next/headers";
|
|
5
|
+
var COOKIE_NAME = "__xcript_status";
|
|
6
|
+
async function getLicense() {
|
|
7
|
+
try {
|
|
8
|
+
const cookieStore = await cookies();
|
|
9
|
+
const raw = cookieStore.get(COOKIE_NAME)?.value;
|
|
10
|
+
if (!raw) return null;
|
|
11
|
+
const parsed = JSON.parse(raw);
|
|
12
|
+
if (!parsed.valid) return null;
|
|
13
|
+
return parsed;
|
|
14
|
+
} catch {
|
|
15
|
+
return null;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
async function requireLicense() {
|
|
19
|
+
const license = await getLicense();
|
|
20
|
+
if (!license) {
|
|
21
|
+
throw new Error(
|
|
22
|
+
"Xcript: License validation required. Ensure withXcript() middleware is configured."
|
|
23
|
+
);
|
|
24
|
+
}
|
|
25
|
+
return license;
|
|
26
|
+
}
|
|
27
|
+
async function getLicenseConfig(key) {
|
|
28
|
+
const license = await getLicense();
|
|
29
|
+
return license?.config[key];
|
|
30
|
+
}
|
|
31
|
+
export {
|
|
32
|
+
getLicense,
|
|
33
|
+
getLicenseConfig,
|
|
34
|
+
requireLicense
|
|
35
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@xcript-dev/next",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Next.js integration for Xcript — middleware, hooks, and server utilities for license validation",
|
|
5
|
+
"main": "./dist/index.js",
|
|
6
|
+
"module": "./dist/index.mjs",
|
|
7
|
+
"types": "./dist/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"import": "./dist/index.mjs",
|
|
11
|
+
"require": "./dist/index.js",
|
|
12
|
+
"types": "./dist/index.d.ts"
|
|
13
|
+
},
|
|
14
|
+
"./client": {
|
|
15
|
+
"import": "./dist/client.mjs",
|
|
16
|
+
"require": "./dist/client.js",
|
|
17
|
+
"types": "./dist/client.d.ts"
|
|
18
|
+
},
|
|
19
|
+
"./server": {
|
|
20
|
+
"import": "./dist/server.mjs",
|
|
21
|
+
"require": "./dist/server.js",
|
|
22
|
+
"types": "./dist/server.d.ts"
|
|
23
|
+
}
|
|
24
|
+
},
|
|
25
|
+
"files": [
|
|
26
|
+
"dist"
|
|
27
|
+
],
|
|
28
|
+
"scripts": {
|
|
29
|
+
"build": "tsup src/index.ts src/client.tsx src/server.ts --format cjs,esm --dts --clean --external react --external next --external @xcript-dev/sdk",
|
|
30
|
+
"dev": "tsup src/index.ts src/client.tsx src/server.ts --format cjs,esm --dts --watch --external react --external next --external @xcript-dev/sdk"
|
|
31
|
+
},
|
|
32
|
+
"keywords": [
|
|
33
|
+
"nextjs",
|
|
34
|
+
"licensing",
|
|
35
|
+
"xcript",
|
|
36
|
+
"middleware",
|
|
37
|
+
"react-hook"
|
|
38
|
+
],
|
|
39
|
+
"license": "MIT",
|
|
40
|
+
"engines": {
|
|
41
|
+
"node": ">=18.0.0"
|
|
42
|
+
},
|
|
43
|
+
"sideEffects": false,
|
|
44
|
+
"repository": {
|
|
45
|
+
"type": "git",
|
|
46
|
+
"url": "https://github.com/xcript/xcript-js",
|
|
47
|
+
"directory": "packages/next"
|
|
48
|
+
},
|
|
49
|
+
"dependencies": {
|
|
50
|
+
"@xcript-dev/sdk": "*"
|
|
51
|
+
},
|
|
52
|
+
"peerDependencies": {
|
|
53
|
+
"next": ">=14.0.0",
|
|
54
|
+
"react": ">=18.0.0"
|
|
55
|
+
},
|
|
56
|
+
"devDependencies": {
|
|
57
|
+
"typescript": "^5.4.0",
|
|
58
|
+
"tsup": "^8.0.0",
|
|
59
|
+
"@types/react": "^18.0.0",
|
|
60
|
+
"next": "^15.0.0",
|
|
61
|
+
"react": "^18.0.0"
|
|
62
|
+
}
|
|
63
|
+
}
|