@scadable/privacy 0.1.0 → 0.2.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.
@@ -0,0 +1,85 @@
1
+ "use client";
2
+ 'use strict';
3
+
4
+ var React = require('react');
5
+ var jsxRuntime = require('react/jsx-runtime');
6
+
7
+ function _interopNamespace(e) {
8
+ if (e && e.__esModule) return e;
9
+ var n = Object.create(null);
10
+ if (e) {
11
+ Object.keys(e).forEach(function (k) {
12
+ if (k !== 'default') {
13
+ var d = Object.getOwnPropertyDescriptor(e, k);
14
+ Object.defineProperty(n, k, d.get ? d : {
15
+ enumerable: true,
16
+ get: function () { return e[k]; }
17
+ });
18
+ }
19
+ });
20
+ }
21
+ n.default = e;
22
+ return Object.freeze(n);
23
+ }
24
+
25
+ var React__namespace = /*#__PURE__*/_interopNamespace(React);
26
+
27
+ // src/client.ts
28
+ var DEFAULT_BASE_URL = "https://api.scadable.com";
29
+ async function fetchPolicy(token, options = {}) {
30
+ if (!token) {
31
+ throw new Error("@scadable/privacy: a policy token is required");
32
+ }
33
+ const baseUrl = (options.baseUrl ?? DEFAULT_BASE_URL).replace(/\/$/, "");
34
+ const docType = options.docType ?? "privacy_policy";
35
+ const revalidate = options.revalidate ?? 3600;
36
+ const url = `${baseUrl}/policy/${encodeURIComponent(token)}?doc_type=${encodeURIComponent(docType)}&format=json`;
37
+ const init = revalidate === false ? { cache: "no-store" } : { next: { revalidate } };
38
+ const res = await fetch(url, init);
39
+ if (!res.ok) {
40
+ throw new Error(`@scadable/privacy: failed to load policy (${res.status}) for token "${token}"`);
41
+ }
42
+ return await res.json();
43
+ }
44
+ function PolicyLive({
45
+ token,
46
+ initialHtml,
47
+ initialVersion,
48
+ initialUpdatedAt,
49
+ className,
50
+ showVersion = false,
51
+ baseUrl,
52
+ docType
53
+ }) {
54
+ const [html, setHtml] = React__namespace.useState(initialHtml);
55
+ const [version, setVersion] = React__namespace.useState(initialVersion);
56
+ const [updatedAt, setUpdatedAt] = React__namespace.useState(initialUpdatedAt ?? null);
57
+ React__namespace.useEffect(() => {
58
+ let alive = true;
59
+ const opts = { revalidate: false };
60
+ if (baseUrl) opts.baseUrl = baseUrl;
61
+ if (docType) opts.docType = docType;
62
+ fetchPolicy(token, opts).then((p) => {
63
+ if (!alive || !p?.html) return;
64
+ setHtml(p.html);
65
+ setVersion(p.version);
66
+ setUpdatedAt(p.updated_at);
67
+ }).catch(() => {
68
+ });
69
+ return () => {
70
+ alive = false;
71
+ };
72
+ }, [token, baseUrl, docType]);
73
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className, children: [
74
+ /* @__PURE__ */ jsxRuntime.jsx("div", { dangerouslySetInnerHTML: { __html: html } }),
75
+ showVersion && updatedAt ? /* @__PURE__ */ jsxRuntime.jsxs("p", { style: { fontSize: 12, color: "#9ca3af", marginTop: 24 }, children: [
76
+ "Version ",
77
+ version,
78
+ ". Last updated ",
79
+ new Date(updatedAt).toLocaleDateString(),
80
+ "."
81
+ ] }) : null
82
+ ] });
83
+ }
84
+
85
+ exports.PolicyLive = PolicyLive;
@@ -0,0 +1,29 @@
1
+ import * as React from 'react';
2
+
3
+ interface PolicyLiveProps {
4
+ /** The public token from the SCADABLE app. */
5
+ token: string;
6
+ /** HTML baked in at build / server-render time, so it is crawlable and paints instantly. */
7
+ initialHtml: string;
8
+ initialVersion?: number;
9
+ initialUpdatedAt?: string | null;
10
+ /** Optional wrapper class so you can style/position the policy. */
11
+ className?: string;
12
+ /** Show a small "Version N, last updated ..." line under the policy. Default false. */
13
+ showVersion?: boolean;
14
+ /** Override the API base (forwarded to the browser fetch). */
15
+ baseUrl?: string;
16
+ /** Which document to fetch (forwarded to the browser fetch). */
17
+ docType?: string;
18
+ }
19
+ /**
20
+ * The client half of <PrivacyPolicy>. It renders the build-time HTML immediately, so
21
+ * the legal text and the "by scadable.com" backlink are in the static HTML (crawlable
22
+ * for SEO, no layout shift), then re-fetches the live policy in the browser so edits
23
+ * made in SCADABLE show up with no redeploy on the customer's side. If the browser
24
+ * fetch is blocked (a strict Content-Security-Policy) or offline, it keeps the baked
25
+ * copy, so the page is never blank.
26
+ */
27
+ declare function PolicyLive({ token, initialHtml, initialVersion, initialUpdatedAt, className, showVersion, baseUrl, docType, }: PolicyLiveProps): React.JSX.Element;
28
+
29
+ export { PolicyLive, type PolicyLiveProps };
@@ -0,0 +1,29 @@
1
+ import * as React from 'react';
2
+
3
+ interface PolicyLiveProps {
4
+ /** The public token from the SCADABLE app. */
5
+ token: string;
6
+ /** HTML baked in at build / server-render time, so it is crawlable and paints instantly. */
7
+ initialHtml: string;
8
+ initialVersion?: number;
9
+ initialUpdatedAt?: string | null;
10
+ /** Optional wrapper class so you can style/position the policy. */
11
+ className?: string;
12
+ /** Show a small "Version N, last updated ..." line under the policy. Default false. */
13
+ showVersion?: boolean;
14
+ /** Override the API base (forwarded to the browser fetch). */
15
+ baseUrl?: string;
16
+ /** Which document to fetch (forwarded to the browser fetch). */
17
+ docType?: string;
18
+ }
19
+ /**
20
+ * The client half of <PrivacyPolicy>. It renders the build-time HTML immediately, so
21
+ * the legal text and the "by scadable.com" backlink are in the static HTML (crawlable
22
+ * for SEO, no layout shift), then re-fetches the live policy in the browser so edits
23
+ * made in SCADABLE show up with no redeploy on the customer's side. If the browser
24
+ * fetch is blocked (a strict Content-Security-Policy) or offline, it keeps the baked
25
+ * copy, so the page is never blank.
26
+ */
27
+ declare function PolicyLive({ token, initialHtml, initialVersion, initialUpdatedAt, className, showVersion, baseUrl, docType, }: PolicyLiveProps): React.JSX.Element;
28
+
29
+ export { PolicyLive, type PolicyLiveProps };
@@ -0,0 +1,63 @@
1
+ "use client";
2
+ import * as React from 'react';
3
+ import { jsxs, jsx } from '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
+ function PolicyLive({
23
+ token,
24
+ initialHtml,
25
+ initialVersion,
26
+ initialUpdatedAt,
27
+ className,
28
+ showVersion = false,
29
+ baseUrl,
30
+ docType
31
+ }) {
32
+ const [html, setHtml] = React.useState(initialHtml);
33
+ const [version, setVersion] = React.useState(initialVersion);
34
+ const [updatedAt, setUpdatedAt] = React.useState(initialUpdatedAt ?? null);
35
+ React.useEffect(() => {
36
+ let alive = true;
37
+ const opts = { revalidate: false };
38
+ if (baseUrl) opts.baseUrl = baseUrl;
39
+ if (docType) opts.docType = docType;
40
+ fetchPolicy(token, opts).then((p) => {
41
+ if (!alive || !p?.html) return;
42
+ setHtml(p.html);
43
+ setVersion(p.version);
44
+ setUpdatedAt(p.updated_at);
45
+ }).catch(() => {
46
+ });
47
+ return () => {
48
+ alive = false;
49
+ };
50
+ }, [token, baseUrl, docType]);
51
+ return /* @__PURE__ */ jsxs("div", { className, children: [
52
+ /* @__PURE__ */ jsx("div", { dangerouslySetInnerHTML: { __html: html } }),
53
+ showVersion && updatedAt ? /* @__PURE__ */ jsxs("p", { style: { fontSize: 12, color: "#9ca3af", marginTop: 24 }, children: [
54
+ "Version ",
55
+ version,
56
+ ". Last updated ",
57
+ new Date(updatedAt).toLocaleDateString(),
58
+ "."
59
+ ] }) : null
60
+ ] });
61
+ }
62
+
63
+ export { PolicyLive };
package/dist/index.cjs CHANGED
@@ -1,5 +1,6 @@
1
1
  'use strict';
