UNPKG

@getverdict/n8n-nodes-shopify-partner-relationship-events-trigger

Version:

n8n trigger node that polls Shopify Partner RelationshipEvents via GraphQL

186 lines (185 loc) 9.24 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.ShopifyPartnerRelationshipEventsTrigger = void 0; const n8n_workflow_1 = require("n8n-workflow"); function buildDefaultQuery() { return `query AppEvents($appId: ID!, $first: Int!, $after: String, $types: [AppEventTypes!]) { app(id: $appId) { events(first: $first, after: $after, types: $types) { edges { cursor node { __typename occurredAt shop { avatarUrl id myshopifyDomain name } } } pageInfo { hasNextPage } } } }`; } class ShopifyPartnerRelationshipEventsTrigger { constructor() { this.description = { displayName: 'Shopify Partner: Relationship Events (Trigger)', name: 'shopifyPartnerRelationshipEventsTrigger', icon: 'fa:bolt', group: ['trigger'], version: 2, description: 'Poll Shopify Partner API RelationshipEvents and emit new events', defaults: { name: 'Shopify Partner: Relationship Events' }, inputs: [], outputs: ["main" /* NodeConnectionType.Main */], credentials: [{ name: 'shopifyPartnerApi', required: true }], polling: true, properties: [ { displayName: 'App ID (GID)', name: 'appId', type: 'string', default: '', required: true, description: 'The App GID, e.g. gid://partners/App/123456', }, { displayName: 'API Version', name: 'apiVersion', type: 'string', default: '2025-07', description: 'Shopify Partner API version (e.g. 2025-07)', }, { displayName: 'Page Size', name: 'pageSize', type: 'number', typeOptions: { minValue: 1, maxValue: 250 }, default: 50, description: 'Number of events to request per poll page', }, { displayName: 'Emit Existing Events On First Run', name: 'emitExistingOnStart', type: 'boolean', default: false, description: 'If disabled, initializes cursor without emitting existing events on first run', }, { displayName: 'Event Types Filter', name: 'eventTypes', type: 'multiOptions', default: [], description: 'Filter by event types server-side; leave empty for all', options: [ { name: 'RELATIONSHIP_INSTALLED', value: 'RELATIONSHIP_INSTALLED' }, { name: 'RELATIONSHIP_UNINSTALLED', value: 'RELATIONSHIP_UNINSTALLED' }, { name: 'RELATIONSHIP_REACTIVATED', value: 'RELATIONSHIP_REACTIVATED' }, { name: 'RELATIONSHIP_DEACTIVATED', value: 'RELATIONSHIP_DEACTIVATED' }, { name: 'ONE_TIME_CHARGE_ACCEPTED', value: 'ONE_TIME_CHARGE_ACCEPTED' }, { name: 'ONE_TIME_CHARGE_ACTIVATED', value: 'ONE_TIME_CHARGE_ACTIVATED' }, { name: 'ONE_TIME_CHARGE_DECLINED', value: 'ONE_TIME_CHARGE_DECLINED' }, { name: 'ONE_TIME_CHARGE_EXPIRED', value: 'ONE_TIME_CHARGE_EXPIRED' }, { name: 'SUBSCRIPTION_CHARGE_ACCEPTED', value: 'SUBSCRIPTION_CHARGE_ACCEPTED' }, { name: 'SUBSCRIPTION_CHARGE_ACTIVATED', value: 'SUBSCRIPTION_CHARGE_ACTIVATED' }, { name: 'SUBSCRIPTION_CHARGE_CANCELED', value: 'SUBSCRIPTION_CHARGE_CANCELED' }, { name: 'SUBSCRIPTION_CHARGE_DECLINED', value: 'SUBSCRIPTION_CHARGE_DECLINED' }, { name: 'SUBSCRIPTION_CHARGE_EXPIRED', value: 'SUBSCRIPTION_CHARGE_EXPIRED' }, { name: 'SUBSCRIPTION_CHARGE_FROZEN', value: 'SUBSCRIPTION_CHARGE_FROZEN' }, { name: 'SUBSCRIPTION_CHARGE_UNFROZEN', value: 'SUBSCRIPTION_CHARGE_UNFROZEN' }, { name: 'SUBSCRIPTION_CAPPED_AMOUNT_UPDATED', value: 'SUBSCRIPTION_CAPPED_AMOUNT_UPDATED' }, { name: 'SUBSCRIPTION_APPROACHING_CAPPED_AMOUNT', value: 'SUBSCRIPTION_APPROACHING_CAPPED_AMOUNT' }, { name: 'CREDIT_APPLIED', value: 'CREDIT_APPLIED' }, { name: 'CREDIT_FAILED', value: 'CREDIT_FAILED' }, { name: 'CREDIT_PENDING', value: 'CREDIT_PENDING' }, { name: 'USAGE_CHARGE_APPLIED', value: 'USAGE_CHARGE_APPLIED' }, ], }, ], }; } async poll() { const credentials = await this.getCredentials('shopifyPartnerApi'); const orgRaw = (credentials?.organizationId || '').trim(); if (!orgRaw) { throw new n8n_workflow_1.NodeOperationError(this.getNode(), 'Organization ID is required (set it in credentials or provide an override).'); } const organizationId = orgRaw.match(/^gid:\/\/partners\/Organization\//) ? orgRaw : `gid://partners/Organization/${orgRaw}`; const creds = await this.getCredentials('shopifyPartnerApi'); const credApiVersion = (creds?.apiVersion || '').trim(); const useJsonSuffix = creds?.useJsonSuffix !== false; // default true const apiVersion = (this.getNodeParameter('apiVersion', 0) || credApiVersion || '2025-07').trim(); // Poll scheduling is handled by n8n const pageSize = this.getNodeParameter('pageSize', 0) || 50; const emitExistingOnStart = this.getNodeParameter('emitExistingOnStart', 0); const eventTypes = this.getNodeParameter('eventTypes', 0) || []; const customQuery = buildDefaultQuery(); const state = this.getWorkflowStaticData('node'); let lastCursor = state.lastCursor || ''; let initialized = Boolean(state.initialized); const orgNumericId = organizationId.split('/').pop(); const endpoint = `https://partners.shopify.com/${orgNumericId}/api/${apiVersion}/graphql${useJsonSuffix ? '.json' : ''}`; const fetchPage = async (afterCursor) => { const appIdInput = this.getNodeParameter('appId', 0).trim(); const appId = appIdInput.match(/^gid:\/\/partners\/App\//) ? appIdInput : `gid://partners/App/${appIdInput}`; const baseVariables = { appId, first: pageSize, }; if (afterCursor) { baseVariables.after = afterCursor; } if (eventTypes.length > 0) { baseVariables.types = eventTypes; } const variables = baseVariables; const requestOptions = { method: 'POST', url: endpoint, body: { query: customQuery, variables }, json: true, }; const response = (await this.helpers.httpRequestWithAuthentication.call(this, 'shopifyPartnerApi', requestOptions)); return response; }; const extractConnection = (response) => { // Surface GraphQL errors const gqlErrors = response.errors; if (Array.isArray(gqlErrors) && gqlErrors.length > 0) { throw new Error(JSON.stringify(gqlErrors)); } const data = response.data || {}; const app = data.app || {}; const conn = app.events || {}; return conn; }; try { const response = await fetchPage(lastCursor || undefined); const conn = extractConnection(response); const edges = (conn.edges || []); const pageInfo = conn.pageInfo || {}; const endCursor = edges.length > 0 ? edges[edges.length - 1].cursor : lastCursor; if (!initialized && !emitExistingOnStart) { // Initialize cursor without emitting existing state.lastCursor = endCursor || lastCursor; state.initialized = true; return []; } const items = []; for (const edge of edges) { const cursor = edge.cursor || undefined; const node = edge.node || {}; items.push({ json: { cursor, event: node } }); } if (endCursor) { state.lastCursor = endCursor; } return items.length > 0 ? [items] : null; } catch (error) { const errMsg = error?.message || 'Unknown error during Partner API poll'; return [[{ json: { error: true, message: errMsg } }]]; } } } exports.ShopifyPartnerRelationshipEventsTrigger = ShopifyPartnerRelationshipEventsTrigger; exports.default = ShopifyPartnerRelationshipEventsTrigger;