@redeemid/emdash 0.0.30 → 0.0.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 +3 -3
- package/docs/access.md +7 -5
- package/docs/content.md +1 -1
- package/docs/developer-api.md +2 -2
- package/docs/getting-started.md +4 -4
- package/docs/production.md +3 -3
- package/docs/reference.md +3 -3
- package/docs/shopify-flow.md +2 -2
- package/docs/shopify-setup.md +4 -4
- package/docs/staff.md +21 -9
- package/package.json +11 -2
- package/src/access-admin.ts +211 -76
- package/src/access-map.ts +143 -0
- package/src/admin.ts +505 -0
- package/src/descriptor.ts +16 -9
- package/src/monitor.ts +20 -33
- package/src/plugin.ts +327 -81
- package/src/setup.ts +33 -38
- package/src/staff.ts +148 -66
- package/src/version.ts +1 -1
package/src/monitor.ts
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Watching what RedeemID does, in the admin (Block Kit):
|
|
3
3
|
*
|
|
4
|
-
* - Webhooks: what Shopify sent and what became of it (
|
|
5
|
-
* deliveries are flowing (Health: the daily check, rejected
|
|
6
|
-
* deliveries, and when to warn staff by email), and where Shopify
|
|
7
|
-
* (
|
|
8
|
-
* - App events: the log in log.ts, newest first, by event or
|
|
4
|
+
* - Webhooks: what Shopify sent and what became of it (Events → Webhook),
|
|
5
|
+
* whether deliveries are flowing (Events → Health: the daily check, rejected
|
|
6
|
+
* and failed deliveries, and when to warn staff by email), and where Shopify
|
|
7
|
+
* sends them (Connection → Webhooks: endpoint, signatures, subscriptions).
|
|
8
|
+
* - App events (Events → App): the log in log.ts, newest first, by event or
|
|
9
|
+
* problems only.
|
|
9
10
|
* - Reports: members by level, who joined and left, sign-ins and metered reads.
|
|
10
11
|
*
|
|
11
12
|
* The daily check re-creates subscriptions Shopify dropped and never changes
|
|
@@ -129,7 +130,7 @@ export async function healthCheck(env_: MonitorEnv): Promise<HealthResult> {
|
|
|
129
130
|
|
|
130
131
|
const last = await lastDelivery(s);
|
|
131
132
|
if (settings.quietDays > 0) {
|
|
132
|
-
if (!last) problems.push({ level: "warn", text: "No webhooks received yet. Check the subscriptions under
|
|
133
|
+
if (!last) problems.push({ level: "warn", text: "No webhooks received yet. Check the webhook subscriptions under Connection → Webhooks." });
|
|
133
134
|
else if (Date.parse(last.receivedAt) < now - settings.quietDays * DAY) {
|
|
134
135
|
const days = Math.floor((now - Date.parse(last.receivedAt)) / DAY);
|
|
135
136
|
problems.push({ level: "warn", text: `No webhooks for ${days} days. Quiet stores are normal; if customers changed in Shopify since, check Setup.` });
|
|
@@ -140,7 +141,7 @@ export async function healthCheck(env_: MonitorEnv): Promise<HealthResult> {
|
|
|
140
141
|
const rejected = await count(s, "webhook.rejected", day);
|
|
141
142
|
if (rejected) problems.push({ level: "warn", text: `${rejected} ${rejected === 1 ? "delivery was" : "deliveries were"} turned away in the last day (bad signature): check the webhook signing secret.` });
|
|
142
143
|
const failed = await count(s, "webhook.failed", day);
|
|
143
|
-
if (failed) problems.push({ level: "error", text: `${failed} ${failed === 1 ? "delivery" : "deliveries"} failed to save in the last day. Shopify sends them again; see App
|
|
144
|
+
if (failed) problems.push({ level: "error", text: `${failed} ${failed === 1 ? "delivery" : "deliveries"} failed to save in the last day. Shopify sends them again; see Events → App.` });
|
|
144
145
|
|
|
145
146
|
const result: HealthResult = { at: new Date(now).toISOString(), problems, registered, subscriptions };
|
|
146
147
|
result.emailed = await alert(env_, settings, result, now);
|
|
@@ -195,10 +196,7 @@ export async function tidy(s: Stores): Promise<void> {
|
|
|
195
196
|
|
|
196
197
|
export { topicName };
|
|
197
198
|
|
|
198
|
-
// ----------------------------------------------------------------
|
|
199
|
-
|
|
200
|
-
export const WEBHOOK_TABS = ["received", "health", "setup"] as const;
|
|
201
|
-
export type WebhookTab = (typeof WEBHOOK_TABS)[number];
|
|
199
|
+
// ---------------------------------------------------------------- Webhook panels
|
|
202
200
|
|
|
203
201
|
const OUTCOMES: Record<string, string> = {
|
|
204
202
|
processed: "Processed",
|
|
@@ -210,7 +208,11 @@ const OUTCOMES: Record<string, string> = {
|
|
|
210
208
|
|
|
211
209
|
const cap = (s: string) => s.charAt(0).toUpperCase() + s.slice(1);
|
|
212
210
|
|
|
213
|
-
|
|
211
|
+
/**
|
|
212
|
+
* The webhook parts of the admin: deliveries and their health (Events →
|
|
213
|
+
* Webhook and Health) and the app's subscriptions (Connection → Webhooks).
|
|
214
|
+
*/
|
|
215
|
+
export async function webhookPanels(env_: MonitorEnv, cursor?: string): Promise<{ received: unknown[]; health: unknown[]; subscriptions: unknown[]; attention: boolean }> {
|
|
214
216
|
const { s, kv } = env_;
|
|
215
217
|
const now = env_.now?.() ?? Date.now();
|
|
216
218
|
const month = since(now, LOG_DAYS);
|
|
@@ -236,7 +238,7 @@ export async function webhooksPage(env_: MonitorEnv, tab: WebhookTab = "received
|
|
|
236
238
|
{ label: "Failed", value: String(failed), ...(failed ? { trend: "down" } : {}) },
|
|
237
239
|
],
|
|
238
240
|
},
|
|
239
|
-
...(cursor ? [{ type: "actions", elements: [{ type: "button", action_id: "
|
|
241
|
+
...(cursor ? [{ type: "actions", elements: [{ type: "button", action_id: "deliveries_page", value: "", label: "Back to the newest", style: "secondary" }] }] : []),
|
|
240
242
|
{
|
|
241
243
|
type: "table",
|
|
242
244
|
page_action_id: "deliveries_page",
|
|
@@ -303,7 +305,7 @@ export async function webhooksPage(env_: MonitorEnv, tab: WebhookTab = "received
|
|
|
303
305
|
},
|
|
304
306
|
];
|
|
305
307
|
|
|
306
|
-
const
|
|
308
|
+
const subscriptions = [
|
|
307
309
|
{ type: "context", text: "Where Shopify sends deliveries, how they're checked, and the app's webhook subscriptions." },
|
|
308
310
|
{ type: "section", text: `Endpoint: ${env_.uri}` },
|
|
309
311
|
{
|
|
@@ -316,21 +318,7 @@ export async function webhooksPage(env_: MonitorEnv, tab: WebhookTab = "received
|
|
|
316
318
|
{ type: "context", text: "customers/redact, customers/data_request and shop/redact are set in the app's compliance webhooks, not here." },
|
|
317
319
|
];
|
|
318
320
|
|
|
319
|
-
return {
|
|
320
|
-
blocks: [
|
|
321
|
-
{ type: "header", text: "Webhooks" },
|
|
322
|
-
{
|
|
323
|
-
type: "tab",
|
|
324
|
-
default_tab: WEBHOOK_TABS.indexOf(tab),
|
|
325
|
-
panels: [
|
|
326
|
-
{ label: "Received", blocks: receivedTab },
|
|
327
|
-
{ label: attention ? "Health (!)" : "Health", blocks: healthTab },
|
|
328
|
-
{ label: "Setup", blocks: setupTab },
|
|
329
|
-
],
|
|
330
|
-
},
|
|
331
|
-
],
|
|
332
|
-
...(toast ? { toast } : {}),
|
|
333
|
-
};
|
|
321
|
+
return { received: receivedTab, health: healthTab, subscriptions, attention };
|
|
334
322
|
}
|
|
335
323
|
|
|
336
324
|
const SUBS: Record<HealthResult["subscriptions"], string> = {
|
|
@@ -385,7 +373,6 @@ export async function eventsPage(s: Stores, filter: EventFilter = {}, cursor?: s
|
|
|
385
373
|
const failed = cursor ? null : await s.failedEvents.query({ orderBy: { at: "desc" }, limit: 50 }).catch(() => null);
|
|
386
374
|
return {
|
|
387
375
|
blocks: [
|
|
388
|
-
{ type: "header", text: "App events" },
|
|
389
376
|
{
|
|
390
377
|
type: "context",
|
|
391
378
|
text: `What RedeemID did and what came of it: webhooks, sign-ins, calls to Shopify, staff actions, settings saves and outbound events. Kept ${LOG_DAYS} days. Never bodies, tokens or emails.`,
|
|
@@ -467,7 +454,7 @@ function failedBlocks(list: FailedEvent[]): unknown[] {
|
|
|
467
454
|
action_id: "failed_clear_all",
|
|
468
455
|
label: "Clear all",
|
|
469
456
|
style: "danger",
|
|
470
|
-
confirm: { title: "Clear every failed event?", text: "They won't be sent. App
|
|
457
|
+
confirm: { title: "Clear every failed event?", text: "They won't be sent. Events → App keeps the record that they failed.", confirm: "Clear all", deny: "Cancel", style: "danger" },
|
|
471
458
|
},
|
|
472
459
|
],
|
|
473
460
|
},
|
|
@@ -483,7 +470,7 @@ function customerOfBody(body: string): string {
|
|
|
483
470
|
}
|
|
484
471
|
}
|
|
485
472
|
|
|
486
|
-
function eventRow(e: LogEntry) {
|
|
473
|
+
export function eventRow(e: LogEntry) {
|
|
487
474
|
const status = e.status === "ok" ? "OK" : cap(e.status);
|
|
488
475
|
return {
|
|
489
476
|
event: LOG_EVENTS[e.event] ?? e.event,
|
|
@@ -559,7 +546,7 @@ export async function reportsPage(config: RedeemIDConfig, s: Stores, now = Date.
|
|
|
559
546
|
{ type: "header", text: "Reports" },
|
|
560
547
|
{
|
|
561
548
|
type: "context",
|
|
562
|
-
text: "A level is joined or left when Shopify adds or removes its tag. Counts come from each member's tag dates; sign-ins and metered reads from App
|
|
549
|
+
text: "A level is joined or left when Shopify adds or removes its tag. Counts come from each member's tag dates; sign-ins and metered reads from Events → App (kept 30 days).",
|
|
563
550
|
},
|
|
564
551
|
{
|
|
565
552
|
type: "stats",
|