2
2
 
3
+ var PolicyLive = require('./PolicyLive.cjs');
3
4
  var jsxRuntime = require('react/jsx-runtime');
4
5
 
5
6
  // src/client.ts
@@ -26,19 +27,25 @@ async function PrivacyPolicy({
26
27
  ...options
27
28
  }) {
28
29
  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
- ] });
30
+ return /* @__PURE__ */ jsxRuntime.jsx(
31
+ PolicyLive.PolicyLive,
32
+ {
33
+ token,
34
+ initialHtml: policy.html,
35
+ initialVersion: policy.version,
36
+ initialUpdatedAt: policy.updated_at,
37
+ className,
38
+ showVersion,
39
+ baseUrl: options.baseUrl,
40
+ docType: options.docType
41
+ }
42
+ );
40
43
  }
41
44
 
45
+ Object.defineProperty(exports, "PolicyLive", {
46
+ enumerable: true,
47
+ get: function () { return PolicyLive.PolicyLive; }
48
+ });
42
49
  exports.DEFAULT_BASE_URL = DEFAULT_BASE_URL;
43
50
  exports.PrivacyPolicy = PrivacyPolicy;
44
51
  exports.fetchPolicy = fetchPolicy;
package/dist/index.d.cts CHANGED
@@ -1,4 +1,5 @@
1
- import * as React from 'react';
1
+ import * as react from 'react';
2
+ export { PolicyLive, PolicyLiveProps } from './PolicyLive';
2
3
 
