@scadable/privacy 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/README.md +60 -0
- package/dist/index.cjs +44 -0
- package/dist/index.d.cts +66 -0
- package/dist/index.d.ts +66 -0
- package/dist/index.js +40 -0
- package/package.json +57 -0
package/README.md
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
# @scadable/privacy
|
|
2
|
+
|
|
3
|
+
Render your always-current privacy policy in a Next.js app. You publish the policy once
|
|
4
|
+
in the SCADABLE app; this component shows whatever version is currently published, so
|
|
5
|
+
updating your policy never means a redeploy on your side.
|
|
6
|
+
|
|
7
|
+
## Install
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
npm install @scadable/privacy
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## Use (App Router)
|
|
14
|
+
|
|
15
|
+
Drop the component on your privacy page. It is a Server Component, so the policy text is
|
|
16
|
+
server-rendered (good for SEO) and cached.
|
|
17
|
+
|
|
18
|
+
```tsx
|
|
19
|
+
import { PrivacyPolicy } from '@scadable/privacy';
|
|
20
|
+
|
|
21
|
+
export default function PrivacyPage() {
|
|
22
|
+
return (
|
|
23
|
+
<main>
|
|
24
|
+
<PrivacyPolicy token="YOUR_PUBLIC_TOKEN" />
|
|
25
|
+
</main>
|
|
26
|
+
);
|
|
27
|
+
}
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
Get `YOUR_PUBLIC_TOKEN` from the SCADABLE app after you publish a policy.
|
|
31
|
+
|
|
32
|
+
### Options
|
|
33
|
+
|
|
34
|
+
| Prop | Type | Default | Notes |
|
|
35
|
+
| --- | --- | --- | --- |
|
|
36
|
+
| `token` | `string` | required | Your scope's public token. |
|
|
37
|
+
| `className` | `string` | none | Class on the wrapper element. |
|
|
38
|
+
| `showVersion` | `boolean` | `false` | Show a "Version N, last updated ..." line. |
|
|
39
|
+
| `revalidate` | `number \| false` | `3600` | Next.js ISR seconds. `false` = always fresh. |
|
|
40
|
+
| `docType` | `string` | `"privacy_policy"` | Which document to render. |
|
|
41
|
+
| `baseUrl` | `string` | `https://api.scadable.com` | Override the API base. |
|
|
42
|
+
|
|
43
|
+
## Just the data
|
|
44
|
+
|
|
45
|
+
If you want to render it yourself:
|
|
46
|
+
|
|
47
|
+
```ts
|
|
48
|
+
import { fetchPolicy } from '@scadable/privacy';
|
|
49
|
+
|
|
50
|
+
const policy = await fetchPolicy('YOUR_PUBLIC_TOKEN');
|
|
51
|
+
// { scope_name, domain, version, effective_date, updated_at, html }
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
`policy.html` is an HTML content fragment with scoped styles (class `scadable-policy`),
|
|
55
|
+
safe to inject inline.
|
|
56
|
+
|
|
57
|
+
## Notes
|
|
58
|
+
|
|
59
|
+
- This package only talks to the public SCADABLE API. It stores no secrets.
|
|
60
|
+
- The rendered HTML is your own published policy content from the SCADABLE API.
|
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var jsxRuntime = require('react/jsx-runtime');
|
|
4
|
+
|
|
5
|
+
// src/client.ts
|
|
6
|
+
var DEFAULT_BASE_URL = "https://api.scadable.com";
|
|
7
|
+
async function fetchPolicy(token, options = {}) {
|
|
8
|
+
if (!token) {
|
|
9
|
+
throw new Error("@scadable/privacy: a policy token is required");
|
|
10
|
+
}
|
|
11
|
+
const baseUrl = (options.baseUrl ?? DEFAULT_BASE_URL).replace(/\/$/, "");
|
|
12
|
+
const docType = options.docType ?? "privacy_policy";
|
|
13
|
+
const revalidate = options.revalidate ?? 3600;
|
|
14
|
+
const url = `${baseUrl}/policy/${encodeURIComponent(token)}?doc_type=${encodeURIComponent(docType)}&format=json`;
|
|
15
|
+
const init = revalidate === false ? { cache: "no-store" } : { next: { revalidate } };
|
|
16
|
+
const res = await fetch(url, init);
|
|
17
|
+
if (!res.ok) {
|
|
18
|
+
throw new Error(`@scadable/privacy: failed to load policy (${res.status}) for token "${token}"`);
|
|
19
|
+
}
|
|
20
|
+
return await res.json();
|
|
21
|
+
}
|
|
22
|
+
async function PrivacyPolicy({
|
|
23
|
+
token,
|
|
24
|
+
className,
|
|
25
|
+
showVersion = false,
|
|
26
|
+
...options
|
|
27
|
+
}) {
|
|
28
|
+
const policy = await fetchPolicy(token, options);
|
|
29
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className, children: [
|
|
30
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { dangerouslySetInnerHTML: { __html: policy.html } }),
|
|
31
|
+
showVersion && policy.updated_at ? /* @__PURE__ */ jsxRuntime.jsxs("p", { style: { fontSize: 12, color: "#9ca3af", marginTop: 24 }, children: [
|
|
32
|
+
"Version ",
|
|
33
|
+
policy.version,
|
|
34
|
+
". Last updated",
|
|
35
|
+
" ",
|
|
36
|
+
new Date(policy.updated_at).toLocaleDateString(),
|
|
37
|
+
"."
|
|
38
|
+
] }) : null
|
|
39
|
+
] });
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
exports.DEFAULT_BASE_URL = DEFAULT_BASE_URL;
|
|
43
|
+
exports.PrivacyPolicy = PrivacyPolicy;
|
|
44
|
+
exports.fetchPolicy = fetchPolicy;
|
package/dist/index.d.cts
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
|
|
3
|
+
interface Policy {
|
|
4
|
+
/** The name you gave the scope (your company / site). */
|
|
5
|
+
scope_name: string;
|
|
6
|
+
domain: string;
|
|
7
|
+
/** "privacy_policy" today; more document types later. */
|
|
8
|
+
doc_type: string;
|
|
9
|
+
/** The published version number that is currently live. */
|
|
10
|
+
version: number;
|
|
11
|
+
effective_date: string;
|
|
12
|
+
/** ISO timestamp of when this version was published, or null. */
|
|
13
|
+
updated_at: string | null;
|
|
14
|
+
/** The rendered policy as an HTML content fragment (no <html> wrapper). */
|
|
15
|
+
html: string;
|
|
16
|
+
}
|
|
17
|
+
interface FetchPolicyOptions {
|
|
18
|
+
/** Which document to fetch. Default "privacy_policy". */
|
|
19
|
+
docType?: string;
|
|
20
|
+
/** Override the API base. Default "https://api.scadable.com". */
|
|
21
|
+
baseUrl?: string;
|
|
22
|
+
/**
|
|
23
|
+
* Next.js ISR revalidation, in seconds. The policy is server-rendered and
|
|
24
|
+
* cached for this long, then refreshed, so it stays current without a fetch
|
|
25
|
+
* on every request. Pass false to always fetch fresh. Default 3600 (1 hour).
|
|
26
|
+
*/
|
|
27
|
+
revalidate?: number | false;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
declare const DEFAULT_BASE_URL = "https://api.scadable.com";
|
|
31
|
+
/**
|
|
32
|
+
* Fetch the currently published policy for a public token.
|
|
33
|
+
*
|
|
34
|
+
* Works anywhere `fetch` is available. In a Next.js Server Component it uses ISR
|
|
35
|
+
* caching (the `revalidate` option) so the policy is server-rendered and cached,
|
|
36
|
+
* then refreshed, which keeps it both fast and current.
|
|
37
|
+
*/
|
|
38
|
+
declare function fetchPolicy(token: string, options?: FetchPolicyOptions): Promise<Policy>;
|
|
39
|
+
|
|
40
|
+
interface PrivacyPolicyProps extends FetchPolicyOptions {
|
|
41
|
+
/** The public token from the SCADABLE app. */
|
|
42
|
+
token: string;
|
|
43
|
+
/** Optional wrapper class so you can style/position the policy. */
|
|
44
|
+
className?: string;
|
|
45
|
+
/** Show a small "Version N, last updated ..." line under the policy. Default false. */
|
|
46
|
+
showVersion?: boolean;
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* A React Server Component that renders your always-current privacy policy.
|
|
50
|
+
*
|
|
51
|
+
* It is server-rendered, so the legal text is in the initial HTML (crawlable),
|
|
52
|
+
* and cached via Next.js ISR (the `revalidate` option), so it stays current
|
|
53
|
+
* without fetching on every request. The HTML comes from the SCADABLE API
|
|
54
|
+
* (your own published, trusted content), injected as a content fragment.
|
|
55
|
+
*
|
|
56
|
+
* ```tsx
|
|
57
|
+
* import { PrivacyPolicy } from '@scadable/privacy';
|
|
58
|
+
*
|
|
59
|
+
* export default function Page() {
|
|
60
|
+
* return <PrivacyPolicy token="YOUR_PUBLIC_TOKEN" />;
|
|
61
|
+
* }
|
|
62
|
+
* ```
|
|
63
|
+
*/
|
|
64
|
+
declare function PrivacyPolicy({ token, className, showVersion, ...options }: PrivacyPolicyProps): Promise<React.JSX.Element>;
|
|
65
|
+
|
|
66
|
+
export { DEFAULT_BASE_URL, type FetchPolicyOptions, type Policy, PrivacyPolicy, type PrivacyPolicyProps, fetchPolicy };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
|
|
3
|
+
interface Policy {
|
|
4
|
+
/** The name you gave the scope (your company / site). */
|
|
5
|
+
scope_name: string;
|
|
6
|
+
domain: string;
|
|
7
|
+
/** "privacy_policy" today; more document types later. */
|
|
8
|
+
doc_type: string;
|
|
9
|
+
/** The published version number that is currently live. */
|
|
10
|
+
version: number;
|
|
11
|
+
effective_date: string;
|
|
12
|
+
/** ISO timestamp of when this version was published, or null. */
|
|
13
|
+
updated_at: string | null;
|
|
14
|
+
/** The rendered policy as an HTML content fragment (no <html> wrapper). */
|
|
15
|
+
html: string;
|
|
16
|
+
}
|
|
17
|
+
interface FetchPolicyOptions {
|
|
18
|
+
/** Which document to fetch. Default "privacy_policy". */
|
|
19
|
+
docType?: string;
|
|
20
|
+
/** Override the API base. Default "https://api.scadable.com". */
|
|
21
|
+
baseUrl?: string;
|
|
22
|
+
/**
|
|
23
|
+
* Next.js ISR revalidation, in seconds. The policy is server-rendered and
|
|
24
|
+
* cached for this long, then refreshed, so it stays current without a fetch
|
|
25
|
+
* on every request. Pass false to always fetch fresh. Default 3600 (1 hour).
|
|
26
|
+
*/
|
|
27
|
+
revalidate?: number | false;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
declare const DEFAULT_BASE_URL = "https://api.scadable.com";
|
|
31
|
+
/**
|
|
32
|
+
* Fetch the currently published policy for a public token.
|
|
33
|
+
*
|
|
34
|
+
* Works anywhere `fetch` is available. In a Next.js Server Component it uses ISR
|
|
35
|
+
* caching (the `revalidate` option) so the policy is server-rendered and cached,
|
|
36
|
+
* then refreshed, which keeps it both fast and current.
|
|
37
|
+
*/
|
|
38
|
+
declare function fetchPolicy(token: string, options?: FetchPolicyOptions): Promise<Policy>;
|
|
39
|
+
|
|
40
|
+
interface PrivacyPolicyProps extends FetchPolicyOptions {
|
|
41
|
+
/** The public token from the SCADABLE app. */
|
|
42
|
+
token: string;
|
|
43
|
+
/** Optional wrapper class so you can style/position the policy. */
|
|
44
|
+
className?: string;
|
|
45
|
+
/** Show a small "Version N, last updated ..." line under the policy. Default false. */
|
|
46
|
+
showVersion?: boolean;
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* A React Server Component that renders your always-current privacy policy.
|
|
50
|
+
*
|
|
51
|
+
* It is server-rendered, so the legal text is in the initial HTML (crawlable),
|
|
52
|
+
* and cached via Next.js ISR (the `revalidate` option), so it stays current
|
|
53
|
+
* without fetching on every request. The HTML comes from the SCADABLE API
|
|
54
|
+
* (your own published, trusted content), injected as a content fragment.
|
|
55
|
+
*
|
|
56
|
+
* ```tsx
|
|
57
|
+
* import { PrivacyPolicy } from '@scadable/privacy';
|
|
58
|
+
*
|
|
59
|
+
* export default function Page() {
|
|
60
|
+
* return <PrivacyPolicy token="YOUR_PUBLIC_TOKEN" />;
|
|
61
|
+
* }
|
|
62
|
+
* ```
|
|
63
|
+
*/
|
|
64
|
+
declare function PrivacyPolicy({ token, className, showVersion, ...options }: PrivacyPolicyProps): Promise<React.JSX.Element>;
|
|
65
|
+
|
|
66
|
+
export { DEFAULT_BASE_URL, type FetchPolicyOptions, type Policy, PrivacyPolicy, type PrivacyPolicyProps, fetchPolicy };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { jsxs, jsx } from 'react/jsx-runtime';
|
|
2
|
+
|
|
3
|
+
// src/client.ts
|
|
4
|
+
var DEFAULT_BASE_URL = "https://api.scadable.com";
|
|
5
|
+
async function fetchPolicy(token, options = {}) {
|
|
6
|
+
if (!token) {
|
|
7
|
+
throw new Error("@scadable/privacy: a policy token is required");
|
|
8
|
+
}
|
|
9
|
+
const baseUrl = (options.baseUrl ?? DEFAULT_BASE_URL).replace(/\/$/, "");
|
|
10
|
+
const docType = options.docType ?? "privacy_policy";
|
|
11
|
+
const revalidate = options.revalidate ?? 3600;
|
|
12
|
+
const url = `${baseUrl}/policy/${encodeURIComponent(token)}?doc_type=${encodeURIComponent(docType)}&format=json`;
|
|
13
|
+
const init = revalidate === false ? { cache: "no-store" } : { next: { revalidate } };
|
|
14
|
+
const res = await fetch(url, init);
|
|
15
|
+
if (!res.ok) {
|
|
16
|
+
throw new Error(`@scadable/privacy: failed to load policy (${res.status}) for token "${token}"`);
|
|
17
|
+
}
|
|
18
|
+
return await res.json();
|
|
19
|
+
}
|
|
20
|
+
async function PrivacyPolicy({
|
|
21
|
+
token,
|
|
22
|
+
className,
|
|
23
|
+
showVersion = false,
|
|
24
|
+
...options
|
|
25
|
+
}) {
|
|
26
|
+
const policy = await fetchPolicy(token, options);
|
|
27
|
+
return /* @__PURE__ */ jsxs("div", { className, children: [
|
|
28
|
+
/* @__PURE__ */ jsx("div", { dangerouslySetInnerHTML: { __html: policy.html } }),
|
|
29
|
+
showVersion && policy.updated_at ? /* @__PURE__ */ jsxs("p", { style: { fontSize: 12, color: "#9ca3af", marginTop: 24 }, children: [
|
|
30
|
+
"Version ",
|
|
31
|
+
policy.version,
|
|
32
|
+
". Last updated",
|
|
33
|
+
" ",
|
|
34
|
+
new Date(policy.updated_at).toLocaleDateString(),
|
|
35
|
+
"."
|
|
36
|
+
] }) : null
|
|
37
|
+
] });
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export { DEFAULT_BASE_URL, PrivacyPolicy, fetchPolicy };
|
package/package.json
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@scadable/privacy",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Render your always-current SCADABLE privacy policy in a Next.js app.",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"sideEffects": false,
|
|
8
|
+
"main": "./dist/index.cjs",
|
|
9
|
+
"module": "./dist/index.js",
|
|
10
|
+
"types": "./dist/index.d.ts",
|
|
11
|
+
"exports": {
|
|
12
|
+
".": {
|
|
13
|
+
"types": "./dist/index.d.ts",
|
|
14
|
+
"import": "./dist/index.js",
|
|
15
|
+
"require": "./dist/index.cjs"
|
|
16
|
+
}
|
|
17
|
+
},
|
|
18
|
+
"files": [
|
|
19
|
+
"dist",
|
|
20
|
+
"README.md"
|
|
21
|
+
],
|
|
22
|
+
"scripts": {
|
|
23
|
+
"build": "tsup",
|
|
24
|
+
"dev": "tsup --watch",
|
|
25
|
+
"typecheck": "tsc --noEmit",
|
|
26
|
+
"prepublishOnly": "npm run build"
|
|
27
|
+
},
|
|
28
|
+
"keywords": [
|
|
29
|
+
"scadable",
|
|
30
|
+
"privacy",
|
|
31
|
+
"privacy-policy",
|
|
32
|
+
"compliance",
|
|
33
|
+
"gdpr",
|
|
34
|
+
"ccpa",
|
|
35
|
+
"nextjs",
|
|
36
|
+
"react"
|
|
37
|
+
],
|
|
38
|
+
"peerDependencies": {
|
|
39
|
+
"next": ">=13",
|
|
40
|
+
"react": ">=18"
|
|
41
|
+
},
|
|
42
|
+
"devDependencies": {
|
|
43
|
+
"@types/react": "^18.3.0",
|
|
44
|
+
"next": "^14.2.0",
|
|
45
|
+
"react": "^18.3.0",
|
|
46
|
+
"tsup": "^8.3.0",
|
|
47
|
+
"typescript": "^5.6.0"
|
|
48
|
+
},
|
|
49
|
+
"publishConfig": {
|
|
50
|
+
"access": "public"
|
|
51
|
+
},
|
|
52
|
+
"repository": {
|
|
53
|
+
"type": "git",
|
|
54
|
+
"url": "https://github.com/scadable/sdk.git",
|
|
55
|
+
"directory": "next"
|
|
56
|
+
}
|
|
57
|
+
}
|