@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/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 (Received), whether
5
- * deliveries are flowing (Health: the daily check, rejected and failed
6
- * deliveries, and when to warn staff by email), and where Shopify sends them
7
- * (Setup: endpoint, signatures, subscriptions).
8
- * - App events: the log in log.ts, newest first, by event or problems only.
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 Setup." });
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 events.` });
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
- // ---------------------------------------------------------------- Webhooks page
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
- export async function webhooksPage(env_: MonitorEnv, tab: WebhookTab = "received", toast?: Toast, cursor?: string): Promise<Blocks> {
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: "webhooks_tab", value: "received", label: "Back to the newest", style: "secondary" }] }] : []),
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 setupTab = [
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 events keeps the record that they failed.", confirm: "Clear all", deny: "Cancel", style: "danger" },
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 events (kept 30 days).",
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",