@chatlychat/cli 0.4.0 → 0.5.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/dist/commands/auth.d.ts +1 -1
- package/dist/commands/auth.d.ts.map +1 -1
- package/dist/commands/auth.js +40 -3
- package/dist/commands/auth.js.map +1 -1
- package/dist/commands/init.d.ts +29 -0
- package/dist/commands/init.d.ts.map +1 -0
- package/dist/commands/init.js +176 -0
- package/dist/commands/init.js.map +1 -0
- package/dist/commands/integrations.d.ts +27 -0
- package/dist/commands/integrations.d.ts.map +1 -1
- package/dist/commands/integrations.js +133 -3
- package/dist/commands/integrations.js.map +1 -1
- package/dist/commands/platform-as-code.d.ts +54 -0
- package/dist/commands/platform-as-code.d.ts.map +1 -0
- package/dist/commands/platform-as-code.js +185 -0
- package/dist/commands/platform-as-code.js.map +1 -0
- package/dist/commands/workspace-as-code.d.ts +65 -11
- package/dist/commands/workspace-as-code.d.ts.map +1 -1
- package/dist/commands/workspace-as-code.js +35 -177
- package/dist/commands/workspace-as-code.js.map +1 -1
- package/dist/help.d.ts +1 -1
- package/dist/help.d.ts.map +1 -1
- package/dist/help.js +30 -1
- package/dist/help.js.map +1 -1
- package/dist/init/shared.d.ts +61 -0
- package/dist/init/shared.d.ts.map +1 -0
- package/dist/init/shared.js +110 -0
- package/dist/init/shared.js.map +1 -0
- package/dist/init/templates.d.ts +29 -0
- package/dist/init/templates.d.ts.map +1 -0
- package/dist/init/templates.js +362 -0
- package/dist/init/templates.js.map +1 -0
- package/dist/pac/platform-document.d.ts +65 -0
- package/dist/pac/platform-document.d.ts.map +1 -0
- package/dist/pac/platform-document.js +149 -0
- package/dist/pac/platform-document.js.map +1 -0
- package/dist/pac/platform-live.d.ts +65 -0
- package/dist/pac/platform-live.d.ts.map +1 -0
- package/dist/pac/platform-live.js +263 -0
- package/dist/pac/platform-live.js.map +1 -0
- package/dist/pac/platform-resources.d.ts +141 -0
- package/dist/pac/platform-resources.d.ts.map +1 -0
- package/dist/pac/platform-resources.js +509 -0
- package/dist/pac/platform-resources.js.map +1 -0
- package/dist/run.d.ts.map +1 -1
- package/dist/run.js +23 -2
- package/dist/run.js.map +1 -1
- package/dist/wac/apply.d.ts.map +1 -1
- package/dist/wac/apply.js +52 -15
- package/dist/wac/apply.js.map +1 -1
- package/dist/wac/diff-warnings.d.ts +35 -0
- package/dist/wac/diff-warnings.d.ts.map +1 -0
- package/dist/wac/diff-warnings.js +87 -0
- package/dist/wac/diff-warnings.js.map +1 -0
- package/dist/wac/diff.d.ts +40 -70
- package/dist/wac/diff.d.ts.map +1 -1
- package/dist/wac/diff.js +90 -91
- package/dist/wac/diff.js.map +1 -1
- package/dist/wac/document-key.d.ts +30 -0
- package/dist/wac/document-key.d.ts.map +1 -0
- package/dist/wac/document-key.js +80 -0
- package/dist/wac/document-key.js.map +1 -0
- package/dist/wac/document.d.ts +75 -65
- package/dist/wac/document.d.ts.map +1 -1
- package/dist/wac/document.js +60 -46
- package/dist/wac/document.js.map +1 -1
- package/dist/wac/execute.d.ts +25 -0
- package/dist/wac/execute.d.ts.map +1 -0
- package/dist/wac/execute.js +126 -0
- package/dist/wac/execute.js.map +1 -0
- package/dist/wac/load.d.ts +15 -0
- package/dist/wac/load.d.ts.map +1 -0
- package/dist/wac/load.js +91 -0
- package/dist/wac/load.js.map +1 -0
- package/dist/wac/resource-def.d.ts +85 -1
- package/dist/wac/resource-def.d.ts.map +1 -1
- package/dist/wac/resource-def.js +4 -0
- package/dist/wac/resource-def.js.map +1 -1
- package/dist/wac/values.d.ts +7 -0
- package/dist/wac/values.d.ts.map +1 -1
- package/dist/wac/values.js +16 -0
- package/dist/wac/values.js.map +1 -1
- package/package.json +2 -2
package/dist/help.js
CHANGED
|
@@ -31,6 +31,7 @@ AUTH
|
|
|
31
31
|
login Store an API key in ~/.config/chatly/config.json (0600)
|
|
32
32
|
logout Remove the stored key
|
|
33
33
|
whoami Which workspace this key belongs to, and its scopes
|
|
34
|
+
(a cp_ credential: scopes, expiry and guardrails)
|
|
34
35
|
|
|
35
36
|
CONVERSATIONS
|
|
36
37
|
conversations list Open conversations, newest first
|
|
@@ -96,7 +97,7 @@ ROUTING + AUTOMATION
|
|
|
96
97
|
knowledge-gaps list | triage <id>
|
|
97
98
|
|
|
98
99
|
INTEGRATIONS
|
|
99
|
-
webhooks deliveries --status
|
|
100
|
+
webhooks deliveries --status --endpoint --trace <ulid> (X-Chatly-Trace)
|
|
100
101
|
webhooks delivery <id>
|
|
101
102
|
webhooks redeliver <id>
|
|
102
103
|
webhooks endpoints Subscriptions, not traffic
|
|
@@ -112,6 +113,18 @@ INTEGRATIONS
|
|
|
112
113
|
docs-sources subscribe <id> A refusal is a success: read the response
|
|
113
114
|
channels list | get <id> | adapters | create | update <id>
|
|
114
115
|
channels rotate-secret <id>
|
|
116
|
+
channels brand <id> --url <brand.json> | --file/--body to apply; neither reads back
|
|
117
|
+
|
|
118
|
+
DATA RIGHTS
|
|
119
|
+
privacy executor-key Mint the credential a privacy platform registers.
|
|
120
|
+
Answers with the executor URL, the workspace id
|
|
121
|
+
and the secret — shown ONCE. The key carries
|
|
122
|
+
exactly privacy:execute and reaches nothing but
|
|
123
|
+
/v1/privacy/*
|
|
124
|
+
privacy lookup --body <json> resolve a person to a subjectId
|
|
125
|
+
privacy request --body <json> file one; caseId is the replay key
|
|
126
|
+
privacy status <id> Read a request back
|
|
127
|
+
privacy cancel <id> Withdraw one that has not run
|
|
115
128
|
|
|
116
129
|
DEVELOPING AGAINST WEBHOOKS AND AGENTS
|
|
117
130
|
listen Tail this workspace's webhook deliveries and POST
|
|
@@ -145,6 +158,22 @@ WORKSPACE AS CODE
|
|
|
145
158
|
--prune also delete what the document omits
|
|
146
159
|
--write-only also send fields that cannot be read back
|
|
147
160
|
--exit-code plan only: exit 2 when there is drift
|
|
161
|
+
platform plan The same over /v1/platform/* with a cp_ credential:
|
|
162
|
+
platform apply workspaces, users, apiKeys, botCredentials,
|
|
163
|
+
knowledgeSources and settings (business hours,
|
|
164
|
+
routing, widget channel) across many tenants.
|
|
165
|
+
Default file: ./chatly.platform.json. Same flags.
|
|
166
|
+
apply re-reads afterwards and exits 1 unless
|
|
167
|
+
everything matches the document
|
|
168
|
+
|
|
169
|
+
INSTALL
|
|
170
|
+
init next|remix|astro|express
|
|
171
|
+
Scaffold the widget into this project for a web channel:
|
|
172
|
+
identity route, launcher or snippet, CSP sources
|
|
173
|
+
(chatly.csp.mjs) and a privacy table. Never overwrites
|
|
174
|
+
--public-id <id> which web channel (needed if several)
|
|
175
|
+
--identity hmac|atlas default hmac (atlas: next, remix)
|
|
176
|
+
--dir <path> default . --dry-run --force
|
|
148
177
|
|
|
149
178
|
AI
|
|
150
179
|
ai providers | quota
|
package/dist/help.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"help.js","sourceRoot":"","sources":["../src/help.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,MAAM,CAAC,MAAM,IAAI,GAAG
|
|
1
|
+
{"version":3,"file":"help.js","sourceRoot":"","sources":["../src/help.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,MAAM,CAAC,MAAM,IAAI,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAwMnB,CAAC"}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The parts of `chatly init` (CHT-F02) that do not depend on the framework:
|
|
3
|
+
* the CSP module and the privacy-policy table. Both are rendered from the
|
|
4
|
+
* channel's own `embed` block, read off `GET /v1/channels` with the caller's
|
|
5
|
+
* key — the same block the dashboard's install screen shows — so neither is a
|
|
6
|
+
* hand-kept copy of something the server already says.
|
|
7
|
+
*/
|
|
8
|
+
/** One generated file. `path` is relative to the target directory. */
|
|
9
|
+
export interface ScaffoldFile {
|
|
10
|
+
readonly path: string;
|
|
11
|
+
readonly contents: string;
|
|
12
|
+
}
|
|
13
|
+
/** The fields of `channel.embed` this wizard reads. Everything else is ignored. */
|
|
14
|
+
export interface EmbedBlock {
|
|
15
|
+
readonly snippet: string;
|
|
16
|
+
readonly contentSecurityPolicy: {
|
|
17
|
+
readonly scriptSrc: ReadonlyArray<string>;
|
|
18
|
+
readonly connectSrc: ReadonlyArray<string>;
|
|
19
|
+
readonly imgSrc: ReadonlyArray<string>;
|
|
20
|
+
readonly mediaSrc: ReadonlyArray<string>;
|
|
21
|
+
};
|
|
22
|
+
readonly storage: ReadonlyArray<StorageKey>;
|
|
23
|
+
}
|
|
24
|
+
/** One row of `embed.storage` — the public storage declaration. */
|
|
25
|
+
export interface StorageKey {
|
|
26
|
+
readonly key: string;
|
|
27
|
+
readonly store: string;
|
|
28
|
+
readonly purpose: string;
|
|
29
|
+
readonly lifetime: string;
|
|
30
|
+
readonly category: string;
|
|
31
|
+
readonly writtenWhen: string;
|
|
32
|
+
}
|
|
33
|
+
export interface ScaffoldInput {
|
|
34
|
+
readonly publicId: string;
|
|
35
|
+
readonly embed: EmbedBlock;
|
|
36
|
+
/** `hmac` signs the user id on your server; `atlas` passes an Atlas token. */
|
|
37
|
+
readonly identity: 'hmac' | 'atlas';
|
|
38
|
+
/** ISO date, stamped into each header so a stale file can be recognised. */
|
|
39
|
+
readonly generatedOn: string;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* The module every framework's CSP is merged from.
|
|
43
|
+
*
|
|
44
|
+
* `.mjs` and dependency-free on purpose: `next.config.{js,mjs,ts}` and an
|
|
45
|
+
* Express server can both import it, and a header built from it needs no
|
|
46
|
+
* library. It does NOT write a header itself — the host page already has a
|
|
47
|
+
* policy, and a second `Content-Security-Policy` header INTERSECTS with the
|
|
48
|
+
* first rather than extending it, which would block the host's own scripts.
|
|
49
|
+
* So it merges into the directives the integrator already has.
|
|
50
|
+
*/
|
|
51
|
+
export declare function cspModule(input: ScaffoldInput): ScaffoldFile;
|
|
52
|
+
/**
|
|
53
|
+
* The privacy-policy table (CHT-DOC12's content, rendered for this channel).
|
|
54
|
+
*
|
|
55
|
+
* Every key with the channel's `publicId` already substituted, which is the
|
|
56
|
+
* form a privacy policy or a consent tool's cookie scan needs. `cookies` is
|
|
57
|
+
* stated as none because the declaration says so — an omitted row reads as
|
|
58
|
+
* "not declared", not as "none".
|
|
59
|
+
*/
|
|
60
|
+
export declare function privacyTable(input: ScaffoldInput): ScaffoldFile;
|
|
61
|
+
//# sourceMappingURL=shared.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"shared.d.ts","sourceRoot":"","sources":["../../src/init/shared.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,sEAAsE;AACtE,MAAM,WAAW,YAAY;IAC3B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;CAC3B;AAED,mFAAmF;AACnF,MAAM,WAAW,UAAU;IACzB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,qBAAqB,EAAE;QAC9B,QAAQ,CAAC,SAAS,EAAE,aAAa,CAAC,MAAM,CAAC,CAAC;QAC1C,QAAQ,CAAC,UAAU,EAAE,aAAa,CAAC,MAAM,CAAC,CAAC;QAC3C,QAAQ,CAAC,MAAM,EAAE,aAAa,CAAC,MAAM,CAAC,CAAC;QACvC,QAAQ,CAAC,QAAQ,EAAE,aAAa,CAAC,MAAM,CAAC,CAAC;KAC1C,CAAC;IACF,QAAQ,CAAC,OAAO,EAAE,aAAa,CAAC,UAAU,CAAC,CAAC;CAC7C;AAED,mEAAmE;AACnE,MAAM,WAAW,UAAU;IACzB,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;CAC9B;AAED,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,KAAK,EAAE,UAAU,CAAC;IAC3B,8EAA8E;IAC9E,QAAQ,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC;IACpC,4EAA4E;IAC5E,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;CAC9B;AAED;;;;;;;;;GASG;AACH,wBAAgB,SAAS,CAAC,KAAK,EAAE,aAAa,GAAG,YAAY,CAwD5D;AAOD;;;;;;;GAOG;AACH,wBAAgB,YAAY,CAAC,KAAK,EAAE,aAAa,GAAG,YAAY,CA0B/D"}
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The parts of `chatly init` (CHT-F02) that do not depend on the framework:
|
|
3
|
+
* the CSP module and the privacy-policy table. Both are rendered from the
|
|
4
|
+
* channel's own `embed` block, read off `GET /v1/channels` with the caller's
|
|
5
|
+
* key — the same block the dashboard's install screen shows — so neither is a
|
|
6
|
+
* hand-kept copy of something the server already says.
|
|
7
|
+
*/
|
|
8
|
+
/**
|
|
9
|
+
* The module every framework's CSP is merged from.
|
|
10
|
+
*
|
|
11
|
+
* `.mjs` and dependency-free on purpose: `next.config.{js,mjs,ts}` and an
|
|
12
|
+
* Express server can both import it, and a header built from it needs no
|
|
13
|
+
* library. It does NOT write a header itself — the host page already has a
|
|
14
|
+
* policy, and a second `Content-Security-Policy` header INTERSECTS with the
|
|
15
|
+
* first rather than extending it, which would block the host's own scripts.
|
|
16
|
+
* So it merges into the directives the integrator already has.
|
|
17
|
+
*/
|
|
18
|
+
export function cspModule(input) {
|
|
19
|
+
const csp = input.embed.contentSecurityPolicy;
|
|
20
|
+
const list = (values) => `[${values.map((v) => JSON.stringify(v)).join(', ')}]`;
|
|
21
|
+
return {
|
|
22
|
+
path: 'chatly.csp.mjs',
|
|
23
|
+
contents: `/**
|
|
24
|
+
* Chatly's Content-Security-Policy sources for the web channel ${input.publicId}.
|
|
25
|
+
*
|
|
26
|
+
* Generated by \`chatly init\` on ${input.generatedOn} from the channel's own
|
|
27
|
+
* \`embed.contentSecurityPolicy\`. Re-run \`chatly init\` (or read
|
|
28
|
+
* \`GET /v1/channels\`) when Chatly announces a host change — the lists are
|
|
29
|
+
* the origins CHATLY serves from, not an image URL an operator put in a theme.
|
|
30
|
+
*
|
|
31
|
+
* MERGE these into your existing directives; do not send a second
|
|
32
|
+
* Content-Security-Policy header. Two headers intersect, so a second one
|
|
33
|
+
* would block your own scripts rather than allow Chatly's.
|
|
34
|
+
*
|
|
35
|
+
* The embed snippet is an inline <script>: your script-src needs a nonce or
|
|
36
|
+
* hash for it, or load the widget with @chatlychat/widget-react instead.
|
|
37
|
+
*/
|
|
38
|
+
export const chatlyCsp = {
|
|
39
|
+
'script-src': ${list(csp.scriptSrc)},
|
|
40
|
+
'connect-src': ${list(csp.connectSrc)},
|
|
41
|
+
'img-src': ${list(csp.imgSrc)},
|
|
42
|
+
'media-src': ${list(csp.mediaSrc)},
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Your directives with Chatly's sources appended, de-duplicated.
|
|
47
|
+
* Directive names are the header's own (\`'script-src'\`, not \`scriptSrc\`).
|
|
48
|
+
*
|
|
49
|
+
* @param {Record<string, string[]>} directives
|
|
50
|
+
* @returns {Record<string, string[]>}
|
|
51
|
+
*/
|
|
52
|
+
export function withChatly(directives) {
|
|
53
|
+
const out = { ...directives };
|
|
54
|
+
for (const [name, sources] of Object.entries(chatlyCsp)) {
|
|
55
|
+
out[name] = [...new Set([...(out[name] ?? ["'self'"]), ...sources])];
|
|
56
|
+
}
|
|
57
|
+
return out;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Directives to a header value: \`"script-src 'self' https://…; connect-src …"\`.
|
|
62
|
+
*
|
|
63
|
+
* @param {Record<string, string[]>} directives
|
|
64
|
+
* @returns {string}
|
|
65
|
+
*/
|
|
66
|
+
export function toHeader(directives) {
|
|
67
|
+
return Object.entries(directives)
|
|
68
|
+
.map(([name, sources]) => [name, ...sources].join(' '))
|
|
69
|
+
.join('; ');
|
|
70
|
+
}
|
|
71
|
+
`,
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
/** Escape a value for a Markdown table cell. */
|
|
75
|
+
function cell(value) {
|
|
76
|
+
return value.replaceAll('|', '\\|').replaceAll('\n', ' ');
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* The privacy-policy table (CHT-DOC12's content, rendered for this channel).
|
|
80
|
+
*
|
|
81
|
+
* Every key with the channel's `publicId` already substituted, which is the
|
|
82
|
+
* form a privacy policy or a consent tool's cookie scan needs. `cookies` is
|
|
83
|
+
* stated as none because the declaration says so — an omitted row reads as
|
|
84
|
+
* "not declared", not as "none".
|
|
85
|
+
*/
|
|
86
|
+
export function privacyTable(input) {
|
|
87
|
+
const rows = input.embed.storage.map((k) => `| \`${cell(k.key)}\` | ${cell(k.store)} | ${cell(k.purpose)} | ${cell(k.lifetime)} | ` +
|
|
88
|
+
`${cell(k.category)} | ${cell(k.writtenWhen)} |`);
|
|
89
|
+
return {
|
|
90
|
+
path: 'CHATLY-PRIVACY.md',
|
|
91
|
+
contents: `# What the Chatly widget stores in a visitor's browser
|
|
92
|
+
|
|
93
|
+
Generated by \`chatly init\` on ${input.generatedOn} from the storage
|
|
94
|
+
declaration of web channel \`${input.publicId}\` — the same list Chatly
|
|
95
|
+
publishes for that channel without a key. Paste the table into your privacy
|
|
96
|
+
policy or cookie notice.
|
|
97
|
+
|
|
98
|
+
**Cookies: none.** The widget sets no cookies.
|
|
99
|
+
|
|
100
|
+
| Key | Store | Purpose | Lifetime | Category | Written when |
|
|
101
|
+
| --- | --- | --- | --- | --- | --- |
|
|
102
|
+
${rows.join('\n')}
|
|
103
|
+
|
|
104
|
+
Whether ANY of these is written is decided by the \`storage\` option on your
|
|
105
|
+
embed, which the server never sees — \`storage: 'deny'\` writes none of them.
|
|
106
|
+
See https://chatlychat.com/guides/consent.
|
|
107
|
+
`,
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
//# sourceMappingURL=shared.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"shared.js","sourceRoot":"","sources":["../../src/init/shared.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAuCH;;;;;;;;;GASG;AACH,MAAM,UAAU,SAAS,CAAC,KAAoB;IAC5C,MAAM,GAAG,GAAG,KAAK,CAAC,KAAK,CAAC,qBAAqB,CAAC;IAC9C,MAAM,IAAI,GAAG,CAAC,MAA6B,EAAU,EAAE,CACrD,IAAI,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC;IACzD,OAAO;QACL,IAAI,EAAE,gBAAgB;QACtB,QAAQ,EAAE;kEACoD,KAAK,CAAC,QAAQ;;qCAE3C,KAAK,CAAC,WAAW;;;;;;;;;;;;;kBAapC,IAAI,CAAC,GAAG,CAAC,SAAS,CAAC;mBAClB,IAAI,CAAC,GAAG,CAAC,UAAU,CAAC;eACxB,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC;iBACd,IAAI,CAAC,GAAG,CAAC,QAAQ,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA6BlC;KACE,CAAC;AACJ,CAAC;AAED,gDAAgD;AAChD,SAAS,IAAI,CAAC,KAAa;IACzB,OAAO,KAAK,CAAC,UAAU,CAAC,GAAG,EAAE,KAAK,CAAC,CAAC,UAAU,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC;AAC5D,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,YAAY,CAAC,KAAoB;IAC/C,MAAM,IAAI,GAAG,KAAK,CAAC,KAAK,CAAC,OAAO,CAAC,GAAG,CAClC,CAAC,CAAC,EAAE,EAAE,CACJ,OAAO,IAAI,CAAC,CAAC,CAAC,GAAG,CAAC,QAAQ,IAAI,CAAC,CAAC,CAAC,KAAK,CAAC,MAAM,IAAI,CAAC,CAAC,CAAC,OAAO,CAAC,MAAM,IAAI,CAAC,CAAC,CAAC,QAAQ,CAAC,KAAK;QACvF,GAAG,IAAI,CAAC,CAAC,CAAC,QAAQ,CAAC,MAAM,IAAI,CAAC,CAAC,CAAC,WAAW,CAAC,IAAI,CACnD,CAAC;IACF,OAAO;QACL,IAAI,EAAE,mBAAmB;QACzB,QAAQ,EAAE;;kCAEoB,KAAK,CAAC,WAAW;+BACpB,KAAK,CAAC,QAAQ;;;;;;;;EAQ3C,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC;;;;;CAKhB;KACE,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The framework templates for `chatly init` (CHT-F02).
|
|
3
|
+
*
|
|
4
|
+
* Every API a generated file uses is in a PUBLISHED package at the version on
|
|
5
|
+
* npm today — `chatlyUserHash` from `@chatlychat/widget-react/server`,
|
|
6
|
+
* `<ChatlyLauncher>`'s `externalId`/`userHash`/`getToken` props, and
|
|
7
|
+
* `Chatly('identify', …)` on the snippet. A template that imported something
|
|
8
|
+
* from a working tree would scaffold a project that does not install.
|
|
9
|
+
*
|
|
10
|
+
* ═══ THE ONE THING A WIZARD CANNOT KNOW ═══════════════════════════════════
|
|
11
|
+
*
|
|
12
|
+
* Who is signed in. Every identity route below has a `currentUserId` stub
|
|
13
|
+
* that answers `null`, so a freshly scaffolded app runs with every visitor
|
|
14
|
+
* ANONYMOUS — never with an unsigned claim. Wiring it to the integrator's
|
|
15
|
+
* auth is the step the summary names first.
|
|
16
|
+
*/
|
|
17
|
+
import { type ScaffoldFile, type ScaffoldInput } from './shared.js';
|
|
18
|
+
export declare const FRAMEWORKS: readonly ["next", "remix", "astro", "express"];
|
|
19
|
+
export type Framework = (typeof FRAMEWORKS)[number];
|
|
20
|
+
/**
|
|
21
|
+
* The frameworks with no `--identity atlas` output. An Atlas token is minted
|
|
22
|
+
* in the browser, so there is no server half to generate, and these two have
|
|
23
|
+
* no React component to pass `getToken` to — the page calls
|
|
24
|
+
* `Chatly('identify', { getToken })` itself.
|
|
25
|
+
*/
|
|
26
|
+
export declare const NO_ATLAS: ReadonlyArray<Framework>;
|
|
27
|
+
/** Every file `chatly init <framework>` writes, in the order it reports them. */
|
|
28
|
+
export declare function scaffold(framework: Framework, input: ScaffoldInput): ReadonlyArray<ScaffoldFile>;
|
|
29
|
+
//# sourceMappingURL=templates.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"templates.d.ts","sourceRoot":"","sources":["../../src/init/templates.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AACH,OAAO,EAA2B,KAAK,YAAY,EAAE,KAAK,aAAa,EAAE,MAAM,aAAa,CAAC;AAE7F,eAAO,MAAM,UAAU,gDAAiD,CAAC;AACzE,MAAM,MAAM,SAAS,GAAG,CAAC,OAAO,UAAU,CAAC,CAAC,MAAM,CAAC,CAAC;AAEpD;;;;;GAKG;AACH,eAAO,MAAM,QAAQ,EAAE,aAAa,CAAC,SAAS,CAAwB,CAAC;AAsTvE,iFAAiF;AACjF,wBAAgB,QAAQ,CAAC,SAAS,EAAE,SAAS,EAAE,KAAK,EAAE,aAAa,GAAG,aAAa,CAAC,YAAY,CAAC,CAwChG"}
|
|
@@ -0,0 +1,362 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The framework templates for `chatly init` (CHT-F02).
|
|
3
|
+
*
|
|
4
|
+
* Every API a generated file uses is in a PUBLISHED package at the version on
|
|
5
|
+
* npm today — `chatlyUserHash` from `@chatlychat/widget-react/server`,
|
|
6
|
+
* `<ChatlyLauncher>`'s `externalId`/`userHash`/`getToken` props, and
|
|
7
|
+
* `Chatly('identify', …)` on the snippet. A template that imported something
|
|
8
|
+
* from a working tree would scaffold a project that does not install.
|
|
9
|
+
*
|
|
10
|
+
* ═══ THE ONE THING A WIZARD CANNOT KNOW ═══════════════════════════════════
|
|
11
|
+
*
|
|
12
|
+
* Who is signed in. Every identity route below has a `currentUserId` stub
|
|
13
|
+
* that answers `null`, so a freshly scaffolded app runs with every visitor
|
|
14
|
+
* ANONYMOUS — never with an unsigned claim. Wiring it to the integrator's
|
|
15
|
+
* auth is the step the summary names first.
|
|
16
|
+
*/
|
|
17
|
+
import { cspModule, privacyTable } from './shared.js';
|
|
18
|
+
export const FRAMEWORKS = ['next', 'remix', 'astro', 'express'];
|
|
19
|
+
/**
|
|
20
|
+
* The frameworks with no `--identity atlas` output. An Atlas token is minted
|
|
21
|
+
* in the browser, so there is no server half to generate, and these two have
|
|
22
|
+
* no React component to pass `getToken` to — the page calls
|
|
23
|
+
* `Chatly('identify', { getToken })` itself.
|
|
24
|
+
*/
|
|
25
|
+
export const NO_ATLAS = ['express', 'astro'];
|
|
26
|
+
const IDENTITY_ROUTE_NEXT = (input) => `/**
|
|
27
|
+
* Chatly identity verification (HMAC) for web channel ${input.publicId}.
|
|
28
|
+
* Generated by \`chatly init next\` on ${input.generatedOn}.
|
|
29
|
+
*
|
|
30
|
+
* Signs the signed-in user's id with the channel's IDV secret, so the widget
|
|
31
|
+
* can prove who the visitor is. The secret stays on this server:
|
|
32
|
+
* \`@chatlychat/widget-react/server\` refuses to run in a browser bundle.
|
|
33
|
+
*
|
|
34
|
+
* Wire \`currentUserId\` to your auth before shipping. Until then it answers
|
|
35
|
+
* \`{ identity: null }\` and every visitor chats anonymously.
|
|
36
|
+
*/
|
|
37
|
+
import { chatlyUserHash } from '@chatlychat/widget-react/server';
|
|
38
|
+
import { NextResponse } from 'next/server';
|
|
39
|
+
|
|
40
|
+
export const dynamic = 'force-dynamic';
|
|
41
|
+
|
|
42
|
+
const NO_STORE = { 'cache-control': 'no-store' };
|
|
43
|
+
|
|
44
|
+
/** TODO: return the signed-in user's stable id from your auth library, or null. */
|
|
45
|
+
async function currentUserId(): Promise<string | null> {
|
|
46
|
+
return null;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export async function GET(): Promise<NextResponse> {
|
|
50
|
+
const externalId = await currentUserId();
|
|
51
|
+
if (externalId === null) {
|
|
52
|
+
return NextResponse.json({ identity: null }, { headers: NO_STORE });
|
|
53
|
+
}
|
|
54
|
+
const secret = process.env.CHATLY_IDV_SECRET;
|
|
55
|
+
if (secret === undefined || secret === '') {
|
|
56
|
+
// Loud rather than anonymous: a signed-in user silently chatting as a
|
|
57
|
+
// stranger is a misconfiguration nobody would notice.
|
|
58
|
+
return NextResponse.json(
|
|
59
|
+
{ error: 'CHATLY_IDV_SECRET is not set on the server' },
|
|
60
|
+
{ status: 500, headers: NO_STORE },
|
|
61
|
+
);
|
|
62
|
+
}
|
|
63
|
+
return NextResponse.json(
|
|
64
|
+
{ identity: { externalId, userHash: chatlyUserHash(secret, externalId) } },
|
|
65
|
+
{ headers: NO_STORE },
|
|
66
|
+
);
|
|
67
|
+
}
|
|
68
|
+
`;
|
|
69
|
+
const LAUNCHER_NEXT_HMAC = (input) => `'use client';
|
|
70
|
+
/**
|
|
71
|
+
* The Chatly launcher for web channel ${input.publicId}, identified through
|
|
72
|
+
* \`app/api/chatly/identity/route.ts\`. Generated by \`chatly init next\` on
|
|
73
|
+
* ${input.generatedOn}. Render <Chatly /> once, in your root layout.
|
|
74
|
+
*/
|
|
75
|
+
import { ChatlyLauncher } from '@chatlychat/widget-react';
|
|
76
|
+
import { useEffect, useState } from 'react';
|
|
77
|
+
|
|
78
|
+
interface Identity {
|
|
79
|
+
readonly externalId: string;
|
|
80
|
+
readonly userHash: string;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export function Chatly() {
|
|
84
|
+
// undefined = still asking; null = anonymous.
|
|
85
|
+
const [identity, setIdentity] = useState<Identity | null | undefined>(undefined);
|
|
86
|
+
|
|
87
|
+
useEffect(() => {
|
|
88
|
+
let live = true;
|
|
89
|
+
fetch('/api/chatly/identity', { cache: 'no-store' })
|
|
90
|
+
.then(async (r) => (r.ok ? ((await r.json()) as { identity: Identity | null }) : { identity: null }))
|
|
91
|
+
.then((body) => live && setIdentity(body.identity))
|
|
92
|
+
.catch(() => live && setIdentity(null));
|
|
93
|
+
return () => {
|
|
94
|
+
live = false;
|
|
95
|
+
};
|
|
96
|
+
}, []);
|
|
97
|
+
|
|
98
|
+
// Rendered only once the identity is known, so the widget starts as the
|
|
99
|
+
// right visitor instead of starting anonymous and switching.
|
|
100
|
+
if (identity === undefined) {
|
|
101
|
+
return null;
|
|
102
|
+
}
|
|
103
|
+
return <ChatlyLauncher publicId=${JSON.stringify(input.publicId)} {...(identity ?? {})} />;
|
|
104
|
+
}
|
|
105
|
+
`;
|
|
106
|
+
const LAUNCHER_NEXT_ATLAS = (input) => `'use client';
|
|
107
|
+
/**
|
|
108
|
+
* The Chatly launcher for web channel ${input.publicId}, identified with an
|
|
109
|
+
* Atlas token (\`identityVerification.mode: 'atlas'\`). Generated by
|
|
110
|
+
* \`chatly init next --identity atlas\` on ${input.generatedOn}.
|
|
111
|
+
*
|
|
112
|
+
* Pass \`getToken\` for a signed-in user and leave it out for a signed-out one:
|
|
113
|
+
*
|
|
114
|
+
* <Chatly getToken={() => atlas.session.getToken({ template: 'chatly' })} />
|
|
115
|
+
*
|
|
116
|
+
* The template and the channel setting it must match are on
|
|
117
|
+
* https://chatlychat.com/guides/identity-verification#worked-example-an-atlas-instance-on-the-shared-host
|
|
118
|
+
*/
|
|
119
|
+
import { ChatlyLauncher } from '@chatlychat/widget-react';
|
|
120
|
+
|
|
121
|
+
export function Chatly({ getToken }: { getToken?: () => Promise<string> }) {
|
|
122
|
+
return (
|
|
123
|
+
<ChatlyLauncher
|
|
124
|
+
publicId=${JSON.stringify(input.publicId)}
|
|
125
|
+
{...(getToken === undefined ? {} : { getToken })}
|
|
126
|
+
/>
|
|
127
|
+
);
|
|
128
|
+
}
|
|
129
|
+
`;
|
|
130
|
+
const IDENTITY_EXPRESS = (input) => `/**
|
|
131
|
+
* Chatly identity verification (HMAC) for web channel ${input.publicId}.
|
|
132
|
+
* Generated by \`chatly init express\` on ${input.generatedOn}.
|
|
133
|
+
*
|
|
134
|
+
* import { chatlyIdentity } from './chatly/identity.mjs';
|
|
135
|
+
* app.get('/api/chatly/identity', chatlyIdentity((req) => req.user?.id ?? null));
|
|
136
|
+
*
|
|
137
|
+
* \`userIdOf\` is the one thing to write: return the signed-in user's stable id,
|
|
138
|
+
* or null for a signed-out visitor, who then chats anonymously.
|
|
139
|
+
*/
|
|
140
|
+
import { createHmac } from 'node:crypto';
|
|
141
|
+
|
|
142
|
+
/** HMAC-SHA256(secret, externalId), hex — what the widget sends as userHash. */
|
|
143
|
+
export function chatlyUserHash(secret, externalId) {
|
|
144
|
+
return createHmac('sha256', secret).update(externalId).digest('hex');
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/** @param {(req: any) => string | null | Promise<string | null>} userIdOf */
|
|
148
|
+
export function chatlyIdentity(userIdOf) {
|
|
149
|
+
return async (req, res) => {
|
|
150
|
+
res.set('cache-control', 'no-store');
|
|
151
|
+
const externalId = await userIdOf(req);
|
|
152
|
+
if (externalId === null || externalId === undefined) {
|
|
153
|
+
return res.json({ identity: null });
|
|
154
|
+
}
|
|
155
|
+
const secret = process.env.CHATLY_IDV_SECRET;
|
|
156
|
+
if (!secret) {
|
|
157
|
+
return res.status(500).json({ error: 'CHATLY_IDV_SECRET is not set on the server' });
|
|
158
|
+
}
|
|
159
|
+
return res.json({ identity: { externalId, userHash: chatlyUserHash(secret, externalId) } });
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
`;
|
|
163
|
+
const SNIPPET_EXPRESS = (input) => `<!--
|
|
164
|
+
Chatly for web channel ${input.publicId}. Generated by \`chatly init express\`
|
|
165
|
+
on ${input.generatedOn}. Paste before </body>. The first script is the
|
|
166
|
+
channel's own embed snippet; the second identifies a signed-in visitor
|
|
167
|
+
through /api/chatly/identity (see chatly/identity.mjs).
|
|
168
|
+
-->
|
|
169
|
+
${input.embed.snippet}
|
|
170
|
+
<script>
|
|
171
|
+
fetch('/api/chatly/identity', { cache: 'no-store' })
|
|
172
|
+
.then(function (r) { return r.ok ? r.json() : { identity: null }; })
|
|
173
|
+
.then(function (body) { if (body.identity) Chatly('identify', body.identity); })
|
|
174
|
+
.catch(function () {});
|
|
175
|
+
</script>
|
|
176
|
+
`;
|
|
177
|
+
/**
|
|
178
|
+
* The Remix loader imports nothing from Remix or React Router on purpose. A
|
|
179
|
+
* `LoaderFunctionArgs` import would pin the file to `@remix-run/node` (Remix
|
|
180
|
+
* v2) or to `react-router` (v7 framework mode), and a loader that takes
|
|
181
|
+
* `{ request }` and returns a `Response` is a valid loader in both.
|
|
182
|
+
*/
|
|
183
|
+
const IDENTITY_ROUTE_REMIX = (input) => `/**
|
|
184
|
+
* Chatly identity verification (HMAC) for web channel ${input.publicId}.
|
|
185
|
+
* Generated by \`chatly init remix\` on ${input.generatedOn}.
|
|
186
|
+
*
|
|
187
|
+
* A resource route (a loader and no component), served at
|
|
188
|
+
* /api/chatly/identity by Remix v2's flat routes and by React Router v7's
|
|
189
|
+
* \`flatRoutes()\`. Signs the signed-in user's id with the channel's IDV
|
|
190
|
+
* secret, so the widget can prove who the visitor is. The secret stays on
|
|
191
|
+
* this server: a loader never reaches the browser bundle, and
|
|
192
|
+
* \`@chatlychat/widget-react/server\` refuses to run in one anyway.
|
|
193
|
+
*
|
|
194
|
+
* Wire \`currentUserId\` to your auth before shipping. Until then it answers
|
|
195
|
+
* \`{ identity: null }\` and every visitor chats anonymously.
|
|
196
|
+
*/
|
|
197
|
+
import { chatlyUserHash } from '@chatlychat/widget-react/server';
|
|
198
|
+
|
|
199
|
+
const HEADERS = { 'content-type': 'application/json', 'cache-control': 'no-store' };
|
|
200
|
+
|
|
201
|
+
/** TODO: return the signed-in user's stable id from your session, or null. */
|
|
202
|
+
async function currentUserId(request: Request): Promise<string | null> {
|
|
203
|
+
void request;
|
|
204
|
+
return null;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
export async function loader({ request }: { request: Request }): Promise<Response> {
|
|
208
|
+
const externalId = await currentUserId(request);
|
|
209
|
+
if (externalId === null) {
|
|
210
|
+
return new Response(JSON.stringify({ identity: null }), { headers: HEADERS });
|
|
211
|
+
}
|
|
212
|
+
const secret = process.env.CHATLY_IDV_SECRET;
|
|
213
|
+
if (secret === undefined || secret === '') {
|
|
214
|
+
// Loud rather than anonymous: a signed-in user silently chatting as a
|
|
215
|
+
// stranger is a misconfiguration nobody would notice.
|
|
216
|
+
return new Response(JSON.stringify({ error: 'CHATLY_IDV_SECRET is not set on the server' }), {
|
|
217
|
+
status: 500,
|
|
218
|
+
headers: HEADERS,
|
|
219
|
+
});
|
|
220
|
+
}
|
|
221
|
+
return new Response(
|
|
222
|
+
JSON.stringify({ identity: { externalId, userHash: chatlyUserHash(secret, externalId) } }),
|
|
223
|
+
{ headers: HEADERS },
|
|
224
|
+
);
|
|
225
|
+
}
|
|
226
|
+
`;
|
|
227
|
+
/**
|
|
228
|
+
* Remix's launcher is Next's without `'use client'` and with its own header:
|
|
229
|
+
* every Remix component renders on the server and hydrates, and the identity
|
|
230
|
+
* fetch is in an effect, so it runs in the browser only.
|
|
231
|
+
*/
|
|
232
|
+
const LAUNCHER_REMIX_HMAC = (input) => LAUNCHER_NEXT_HMAC(input)
|
|
233
|
+
.replace(/^'use client';\n/, '')
|
|
234
|
+
.replace('`app/api/chatly/identity/route.ts`', '`app/routes/api.chatly.identity.ts`')
|
|
235
|
+
.replace('`chatly init next`', '`chatly init remix`')
|
|
236
|
+
.replace('in your root layout', 'in app/root.tsx');
|
|
237
|
+
const LAUNCHER_REMIX_ATLAS = (input) => LAUNCHER_NEXT_ATLAS(input)
|
|
238
|
+
.replace(/^'use client';\n/, '')
|
|
239
|
+
.replace('`chatly init next --identity atlas`', '`chatly init remix --identity atlas`');
|
|
240
|
+
/**
|
|
241
|
+
* Astro's endpoint signs with `node:crypto`, like Express's, rather than
|
|
242
|
+
* importing `@chatlychat/widget-react/server`: an Astro site has no reason to
|
|
243
|
+
* install a React package, and the construction is one line.
|
|
244
|
+
*/
|
|
245
|
+
const IDENTITY_ROUTE_ASTRO = (input) => `/**
|
|
246
|
+
* Chatly identity verification (HMAC) for web channel ${input.publicId}.
|
|
247
|
+
* Generated by \`chatly init astro\` on ${input.generatedOn}.
|
|
248
|
+
*
|
|
249
|
+
* Served at /api/chatly/identity. \`prerender = false\` makes it run per
|
|
250
|
+
* request, which needs a server adapter with Node built-ins
|
|
251
|
+
* (\`@astrojs/node\`, or another adapter with \`node:crypto\`): a prerendered
|
|
252
|
+
* identity would be one visitor's answer frozen into a static file.
|
|
253
|
+
*
|
|
254
|
+
* Wire \`currentUserId\` to your auth before shipping. Until then it answers
|
|
255
|
+
* \`{ identity: null }\` and every visitor chats anonymously.
|
|
256
|
+
*/
|
|
257
|
+
import { createHmac } from 'node:crypto';
|
|
258
|
+
|
|
259
|
+
import type { APIRoute } from 'astro';
|
|
260
|
+
|
|
261
|
+
export const prerender = false;
|
|
262
|
+
|
|
263
|
+
const HEADERS = { 'content-type': 'application/json', 'cache-control': 'no-store' };
|
|
264
|
+
|
|
265
|
+
/** HMAC-SHA256(secret, externalId), hex — what the widget sends as userHash. */
|
|
266
|
+
export function chatlyUserHash(secret: string, externalId: string): string {
|
|
267
|
+
return createHmac('sha256', secret).update(externalId).digest('hex');
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
/** TODO: return the signed-in user's stable id (from \`locals\`, a cookie, …), or null. */
|
|
271
|
+
async function currentUserId(context: Parameters<APIRoute>[0]): Promise<string | null> {
|
|
272
|
+
void context;
|
|
273
|
+
return null;
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
export const GET: APIRoute = async (context) => {
|
|
277
|
+
const externalId = await currentUserId(context);
|
|
278
|
+
if (externalId === null) {
|
|
279
|
+
return new Response(JSON.stringify({ identity: null }), { headers: HEADERS });
|
|
280
|
+
}
|
|
281
|
+
const secret = process.env.CHATLY_IDV_SECRET;
|
|
282
|
+
if (secret === undefined || secret === '') {
|
|
283
|
+
// Loud rather than anonymous: a signed-in user silently chatting as a
|
|
284
|
+
// stranger is a misconfiguration nobody would notice.
|
|
285
|
+
return new Response(JSON.stringify({ error: 'CHATLY_IDV_SECRET is not set on the server' }), {
|
|
286
|
+
status: 500,
|
|
287
|
+
headers: HEADERS,
|
|
288
|
+
});
|
|
289
|
+
}
|
|
290
|
+
return new Response(
|
|
291
|
+
JSON.stringify({ identity: { externalId, userHash: chatlyUserHash(secret, externalId) } }),
|
|
292
|
+
{ headers: HEADERS },
|
|
293
|
+
);
|
|
294
|
+
};
|
|
295
|
+
`;
|
|
296
|
+
/**
|
|
297
|
+
* The snippet is a string in the frontmatter and rendered with `set:html`, not
|
|
298
|
+
* pasted into the template: a bare `<script>` in an `.astro` file is bundled
|
|
299
|
+
* and turned into a module, which would move the shim off `window` and out of
|
|
300
|
+
* order. `set:html` emits it byte for byte, and `is:inline` does the same for
|
|
301
|
+
* the identify call.
|
|
302
|
+
*/
|
|
303
|
+
const COMPONENT_ASTRO = (input) => `---
|
|
304
|
+
/**
|
|
305
|
+
* Chatly for web channel ${input.publicId}. Generated by \`chatly init astro\`
|
|
306
|
+
* on ${input.generatedOn}. Render <Chatly /> once, before </body> in your
|
|
307
|
+
* layout. The first script is the channel's own embed snippet; the second
|
|
308
|
+
* identifies a signed-in visitor through /api/chatly/identity
|
|
309
|
+
* (src/pages/api/chatly/identity.ts).
|
|
310
|
+
*/
|
|
311
|
+
const snippet = ${JSON.stringify(input.embed.snippet)};
|
|
312
|
+
---
|
|
313
|
+
|
|
314
|
+
<Fragment set:html={snippet} />
|
|
315
|
+
<script is:inline>
|
|
316
|
+
fetch('/api/chatly/identity', { cache: 'no-store' })
|
|
317
|
+
.then(function (r) { return r.ok ? r.json() : { identity: null }; })
|
|
318
|
+
.then(function (body) { if (body.identity) Chatly('identify', body.identity); })
|
|
319
|
+
.catch(function () {});
|
|
320
|
+
</script>
|
|
321
|
+
`;
|
|
322
|
+
/** Every file `chatly init <framework>` writes, in the order it reports them. */
|
|
323
|
+
export function scaffold(framework, input) {
|
|
324
|
+
const common = [cspModule(input), privacyTable(input)];
|
|
325
|
+
if (framework === 'next') {
|
|
326
|
+
return [
|
|
327
|
+
...(input.identity === 'hmac'
|
|
328
|
+
? [{ path: 'app/api/chatly/identity/route.ts', contents: IDENTITY_ROUTE_NEXT(input) }]
|
|
329
|
+
: []),
|
|
330
|
+
{
|
|
331
|
+
path: 'components/chatly.tsx',
|
|
332
|
+
contents: input.identity === 'hmac' ? LAUNCHER_NEXT_HMAC(input) : LAUNCHER_NEXT_ATLAS(input),
|
|
333
|
+
},
|
|
334
|
+
...common,
|
|
335
|
+
];
|
|
336
|
+
}
|
|
337
|
+
if (framework === 'remix') {
|
|
338
|
+
return [
|
|
339
|
+
...(input.identity === 'hmac'
|
|
340
|
+
? [{ path: 'app/routes/api.chatly.identity.ts', contents: IDENTITY_ROUTE_REMIX(input) }]
|
|
341
|
+
: []),
|
|
342
|
+
{
|
|
343
|
+
path: 'app/components/chatly.tsx',
|
|
344
|
+
contents: input.identity === 'hmac' ? LAUNCHER_REMIX_HMAC(input) : LAUNCHER_REMIX_ATLAS(input),
|
|
345
|
+
},
|
|
346
|
+
...common,
|
|
347
|
+
];
|
|
348
|
+
}
|
|
349
|
+
if (framework === 'astro') {
|
|
350
|
+
return [
|
|
351
|
+
{ path: 'src/pages/api/chatly/identity.ts', contents: IDENTITY_ROUTE_ASTRO(input) },
|
|
352
|
+
{ path: 'src/components/Chatly.astro', contents: COMPONENT_ASTRO(input) },
|
|
353
|
+
...common,
|
|
354
|
+
];
|
|
355
|
+
}
|
|
356
|
+
return [
|
|
357
|
+
{ path: 'chatly/identity.mjs', contents: IDENTITY_EXPRESS(input) },
|
|
358
|
+
{ path: 'chatly/snippet.html', contents: SNIPPET_EXPRESS(input) },
|
|
359
|
+
...common,
|
|
360
|
+
];
|
|
361
|
+
}
|
|
362
|
+
//# sourceMappingURL=templates.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"templates.js","sourceRoot":"","sources":["../../src/init/templates.ts"],"names":[],"mappings":"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"}
|