@catandbox/schrodinger-web-adapter 0.1.28 → 0.1.32
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 +174 -0
- package/dist/client/file-upload.d.ts +1 -1
- package/dist/client/index.d.ts +2 -2
- package/dist/client/index.js +1 -2
- package/dist/client/new-ticket-form.d.ts +1 -1
- package/dist/client/portal.d.ts +1 -1
- package/dist/client/portal.js +1 -1
- package/dist/client/status-badge.js +2 -2
- package/dist/client/ticket-detail.d.ts +1 -1
- package/dist/client/ticket-detail.js +94 -98
- package/dist/client/ticket-list.d.ts +1 -1
- package/dist/server/index.d.ts +4 -6
- package/dist/server/index.js +3 -1
- package/dist/server/routes.d.ts +15 -0
- package/dist/server/routes.js +353 -0
- package/dist/server/shopifyAuth.d.ts +28 -0
- package/dist/server/shopifyAuth.js +179 -0
- package/dist/server/signing.d.ts +18 -0
- package/dist/server/signing.js +25 -0
- package/dist/server/types.d.ts +60 -0
- package/dist/server/types.js +1 -0
- package/dist/signer.d.ts +29 -2
- package/dist/signer.js +51 -1
- package/package.json +2 -3
package/README.md
ADDED
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
# @catandbox/schrodinger-web-adapter
|
|
2
|
+
|
|
3
|
+
Framework-agnostic web adapter for [Schrodinger](https://github.com/yetone/schrodinger) — an AI-powered headless helpdesk built on Cloudflare Workers.
|
|
4
|
+
|
|
5
|
+
This package provides everything needed to embed a full customer-facing support portal into any web app, plus the server-side Shopify proxy and webhook utilities. **No React dependency** — the UI renders with [Shopify Polaris Web Components](https://shopify.dev/docs/api/app-home/polaris-web-components).
|
|
6
|
+
|
|
7
|
+
## Installation
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
npm install @catandbox/schrodinger-web-adapter
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## Package exports
|
|
14
|
+
|
|
15
|
+
| Entry point | Contents |
|
|
16
|
+
|---|---|
|
|
17
|
+
| `@catandbox/schrodinger-web-adapter/client` | Portal UI renderer + `SupportApiClient` |
|
|
18
|
+
| `@catandbox/schrodinger-web-adapter/server` | Shopify proxy handler, webhook forwarders, auth utilities |
|
|
19
|
+
| `@catandbox/schrodinger-web-adapter/signer` | HMAC request signing primitives |
|
|
20
|
+
|
|
21
|
+
---
|
|
22
|
+
|
|
23
|
+
## Client — portal UI
|
|
24
|
+
|
|
25
|
+
Renders a complete support portal (ticket list → detail → new ticket form) into any `HTMLElement`. Requires Polaris Web Components to be loaded on the page.
|
|
26
|
+
|
|
27
|
+
```ts
|
|
28
|
+
import { renderSupportPortal } from "@catandbox/schrodinger-web-adapter/client";
|
|
29
|
+
|
|
30
|
+
const container = document.getElementById("support-root")!;
|
|
31
|
+
|
|
32
|
+
await renderSupportPortal(container, {
|
|
33
|
+
basePath: "/support/api", // path where the server proxy is mounted
|
|
34
|
+
// headers: { "Authorization": "Bearer ..." },
|
|
35
|
+
// getHeaders: async () => ({ "Authorization": `Bearer ${await getToken()}` }),
|
|
36
|
+
});
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
### Individual renderers
|
|
40
|
+
|
|
41
|
+
You can also render each view independently:
|
|
42
|
+
|
|
43
|
+
```ts
|
|
44
|
+
import {
|
|
45
|
+
renderTicketList,
|
|
46
|
+
renderTicketDetail,
|
|
47
|
+
renderNewTicketForm,
|
|
48
|
+
EventEmitter,
|
|
49
|
+
} from "@catandbox/schrodinger-web-adapter/client";
|
|
50
|
+
|
|
51
|
+
const emitter = new EventEmitter();
|
|
52
|
+
|
|
53
|
+
await renderTicketList(container, client, emitter);
|
|
54
|
+
// emitter emits "ticket:select", "ticket:create"
|
|
55
|
+
|
|
56
|
+
await renderTicketDetail(container, client, ticketId, emitter);
|
|
57
|
+
// emitter emits "ticket:back", "ticket:closed", "ticket:reopened"
|
|
58
|
+
|
|
59
|
+
await renderNewTicketForm(container, client, categories, emitter);
|
|
60
|
+
// emitter emits "ticket:created", "ticket:cancel"
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
### SupportApiClient
|
|
64
|
+
|
|
65
|
+
The API client is re-exported for convenience — see [`@catandbox/schrodinger-contracts`](https://www.npmjs.com/package/@catandbox/schrodinger-contracts) for full documentation.
|
|
66
|
+
|
|
67
|
+
```ts
|
|
68
|
+
import { SupportApiClient } from "@catandbox/schrodinger-web-adapter/client";
|
|
69
|
+
|
|
70
|
+
const client = new SupportApiClient({ basePath: "/support/api" });
|
|
71
|
+
const { items } = await client.listTickets();
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
---
|
|
75
|
+
|
|
76
|
+
## Server — Shopify proxy
|
|
77
|
+
|
|
78
|
+
The server module provides a ready-made HTTP proxy that sits between your Shopify app's frontend and the Schrodinger API. It verifies the Shopify session token on every request and signs outbound calls with HMAC credentials.
|
|
79
|
+
|
|
80
|
+
```ts
|
|
81
|
+
import { createShopifyProxyHandler } from "@catandbox/schrodinger-web-adapter/server";
|
|
82
|
+
|
|
83
|
+
const handler = createShopifyProxyHandler({
|
|
84
|
+
schApiBaseUrl: "https://your-schrodinger-api.example.com",
|
|
85
|
+
schAppId: env.SCH_APP_ID,
|
|
86
|
+
schKeyId: env.SCH_KEY_ID,
|
|
87
|
+
schSecret: env.SCH_SECRET,
|
|
88
|
+
shopifyApiKey: env.SHOPIFY_API_KEY,
|
|
89
|
+
shopifyApiSecret: env.SHOPIFY_API_SECRET,
|
|
90
|
+
basePath: "/support/api", // optional, default: "/support/api"
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
// Cloudflare Workers / any fetch-based runtime:
|
|
94
|
+
export default {
|
|
95
|
+
async fetch(request: Request): Promise<Response> {
|
|
96
|
+
return handler(request);
|
|
97
|
+
},
|
|
98
|
+
};
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
### GDPR webhook forwarding
|
|
102
|
+
|
|
103
|
+
```ts
|
|
104
|
+
import { createShopifyWebhookHandlers } from "@catandbox/schrodinger-web-adapter/server";
|
|
105
|
+
|
|
106
|
+
const webhooks = createShopifyWebhookHandlers({
|
|
107
|
+
schApiBaseUrl: "https://your-schrodinger-api.example.com",
|
|
108
|
+
schAdminApiToken: env.SCH_ADMIN_API_TOKEN,
|
|
109
|
+
shopifyApiKey: env.SHOPIFY_API_KEY,
|
|
110
|
+
shopifyApiSecret: env.SHOPIFY_API_SECRET,
|
|
111
|
+
// called on app/uninstalled to revoke portal access:
|
|
112
|
+
disablePortalAccess: async ({ shopDomain }) => { /* ... */ },
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
// Wire up to your router:
|
|
116
|
+
router.post("/webhooks/customers/data_request", webhooks.handleCustomersDataRequestWebhook);
|
|
117
|
+
router.post("/webhooks/customers/redact", webhooks.handleCustomersRedactWebhook);
|
|
118
|
+
router.post("/webhooks/shop/redact", webhooks.handleShopRedactWebhook);
|
|
119
|
+
router.post("/webhooks/app/uninstalled", webhooks.handleAppUninstalledWebhook);
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
### Auth utilities
|
|
123
|
+
|
|
124
|
+
```ts
|
|
125
|
+
import {
|
|
126
|
+
createPrincipalContext,
|
|
127
|
+
verifyShopifySessionToken,
|
|
128
|
+
verifyShopifyWebhookHmac,
|
|
129
|
+
ShopifyAuthError,
|
|
130
|
+
} from "@catandbox/schrodinger-web-adapter/server";
|
|
131
|
+
|
|
132
|
+
// Verify a Shopify session JWT and extract principal info
|
|
133
|
+
const principal = await createPrincipalContext(request, {
|
|
134
|
+
shopifyApiKey: env.SHOPIFY_API_KEY,
|
|
135
|
+
shopifyApiSecret: env.SHOPIFY_API_SECRET,
|
|
136
|
+
});
|
|
137
|
+
// principal.tenantExternalId — shop domain
|
|
138
|
+
// principal.principalExternalId — customer identifier
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
---
|
|
142
|
+
|
|
143
|
+
## Signer — HMAC request signing
|
|
144
|
+
|
|
145
|
+
Low-level primitives for signing requests to the Schrodinger API with `HMAC-SHA256`. Useful when making server-to-server calls outside of the proxy handler.
|
|
146
|
+
|
|
147
|
+
```ts
|
|
148
|
+
import { signRequest, sha256Hex } from "@catandbox/schrodinger-web-adapter/signer";
|
|
149
|
+
|
|
150
|
+
const headers = await signRequest({
|
|
151
|
+
appId: "my-app",
|
|
152
|
+
keyId: "key-1",
|
|
153
|
+
keySecret: "secret",
|
|
154
|
+
timestamp: Math.floor(Date.now() / 1000),
|
|
155
|
+
nonce: crypto.randomUUID(),
|
|
156
|
+
method: "POST",
|
|
157
|
+
path: "/v1/tickets",
|
|
158
|
+
rawBody: JSON.stringify({ title: "Hello" }),
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
// headers["X-Sch-AppId"], ["X-Sch-KeyId"], ["X-Sch-Timestamp"],
|
|
162
|
+
// ["X-Sch-Nonce"], ["Content-SHA256"], ["X-Sch-Signature"]
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
---
|
|
166
|
+
|
|
167
|
+
## Requirements
|
|
168
|
+
|
|
169
|
+
- **Runtime**: any environment with `fetch`, `crypto.subtle`, and `TextEncoder` (Cloudflare Workers, modern browsers, Node.js ≥ 18)
|
|
170
|
+
- **UI**: Polaris Web Components must be loaded on the page before calling client renderers
|
|
171
|
+
|
|
172
|
+
## License
|
|
173
|
+
|
|
174
|
+
MIT
|
package/dist/client/index.d.ts
CHANGED
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
* Client-side exports — Vanilla TS + Polaris Web Components UI.
|
|
3
3
|
* No React dependency.
|
|
4
4
|
*/
|
|
5
|
-
export { SupportApiClient, SupportApiError } from "@catandbox/schrodinger-
|
|
6
|
-
export type { ClientHeadersProvider, ListTicketsParams, SupportCategory, SupportClientOptions, TicketDetailData, UploadCompleteInput, UploadInitResult, UploadInputFile } from "@catandbox/schrodinger-
|
|
5
|
+
export { SupportApiClient, SupportApiError } from "@catandbox/schrodinger-contracts";
|
|
6
|
+
export type { ClientHeadersProvider, ListTicketsParams, SupportCategory, SupportClientOptions, TicketDetailData, UploadCompleteInput, UploadInitResult, UploadInputFile } from "@catandbox/schrodinger-contracts";
|
|
7
7
|
export { renderSupportPortal } from "./portal";
|
|
8
8
|
export type { SupportPortalOptions } from "./portal";
|
|
9
9
|
export { renderTicketList } from "./ticket-list";
|
package/dist/client/index.js
CHANGED
|
@@ -2,8 +2,7 @@
|
|
|
2
2
|
* Client-side exports — Vanilla TS + Polaris Web Components UI.
|
|
3
3
|
* No React dependency.
|
|
4
4
|
*/
|
|
5
|
-
|
|
6
|
-
export { SupportApiClient, SupportApiError } from "@catandbox/schrodinger-shopify-adapter/client";
|
|
5
|
+
export { SupportApiClient, SupportApiError } from "@catandbox/schrodinger-contracts";
|
|
7
6
|
// Vanilla rendering functions
|
|
8
7
|
export { renderSupportPortal } from "./portal";
|
|
9
8
|
export { renderTicketList } from "./ticket-list";
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { SupportApiClient, SupportCategory } from "@catandbox/schrodinger-
|
|
1
|
+
import type { SupportApiClient, SupportCategory } from "@catandbox/schrodinger-contracts";
|
|
2
2
|
import type { EventEmitter } from "./events";
|
|
3
3
|
export interface NewTicketFormEvents {
|
|
4
4
|
[key: string]: unknown;
|
package/dist/client/portal.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { SupportClientOptions, SupportCategory } from "@catandbox/schrodinger-
|
|
1
|
+
import type { SupportClientOptions, SupportCategory } from "@catandbox/schrodinger-contracts";
|
|
2
2
|
export interface SupportPortalOptions extends SupportClientOptions {
|
|
3
3
|
/** Pre-loaded categories. If omitted, categories are fetched from the API. */
|
|
4
4
|
categories?: SupportCategory[];
|
package/dist/client/portal.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { SupportApiClient } from "@catandbox/schrodinger-
|
|
1
|
+
import { SupportApiClient } from "@catandbox/schrodinger-contracts";
|
|
2
2
|
import { EventEmitter } from "./events";
|
|
3
3
|
import { renderTicketList } from "./ticket-list";
|
|
4
4
|
import { renderTicketDetail } from "./ticket-detail";
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
const STATUS_CONFIG = {
|
|
2
2
|
Active: { label: "Active", tone: "info" },
|
|
3
|
-
InProgress: { label: "In Progress", tone: "
|
|
3
|
+
InProgress: { label: "In Progress", tone: "caution" },
|
|
4
4
|
AwaitingResponse: { label: "Awaiting Response", tone: "warning" },
|
|
5
5
|
Closed: { label: "Closed", tone: "success" },
|
|
6
|
-
Archived: { label: "Archived", tone: "
|
|
6
|
+
Archived: { label: "Archived", tone: "neutral" }
|
|
7
7
|
};
|
|
8
8
|
export function renderStatusBadge(status) {
|
|
9
9
|
const config = STATUS_CONFIG[status] ?? { label: status, tone: "subdued" };
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { SupportApiClient } from "@catandbox/schrodinger-
|
|
1
|
+
import type { SupportApiClient } from "@catandbox/schrodinger-contracts";
|
|
2
2
|
import type { EventEmitter } from "./events";
|
|
3
3
|
export interface TicketDetailEvents {
|
|
4
4
|
[key: string]: unknown;
|
|
@@ -28,9 +28,9 @@ export async function renderTicketDetail(container, client, ticketId, emitter) {
|
|
|
28
28
|
container.innerHTML = `
|
|
29
29
|
<s-card>
|
|
30
30
|
<s-box padding="large">
|
|
31
|
-
<
|
|
32
|
-
<s-spinner
|
|
33
|
-
</
|
|
31
|
+
<s-stack direction="inline" justifyContent="center" padding="large none">
|
|
32
|
+
<s-spinner></s-spinner>
|
|
33
|
+
</s-stack>
|
|
34
34
|
</s-box>
|
|
35
35
|
</s-card>
|
|
36
36
|
`;
|
|
@@ -44,89 +44,89 @@ export async function renderTicketDetail(container, client, ticketId, emitter) {
|
|
|
44
44
|
const aliasMap = new Map(portalConfig.aliases.map((a) => [a.id, a]));
|
|
45
45
|
const assignedAlias = ticket.assignedAliasId ? (aliasMap.get(ticket.assignedAliasId) ?? null) : null;
|
|
46
46
|
setInnerHtml(container, `
|
|
47
|
-
<s-
|
|
48
|
-
<s-
|
|
49
|
-
<s-
|
|
50
|
-
<
|
|
51
|
-
<
|
|
52
|
-
<
|
|
53
|
-
<
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
47
|
+
<s-stack gap="base">
|
|
48
|
+
<s-card>
|
|
49
|
+
<s-box padding="large">
|
|
50
|
+
<s-stack gap="large">
|
|
51
|
+
<div>
|
|
52
|
+
<s-button variant="tertiary" id="sch-back-btn">
|
|
53
|
+
<span style="display:inline-flex; align-items:center; gap:4px;">
|
|
54
|
+
<svg width="16" height="16" viewBox="0 0 20 20" fill="currentColor"><path d="M12.707 15.707a1 1 0 0 1-1.414 0l-5-5a1 1 0 0 1 0-1.414l5-5a1 1 0 1 1 1.414 1.414L8.414 10l4.293 4.293a1 1 0 0 1 0 1.414z"/></svg>
|
|
55
|
+
All tickets
|
|
56
|
+
</span>
|
|
57
|
+
</s-button>
|
|
58
|
+
</div>
|
|
58
59
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
60
|
+
<s-stack direction="inline" justifyContent="space-between" alignItems="start" gap="base">
|
|
61
|
+
<div style="min-width:0; flex:1;">
|
|
62
|
+
<s-heading>${escapeHtml(ticket.title)}</s-heading>
|
|
63
|
+
<s-stack direction="inline" alignItems="center" gap="small" style="margin-top:4px; flex-wrap:wrap;">
|
|
64
|
+
${renderStatusBadge(ticket.status)}
|
|
65
|
+
<s-text tone="neutral">#${ticket.id.slice(0, 8)}</s-text>
|
|
66
|
+
<s-text tone="neutral">·</s-text>
|
|
67
|
+
<s-text tone="neutral">Created ${formatFullDate(ticket.createdAt)}</s-text>
|
|
68
|
+
</s-stack>
|
|
67
69
|
</div>
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
</
|
|
71
|
-
</s-
|
|
72
|
-
</s-
|
|
73
|
-
</s-card>
|
|
70
|
+
${!isOpen ? `<s-button id="sch-reopen-ticket-btn">Reopen</s-button>` : ""}
|
|
71
|
+
</s-stack>
|
|
72
|
+
</s-stack>
|
|
73
|
+
</s-box>
|
|
74
|
+
</s-card>
|
|
74
75
|
|
|
75
|
-
|
|
76
|
+
${assignedAlias
|
|
76
77
|
? `
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
<
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
</s-box>
|
|
89
|
-
</s-card>`
|
|
78
|
+
<s-card>
|
|
79
|
+
<s-box padding="base large">
|
|
80
|
+
<s-stack direction="inline" alignItems="center" gap="small">
|
|
81
|
+
<s-avatar size="base" initials="${escapeHtml(getInitials(assignedAlias.displayName))}"${assignedAlias.avatarUrl ? ` src="${escapeHtml(assignedAlias.avatarUrl)}"` : ""}></s-avatar>
|
|
82
|
+
<div>
|
|
83
|
+
<s-text type="strong">${escapeHtml(assignedAlias.displayName)}</s-text>
|
|
84
|
+
<div><s-text tone="neutral">${escapeHtml(assignedAlias.department)}</s-text></div>
|
|
85
|
+
</div>
|
|
86
|
+
</s-stack>
|
|
87
|
+
</s-box>
|
|
88
|
+
</s-card>`
|
|
90
89
|
: ""}
|
|
91
90
|
|
|
92
|
-
|
|
93
|
-
|
|
91
|
+
<div id="sch-messages-list">
|
|
92
|
+
${messages.length === 0
|
|
94
93
|
? `
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
: `<
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
94
|
+
<s-card>
|
|
95
|
+
<s-box padding="large">
|
|
96
|
+
<div style="text-align:center; padding:24px 0;">
|
|
97
|
+
<s-text tone="neutral">No messages yet. Our team will respond shortly.</s-text>
|
|
98
|
+
</div>
|
|
99
|
+
</s-box>
|
|
100
|
+
</s-card>`
|
|
101
|
+
: `<s-stack gap="small-100">
|
|
102
|
+
${messages.map((msg) => renderMessage(msg, aliasMap.get(msg.authorAliasId ?? "") ?? null)).join("")}
|
|
103
|
+
</s-stack>`}
|
|
104
|
+
</div>
|
|
106
105
|
|
|
107
|
-
|
|
106
|
+
${isOpen
|
|
108
107
|
? `
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
108
|
+
<s-card>
|
|
109
|
+
<s-box padding="large">
|
|
110
|
+
<div id="sch-reply-section">
|
|
111
|
+
<s-stack gap="base">
|
|
112
|
+
<s-heading>Reply</s-heading>
|
|
113
|
+
${renderFormattingField({
|
|
115
114
|
id: "sch-reply-body",
|
|
116
115
|
rows: 4,
|
|
117
116
|
placeholder: "Write your reply...",
|
|
118
117
|
extraStyle: "resize:vertical"
|
|
119
118
|
})}
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
119
|
+
<div id="sch-reply-files"></div>
|
|
120
|
+
<s-stack direction="inline" justifyContent="space-between" alignItems="center">
|
|
121
|
+
<s-button id="sch-close-ticket-btn">Close Ticket</s-button>
|
|
122
|
+
<s-button variant="primary" id="sch-send-reply-btn">Send Reply</s-button>
|
|
123
|
+
</s-stack>
|
|
124
|
+
</s-stack>
|
|
125
|
+
</div>
|
|
126
|
+
</s-box>
|
|
127
|
+
</s-card>`
|
|
129
128
|
: ""}
|
|
129
|
+
</s-stack>
|
|
130
130
|
`);
|
|
131
131
|
// Back button
|
|
132
132
|
container.querySelector("#sch-back-btn")?.addEventListener("click", () => {
|
|
@@ -198,10 +198,10 @@ export async function renderTicketDetail(container, client, ticketId, emitter) {
|
|
|
198
198
|
<s-card>
|
|
199
199
|
<s-box padding="large">
|
|
200
200
|
<s-stack gap="base">
|
|
201
|
-
<s-banner tone="critical">
|
|
202
|
-
<s-text
|
|
201
|
+
<s-banner tone="critical" heading="Failed to load ticket">
|
|
202
|
+
<s-text>${escapeHtml(error instanceof Error ? error.message : String(error))}</s-text>
|
|
203
203
|
</s-banner>
|
|
204
|
-
<s-button variant="
|
|
204
|
+
<s-button variant="tertiary" id="sch-back-btn">
|
|
205
205
|
<span style="display:inline-flex; align-items:center; gap:4px;">
|
|
206
206
|
<svg width="16" height="16" viewBox="0 0 20 20" fill="currentColor"><path d="M12.707 15.707a1 1 0 0 1-1.414 0l-5-5a1 1 0 0 1 0-1.414l5-5a1 1 0 1 1 1.414 1.414L8.414 10l4.293 4.293a1 1 0 0 1 0 1.414z"/></svg>
|
|
207
207
|
All tickets
|
|
@@ -230,39 +230,35 @@ function renderMessage(msg, alias) {
|
|
|
230
230
|
if (isSystem) {
|
|
231
231
|
return `
|
|
232
232
|
<div style="text-align:center; padding:12px 0;">
|
|
233
|
-
<s-text
|
|
233
|
+
<s-text tone="neutral">${escapeHtml(msg.bodyPlain)}</s-text>
|
|
234
234
|
</div>
|
|
235
235
|
`;
|
|
236
236
|
}
|
|
237
|
-
const
|
|
238
|
-
|
|
239
|
-
: "var(--p-color-bg-fill-success, #cdfee1)";
|
|
240
|
-
const avatarColor = isAgent
|
|
241
|
-
? "var(--p-color-text-info, #2c6ecb)"
|
|
242
|
-
: "var(--p-color-text-success, #008060)";
|
|
243
|
-
const avatarLetter = isAgent ? (alias ? getInitials(alias.displayName) : "S") : "Y";
|
|
237
|
+
const avatarInitials = isAgent ? (alias ? getInitials(alias.displayName) : "S") : "Y";
|
|
238
|
+
const avatarSrc = isAgent && alias?.avatarUrl ? ` src="${escapeHtml(alias.avatarUrl)}"` : "";
|
|
244
239
|
const label = isAgent ? (alias ? alias.displayName : "Support") : "You";
|
|
245
240
|
const bubbleBg = isCustomer
|
|
246
241
|
? "var(--p-color-bg-fill-info, #e4f0fe)"
|
|
247
242
|
: "var(--p-color-bg-surface-secondary, #f6f6f7)";
|
|
248
|
-
const
|
|
249
|
-
const
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
<
|
|
254
|
-
</
|
|
255
|
-
<div style="
|
|
256
|
-
<
|
|
257
|
-
<s-text variant="bodySm" fontWeight="semibold">${label}</s-text>
|
|
258
|
-
<s-text variant="bodySm" tone="subdued">${formatTimestamp(msg.createdAt)}</s-text>
|
|
259
|
-
</div>
|
|
260
|
-
<div style="background:${bubbleBg}; padding:10px 14px; border-radius:12px; display:inline-block; text-align:left; max-width:100%;">
|
|
261
|
-
<s-text variant="bodyMd" style="white-space:pre-wrap; word-break:break-word;">${formatBodyText(msg.bodyPlain)}</s-text>
|
|
262
|
-
</div>
|
|
243
|
+
const avatar = `<s-avatar size="small" initials="${escapeHtml(avatarInitials)}"${avatarSrc}></s-avatar>`;
|
|
244
|
+
const content = `
|
|
245
|
+
<div style="min-width:0;">
|
|
246
|
+
<s-stack direction="inline" alignItems="baseline" gap="small" justifyContent="${isCustomer ? "end" : "start"}" style="margin-bottom:4px;">
|
|
247
|
+
<s-text type="strong">${label}</s-text>
|
|
248
|
+
<s-text tone="neutral">${formatTimestamp(msg.createdAt)}</s-text>
|
|
249
|
+
</s-stack>
|
|
250
|
+
<div style="background:${bubbleBg}; padding:10px 14px; border-radius:12px; display:inline-block; text-align:left; max-width:100%;">
|
|
251
|
+
<s-text style="white-space:pre-wrap; word-break:break-word;">${formatBodyText(msg.bodyPlain)}</s-text>
|
|
263
252
|
</div>
|
|
264
253
|
</div>
|
|
265
254
|
`;
|
|
255
|
+
return `
|
|
256
|
+
<div style="padding:10px 0; max-width:85%;${isCustomer ? " margin-left:auto;" : ""}">
|
|
257
|
+
<s-stack direction="inline" alignItems="start" gap="small">
|
|
258
|
+
${isCustomer ? content + avatar : avatar + content}
|
|
259
|
+
</s-stack>
|
|
260
|
+
</div>
|
|
261
|
+
`;
|
|
266
262
|
}
|
|
267
263
|
function showError(container, message) {
|
|
268
264
|
const existing = container.querySelector("#sch-error-banner");
|
|
@@ -271,7 +267,7 @@ function showError(container, message) {
|
|
|
271
267
|
const banner = document.createElement("div");
|
|
272
268
|
banner.id = "sch-error-banner";
|
|
273
269
|
setInnerHtml(banner, `
|
|
274
|
-
<s-banner tone="critical" style="margin-bottom:12px;">
|
|
270
|
+
<s-banner tone="critical" heading="Error" style="margin-bottom:12px;">
|
|
275
271
|
<s-text>${escapeHtml(message)}</s-text>
|
|
276
272
|
</s-banner>
|
|
277
273
|
`);
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { SupportApiClient } from "@catandbox/schrodinger-
|
|
1
|
+
import type { SupportApiClient } from "@catandbox/schrodinger-contracts";
|
|
2
2
|
import type { EventEmitter } from "./events";
|
|
3
3
|
export interface TicketListEvents {
|
|
4
4
|
[key: string]: unknown;
|
package/dist/server/index.d.ts
CHANGED
|
@@ -1,6 +1,4 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
export type { AdapterEnvironment, PrincipalContext, ProxyHandlerOptions, ShopifySessionVerificationOptions, WebhookForwardingOptions } from "@catandbox/schrodinger-shopify-adapter/server";
|
|
6
|
-
export { ShopifyAuthError, createPrincipalContext, extractShopifySessionToken, verifyShopifySessionToken, verifyShopifyWebhookHmac, signSchrodingerRequest, createShopifyProxyHandler, createShopifyWebhookHandlers, handleAppUninstalledWebhook, handleCustomersDataRequestWebhook, handleCustomersRedactWebhook, handleShopRedactWebhook, parsePrefillRoute } from "@catandbox/schrodinger-shopify-adapter/server";
|
|
1
|
+
export type { AdapterEnvironment, PrefillLengthCaps, PrefillParseOptions, PrefillState, PrincipalContext, ProxyHandlerOptions, ShopifySessionVerificationOptions, WebhookForwardingOptions } from "./types";
|
|
2
|
+
export { ShopifyAuthError, createPrincipalContext, extractShopifySessionToken, verifyShopifySessionToken, verifyShopifyWebhookHmac } from "./shopifyAuth";
|
|
3
|
+
export { signSchrodingerRequest } from "./signing";
|
|
4
|
+
export { createShopifyProxyHandler, createShopifyWebhookHandlers, handleAppUninstalledWebhook, handleCustomersDataRequestWebhook, handleCustomersRedactWebhook, handleShopRedactWebhook, parsePrefillRoute } from "./routes";
|
package/dist/server/index.js
CHANGED
|
@@ -1 +1,3 @@
|
|
|
1
|
-
export { ShopifyAuthError, createPrincipalContext, extractShopifySessionToken, verifyShopifySessionToken, verifyShopifyWebhookHmac
|
|
1
|
+
export { ShopifyAuthError, createPrincipalContext, extractShopifySessionToken, verifyShopifySessionToken, verifyShopifyWebhookHmac } from "./shopifyAuth";
|
|
2
|
+
export { signSchrodingerRequest } from "./signing";
|
|
3
|
+
export { createShopifyProxyHandler, createShopifyWebhookHandlers, handleAppUninstalledWebhook, handleCustomersDataRequestWebhook, handleCustomersRedactWebhook, handleShopRedactWebhook, parsePrefillRoute } from "./routes";
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { PrefillParseOptions, PrefillState, ProxyHandlerOptions, WebhookForwardingOptions } from "./types";
|
|
2
|
+
export declare function createShopifyProxyHandler(options: ProxyHandlerOptions): (request: Request) => Promise<Response>;
|
|
3
|
+
export declare function parsePrefillRoute(input: string | URL, options: PrefillParseOptions): PrefillState;
|
|
4
|
+
interface ShopifyWebhookHandlers {
|
|
5
|
+
handleCustomersDataRequestWebhook: (request: Request) => Promise<Response>;
|
|
6
|
+
handleCustomersRedactWebhook: (request: Request) => Promise<Response>;
|
|
7
|
+
handleShopRedactWebhook: (request: Request) => Promise<Response>;
|
|
8
|
+
handleAppUninstalledWebhook: (request: Request) => Promise<Response>;
|
|
9
|
+
}
|
|
10
|
+
export declare function createShopifyWebhookHandlers(options: WebhookForwardingOptions): ShopifyWebhookHandlers;
|
|
11
|
+
export declare function handleCustomersDataRequestWebhook(request: Request, options: WebhookForwardingOptions): Promise<Response>;
|
|
12
|
+
export declare function handleCustomersRedactWebhook(request: Request, options: WebhookForwardingOptions): Promise<Response>;
|
|
13
|
+
export declare function handleShopRedactWebhook(request: Request, options: WebhookForwardingOptions): Promise<Response>;
|
|
14
|
+
export declare function handleAppUninstalledWebhook(request: Request, options: WebhookForwardingOptions): Promise<Response>;
|
|
15
|
+
export {};
|