From 1fd26ff98b94bc48f6fa98433282216f8c8e60fb Mon Sep 17 00:00:00 2001 From: Ramadan Omar <55840806+ramadanomar@users.noreply.github.com> Date: Wed, 8 May 2024 19:22:59 +0300 Subject: [PATCH] Improvement: Added more context to Shopify webhook registering errors (#1090) * Improvement: Added more context to Shopify webhook registering errors Co-authored-by: Ahmed Ramadan * revert: shopify reference jobs * fix: added error handling to some shopify client edge cases * small changes to crud error handling --------- Co-authored-by: Ahmed Ramadan Co-authored-by: nicktrn <55853254+nicktrn@users.noreply.github.com> --- .changeset/blue-pumas-whisper.md | 5 ++ integrations/shopify/src/index.ts | 24 +++++++- integrations/shopify/src/webhooks.ts | 85 +++++++++++++++++++--------- 3 files changed, 87 insertions(+), 27 deletions(-) create mode 100644 .changeset/blue-pumas-whisper.md diff --git a/.changeset/blue-pumas-whisper.md b/.changeset/blue-pumas-whisper.md new file mode 100644 index 0000000000..9bfa259b8b --- /dev/null +++ b/.changeset/blue-pumas-whisper.md @@ -0,0 +1,5 @@ +--- +"@trigger.dev/shopify": patch +--- + +improved error messages when a shopify webhook fails to register diff --git a/integrations/shopify/src/index.ts b/integrations/shopify/src/index.ts index 2bc03a14c4..882f866e60 100644 --- a/integrations/shopify/src/index.ts +++ b/integrations/shopify/src/index.ts @@ -63,8 +63,30 @@ export class Shopify implements TriggerIntegration { throw `Can't create Shopify integration (${options.id}) as apiKey was undefined`; } + if (Object.keys(options).includes("apiSecretKey") && !options.apiSecretKey) { + throw `Can't create Shopify integration (${options.id}) as apiSecretKey was undefined`; + } + + if (Object.keys(options).includes("adminAccessToken") && !options.adminAccessToken) { + throw `Can't create Shopify integration (${options.id}) as adminAccessToken was undefined`; + } + + if (Object.keys(options).includes("hostName") && !options.hostName) { + throw `Can't create Shopify integration (${options.id}) as hostName was undefined`; + } + this._options = options; - this._shopDomain = this._options.hostName.replace("http://", "").replace("https://", ""); + // Extract the shop domain if user has entered the full URL + this._shopDomain = this._options.hostName + .replace(/^https?:\/\//, "") // Remove http:// or https:// + .replace(/\/$/, ""); // Remove trailing slash if it exists (e.g. `example.myshopify.com/`) + + // Regular expression to ensure the shopDomain is a valid `.myshopify.com` domain + const shopifyDomainPattern = /^[a-zA-Z0-9-]+\.myshopify\.com$/; + + if (!shopifyDomainPattern.test(this._shopDomain)) { + throw `Can't create Shopify integration (${options.id}) because hostName should be a valid ".myshopify.com" domain, not a custom primary domain. For example: my-domain.myshopify.com`; + } } get authSource() { diff --git a/integrations/shopify/src/webhooks.ts b/integrations/shopify/src/webhooks.ts index c39bcd8fa7..8bcc8b0dec 100644 --- a/integrations/shopify/src/webhooks.ts +++ b/integrations/shopify/src/webhooks.ts @@ -86,21 +86,36 @@ export function createWebhookEventSource(integration: Shopify) { key: (params) => params.topic, crud: { create: async ({ io, ctx }) => { - const webhook = await io.integration.rest.Webhook.save("create-webhook", { - fromData: { - address: ctx.url, - topic: ctx.params.topic, - // fields: ctx.params.fields, - }, - }); - - const clientSecret = await io.integration.runTask( - "get-client-secret", - async (client) => client.config.apiSecretKey - ); - - await io.store.job.set("set-id", "webhook-id", webhook.id); - await io.store.job.set("set-secret", "webhook-secret", clientSecret); + try { + const webhook = await io.integration.rest.Webhook.save("create-webhook", { + fromData: { + address: ctx.url, + topic: ctx.params.topic, + // fields: ctx.params.fields, + }, + }); + + if (!webhook.id) { + throw new Error( + "Failed to create webhook. Ensure your Shopfiy client configuration is correct. Have you set the correct access scopes? Are you using the primary myshopify.com domain?" + ); + } + + const clientSecret = await io.integration.runTask( + "get-client-secret", + async (client) => client.config.apiSecretKey + ); + + await io.store.job.set("set-id", "webhook-id", webhook.id); + await io.store.job.set("set-secret", "webhook-secret", clientSecret); + } catch (error) { + if (error instanceof Error) { + await io.logger.error(`Failed to create webhook: ${error.message}`); + } else { + await io.logger.error("Failed to create webhook", { rawError: error }); + } + throw error; + } }, delete: async ({ io, ctx }) => { const webhookId = await io.store.job.get("get-webhook-id", "webhook-id"); @@ -109,23 +124,41 @@ export function createWebhookEventSource(integration: Shopify) { throw new Error("Missing webhook ID for delete operation."); } - await io.integration.rest.Webhook.delete("delete-webhook", { - id: webhookId, - }); + try { + await io.integration.rest.Webhook.delete("delete-webhook", { + id: webhookId, + }); + } catch (error) { + if (error instanceof Error) { + await io.logger.error(`Failed to delete webhook: ${error.message}`); + } else { + await io.logger.error("Failed to delete webhook", { rawError: error }); + } + throw error; + } await io.store.job.delete("delete-webhook-id", "webhook-id"); }, update: async ({ io, ctx }) => { const webhookId = await io.store.job.get("get-webhook-id", "webhook-id"); - await io.integration.rest.Webhook.save("update-webhook", { - fromData: { - id: webhookId, - address: ctx.url, - topic: ctx.params.topic, - // fields: ctx.params.fields, - }, - }); + try { + await io.integration.rest.Webhook.save("update-webhook", { + fromData: { + id: webhookId, + address: ctx.url, + topic: ctx.params.topic, + // fields: ctx.params.fields, + }, + }); + } catch (error) { + if (error instanceof Error) { + await io.logger.error(`Failed to update webhook: ${error.message}`); + } else { + await io.logger.error("Failed to update webhook", { rawError: error }); + } + throw error; + } }, }, verify: async ({ request, client, ctx }) => {