3
4
  interface Policy {
4
5
  /** The name you gave the scope (your company / site). */
@@ -46,12 +47,14 @@ interface PrivacyPolicyProps extends FetchPolicyOptions {
46
47
  showVersion?: boolean;
47
48
  }
48
49
  /**
49
- * A React Server Component that renders your always-current privacy policy.
50
+ * Renders your always-current privacy policy.
50
51
  *
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.
52
+ * Hybrid by design. The policy HTML is fetched at build / server-render time and baked
53
+ * into the static HTML, so the legal text and the "by scadable.com" backlink are
54
+ * crawlable for SEO and paint instantly. It is then re-fetched in the browser (see
55
+ * {@link PolicyLive}) so edits you make in SCADABLE go live with no redeploy on your
56
+ * side. If a strict Content-Security-Policy blocks the browser fetch, the baked copy
57
+ * stays put, so the page is never blank.
55
58
  *
56
59
  * ```tsx
57
60
  * import { PrivacyPolicy } from '@scadable/privacy';
@@ -61,6 +64,6 @@ interface PrivacyPolicyProps extends FetchPolicyOptions {
61
64
  * }
62
65
  * ```
63
66
  */
64
- declare function PrivacyPolicy({ token, className, showVersion, ...options }: PrivacyPolicyProps): Promise<React.JSX.Element>;
67
+ declare function PrivacyPolicy({ token, className, showVersion, ...options }: PrivacyPolicyProps): Promise<react.JSX.Element>;
65
68
 
66
69
  export { DEFAULT_BASE_URL, type FetchPolicyOptions, type Policy, PrivacyPolicy, type PrivacyPolicyProps, fetchPolicy };
package/dist/index.d.ts CHANGED
@@ -1,4 +1,5 @@
1
- import * as React from 'react';
1
+ import * as react from 'react';
2
+ export { PolicyLive, PolicyLiveProps } from './PolicyLive';
2
3
 
3
4
  interface Policy {
4
5
  /** The name you gave the scope (your company / site). */
@@ -46,12 +47,14 @@ interface PrivacyPolicyProps extends FetchPolicyOptions {
46
47
  showVersion?: boolean;
47
48
  }
48
49
  /**
49
- * A React Server Component that renders your always-current privacy policy.
50
+ * Renders your always-current privacy policy.
50
51
  *
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.
52
+ * Hybrid by design. The policy HTML is fetched at build / server-render time and baked
53
+ * into the static HTML, so the legal text and the "by scadable.com" backlink are
54
+ * crawlable for SEO and paint instantly. It is then re-fetched in the browser (see
55
+ * {@link PolicyLive}) so edits you make in SCADABLE go live with no redeploy on your
56
+ * side. If a strict Content-Security-Policy blocks the browser fetch, the baked copy
57
+ * stays put, so the page is never blank.
55
58
  *
56
59
  * ```tsx
57
60
  * import { PrivacyPolicy } from '@scadable/privacy';
@@ -61,6 +64,6 @@ interface PrivacyPolicyProps extends FetchPolicyOptions {
61
64
  * }
62
65
  * ```
63
66
  */
64
- declare function PrivacyPolicy({ token, className, showVersion, ...options }: PrivacyPolicyProps): Promise<React.JSX.Element>;
67
+ declare function PrivacyPolicy({ token, className, showVersion, ...options }: PrivacyPolicyProps): Promise<react.JSX.Element>;
65
68
 
66
69
  export { DEFAULT_BASE_URL, type FetchPolicyOptions, type Policy, PrivacyPolicy, type PrivacyPolicyProps, fetchPolicy };
package/dist/index.js CHANGED
@@ -1,4 +1,6 @@
1
- import { jsxs, jsx } from 'react/jsx-runtime';
1
+ import { PolicyLive } from './PolicyLive.js';
2
+ export { PolicyLive } from './PolicyLive.js';
3
+ import { jsx } from 'react/jsx-runtime';
2
4
 
3
5
  // src/client.ts
4
6
  var DEFAULT_BASE_URL = "https://api.scadable.com";
@@ -24,17 +26,19 @@ async function PrivacyPolicy({
24
26
  ...options
25
27
  }) {
26
28
  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
- ] });
29
+ return /* @__PURE__ */ jsx(
30
+ PolicyLive,
31
+ {
32
+ token,
33
+ initialHtml: policy.html,
34
+ initialVersion: policy.version,
35
+ initialUpdatedAt: policy.updated_at,
36
+ className,
37
+ showVersion,
38
+ baseUrl: options.baseUrl,
39
+ docType: options.docType
40
+ }
41
+ );
38
42
  }
39
43
 
40
44
  export { DEFAULT_BASE_URL, PrivacyPolicy, fetchPolicy };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@scadable/privacy",
3
- "version": "0.1.0",
3
+ "version": "0.2.0",
4
4
  "description": "Render your always-current SCADABLE privacy policy in a Next.js app.",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -20,7 +20,7 @@
20
20
  "README.md"
21
21
  ],
22
22
  "scripts": {
23
- "build": "tsup",
23
+ "build": "tsup && node scripts/postbuild-directives.mjs",
24
24
  "dev": "tsup --watch",
25
25
  "typecheck": "tsc --noEmit",
26
26
  "prepublishOnly": "npm run build"