From 40e997393e0412e33ba1b5267ea08318462dfc7c Mon Sep 17 00:00:00 2001 From: Luan Cazarine Date: Tue, 10 Jun 2025 19:16:40 -0300 Subject: [PATCH 1/3] prodatakey init --- .../create-customer/create-customer.mjs | 65 ++++++ .../retrieve-credential.mjs | 41 ++++ .../retrieve-organization.mjs | 27 +++ components/prodatakey/package.json | 2 +- components/prodatakey/prodatakey.app.mjs | 214 +++++++++++++++++- .../cloud-node-connected-instant.mjs | 101 +++++++++ .../new-event-instant/new-event-instant.mjs | 157 +++++++++++++ 7 files changed, 601 insertions(+), 6 deletions(-) create mode 100644 components/prodatakey/actions/create-customer/create-customer.mjs create mode 100644 components/prodatakey/actions/retrieve-credential/retrieve-credential.mjs create mode 100644 components/prodatakey/actions/retrieve-organization/retrieve-organization.mjs create mode 100644 components/prodatakey/sources/cloud-node-connected-instant/cloud-node-connected-instant.mjs create mode 100644 components/prodatakey/sources/new-event-instant/new-event-instant.mjs diff --git a/components/prodatakey/actions/create-customer/create-customer.mjs b/components/prodatakey/actions/create-customer/create-customer.mjs new file mode 100644 index 0000000000000..345da6c42bb19 --- /dev/null +++ b/components/prodatakey/actions/create-customer/create-customer.mjs @@ -0,0 +1,65 @@ +import prodatakey from "../../prodatakey.app.mjs"; +import { axios } from "@pipedream/platform"; + +export default { + key: "prodatakey-create-customer", + name: "Create Customer", + description: "Creates a new customer in the ProdataKey system. [See the documentation](https://developer.pdk.io/web/2.0/rest/organizations)", + version: "0.0.{{ts}}", + type: "action", + props: { + prodatakey, + dealerId: { + propDefinition: [ + prodatakey, + "dealerId", + ], + }, + name: { + type: "string", + label: "Name", + description: "The name of the customer to be created", + }, + useBluetoothCredentials: { + propDefinition: [ + prodatakey, + "useBluetoothCredentials", + ], + optional: true, + }, + useTouchMobileApp: { + propDefinition: [ + prodatakey, + "useTouchMobileApp", + ], + optional: true, + }, + allowCredentialResets: { + propDefinition: [ + prodatakey, + "allowCredentialResets", + ], + optional: true, + }, + type: { + propDefinition: [ + prodatakey, + "type", + ], + optional: true, + }, + }, + async run({ $ }) { + const response = await this.prodatakey.createCustomer({ + dealerId: this.dealerId, + name: this.name, + useBluetoothCredentials: this.useBluetoothCredentials, + useTouchMobileApp: this.useTouchMobileApp, + allowCredentialResets: this.allowCredentialResets, + type: this.type, + }); + + $.export("$summary", `Successfully created customer with ID: ${response.id}`); + return response; + }, +}; diff --git a/components/prodatakey/actions/retrieve-credential/retrieve-credential.mjs b/components/prodatakey/actions/retrieve-credential/retrieve-credential.mjs new file mode 100644 index 0000000000000..4b7fd1cd6e910 --- /dev/null +++ b/components/prodatakey/actions/retrieve-credential/retrieve-credential.mjs @@ -0,0 +1,41 @@ +import prodatakey from "../../prodatakey.app.mjs"; +import { axios } from "@pipedream/platform"; + +export default { + key: "prodatakey-retrieve-credential", + name: "Retrieve Credential", + description: "Retrieve a specific credential using the system ID, holder ID, and credential ID. [See the documentation](https://developer.pdk.io/web/2.0/rest/credentials)", + version: "0.0.{{ts}}", + type: "action", + props: { + prodatakey, + systemId: { + type: "string", + label: "System ID", + }, + holderId: { + type: "string", + label: "Holder ID", + }, + credentialId: { + propDefinition: [ + prodatakey, + "credentialId", + (c) => ({ + systemId: c.systemId, + holderId: c.holderId, + }), + ], + }, + }, + async run({ $ }) { + const response = await this.prodatakey.retrieveCredential({ + systemId: this.systemId, + holderId: this.holderId, + credentialId: this.credentialId, + }); + + $.export("$summary", `Successfully retrieved credential with ID ${response.id}`); + return response; + }, +}; diff --git a/components/prodatakey/actions/retrieve-organization/retrieve-organization.mjs b/components/prodatakey/actions/retrieve-organization/retrieve-organization.mjs new file mode 100644 index 0000000000000..812bc22ecd900 --- /dev/null +++ b/components/prodatakey/actions/retrieve-organization/retrieve-organization.mjs @@ -0,0 +1,27 @@ +import prodatakey from "../../prodatakey.app.mjs"; +import { axios } from "@pipedream/platform"; + +export default { + key: "prodatakey-retrieve-organization", + name: "Retrieve Organization", + description: "Retrieve an existing organization. [See the documentation](https://developer.pdk.io/web/2.0/rest/organizations)", + version: "0.0.1", + type: "action", + props: { + prodatakey, + organizationId: { + propDefinition: [ + prodatakey, + "organizationId", + ], + }, + }, + async run({ $ }) { + const response = await this.prodatakey.retrieveOrganization({ + organizationId: this.organizationId, + }); + + $.export("$summary", `Successfully retrieved organization: ${response.name}`); + return response; + }, +}; diff --git a/components/prodatakey/package.json b/components/prodatakey/package.json index e6d828f157bb2..abb34071ed275 100644 --- a/components/prodatakey/package.json +++ b/components/prodatakey/package.json @@ -12,4 +12,4 @@ "publishConfig": { "access": "public" } -} \ No newline at end of file +} diff --git a/components/prodatakey/prodatakey.app.mjs b/components/prodatakey/prodatakey.app.mjs index d26f7cf3ec95f..28adc5800633e 100644 --- a/components/prodatakey/prodatakey.app.mjs +++ b/components/prodatakey/prodatakey.app.mjs @@ -1,11 +1,215 @@ +import { axios } from "@pipedream/platform"; + export default { type: "app", app: "prodatakey", - propDefinitions: {}, + propDefinitions: { + organizationId: { + type: "string", + label: "Organization ID", + async options() { + const organizations = await this.listOrganizations(); + return organizations.map((org) => ({ + label: org.name, + value: org.id, + })); + }, + }, + dealerId: { + type: "string", + label: "Dealer ID", + async options() { + const dealers = await this.listDealers(); + return dealers.map((dealer) => ({ + label: dealer.name, + value: dealer.id, + })); + }, + }, + credentialId: { + type: "string", + label: "Credential ID", + async options({ + systemId, holderId, + }) { + const credentials = await this.listCredentials({ + systemId, + holderId, + }); + return credentials.map((credential) => ({ + label: credential.id, + value: credential.id, + })); + }, + }, + systemId: { + type: "string", + label: "System ID", + }, + holderId: { + type: "string", + label: "Holder ID", + }, + name: { + type: "string", + label: "Name", + }, + url: { + type: "string", + label: "URL", + }, + scope: { + type: "string", + label: "Scope", + }, + authenticationType: { + type: "string", + label: "Authentication Type", + }, + events: { + type: "string[]", + label: "Events", + optional: true, + }, + authenticationUser: { + type: "string", + label: "Authentication User", + optional: true, + }, + authenticationPassword: { + type: "string", + label: "Authentication Password", + optional: true, + }, + secret: { + type: "string", + label: "Secret", + optional: true, + }, + useBluetoothCredentials: { + type: "boolean", + label: "Use Bluetooth Credentials", + optional: true, + }, + useTouchMobileApp: { + type: "boolean", + label: "Use Touch Mobile App", + optional: true, + }, + allowCredentialResets: { + type: "boolean", + label: "Allow Credential Resets", + optional: true, + }, + type: { + type: "string", + label: "Type", + optional: true, + }, + }, methods: { - // this.$auth contains connected account data - authKeys() { - console.log(Object.keys(this.$auth)); + _baseUrl() { + return "https://accounts.pdk.io/api"; + }, + async _makeRequest(opts = {}) { + const { + $ = this, method = "GET", path, headers, ...otherOpts + } = opts; + return axios($, { + ...otherOpts, + method, + url: this._baseUrl() + path, + headers: { + ...headers, + Authorization: `Bearer ${this.$auth.oauth_access_token}`, + }, + }); + }, + async listOrganizations(opts = {}) { + return this._makeRequest({ + path: "/organizations", + ...opts, + }); + }, + async listDealers(opts = {}) { + return this._makeRequest({ + path: "/dealers", + ...opts, + }); + }, + async listCredentials(opts = {}) { + const { + systemId, holderId, + } = opts; + return this._makeRequest({ + path: `/systems/${systemId}/holders/${holderId}/credentials`, + }); + }, + async createCustomer({ + dealerId, name, useBluetoothCredentials, useTouchMobileApp, allowCredentialResets, type, + }) { + return this._makeRequest({ + method: "POST", + path: `/organizations/${dealerId}/children`, + data: { + name, + useBluetoothCredentials, + useTouchMobileApp, + allowCredentialResets, + type, + }, + }); + }, + async retrieveOrganization({ organizationId }) { + return this._makeRequest({ + path: `/organizations/${organizationId}`, + }); + }, + async retrieveCredential({ + systemId, holderId, credentialId, + }) { + return this._makeRequest({ + path: `/systems/${systemId}/holders/${holderId}/credentials/${credentialId}`, + }); + }, + async emitEventCloudNodeConnected({ + organizationId, name, url, scope, authenticationType, authenticationUser, authenticationPassword, secret, + }) { + return this._makeRequest({ + method: "POST", + path: "/events", + data: { + event_type: "cloudnode.connected", + organization_id: organizationId, + name, + url, + scope, + authentication_type: authenticationType, + authentication_user: authenticationUser, + authentication_password: authenticationPassword, + secret, + }, + }); + }, + async emitEvent({ + organizationId, name, url, scope, authenticationType, events, authenticationUser, authenticationPassword, secret, + }) { + const eventPromises = events.map((event) => this._makeRequest({ + method: "POST", + path: "/events", + data: { + event_type: event, + organization_id: organizationId, + name, + url, + scope, + authentication_type: authenticationType, + authentication_user: authenticationUser, + authentication_password: authenticationPassword, + secret, + }, + })); + return Promise.all(eventPromises); }, }, -}; \ No newline at end of file +}; diff --git a/components/prodatakey/sources/cloud-node-connected-instant/cloud-node-connected-instant.mjs b/components/prodatakey/sources/cloud-node-connected-instant/cloud-node-connected-instant.mjs new file mode 100644 index 0000000000000..63c410dc74d46 --- /dev/null +++ b/components/prodatakey/sources/cloud-node-connected-instant/cloud-node-connected-instant.mjs @@ -0,0 +1,101 @@ +import prodatakey from "../../prodatakey.app.mjs"; +import { axios } from "@pipedream/platform"; + +export default { + key: "prodatakey-cloud-node-connected-instant", + name: "Cloud Node Connected", + description: "Emit a new event when a cloud node is connected to the ProdataKey system. [See the documentation](https://developer.pdk.io/web/2.0/rest/webhooks)", + version: "0.0.{{ts}}", + type: "source", + dedupe: "unique", + props: { + prodatakey: { + type: "app", + app: "prodatakey", + }, + http: { + type: "$.interface.http", + customResponse: false, + }, + db: "$.service.db", + organizationId: { + propDefinition: [ + prodatakey, + "organizationId", + ], + }, + name: { + propDefinition: [ + prodatakey, + "name", + ], + }, + url: { + propDefinition: [ + prodatakey, + "url", + ], + }, + scope: { + propDefinition: [ + prodatakey, + "scope", + ], + }, + authenticationType: { + propDefinition: [ + prodatakey, + "authenticationType", + ], + }, + authenticationUser: { + propDefinition: [ + prodatakey, + "authenticationUser", + ], + optional: true, + }, + authenticationPassword: { + propDefinition: [ + prodatakey, + "authenticationPassword", + ], + optional: true, + }, + secret: { + propDefinition: [ + prodatakey, + "secret", + ], + optional: true, + }, + }, + hooks: { + async deploy() { + // Fetch historical data for deployment-related setup + // For this specific event type, historical fetching is not applicable + }, + async activate() { + // Mechanism to create webhook if needed at app or integration level + // ProdataKey handles subscriptions for real-time event streaming + }, + async deactivate() { + // Mechanism to clear webhook if needed at app or integration level + // ProdataKey handles cleanup on webhooks + }, + }, + async run(event) { + if (event.body && event.body.topic === "cloudnode.connected") { + const { + id, name, metadata, + } = event.body; + const summary = `Cloud node ${name} connected`; + const ts = metadata.occurred || Date.now(); + this.$emit(event.body, { + id, + summary, + ts, + }); + } + }, +}; diff --git a/components/prodatakey/sources/new-event-instant/new-event-instant.mjs b/components/prodatakey/sources/new-event-instant/new-event-instant.mjs new file mode 100644 index 0000000000000..f95cd23e70e2a --- /dev/null +++ b/components/prodatakey/sources/new-event-instant/new-event-instant.mjs @@ -0,0 +1,157 @@ +import { axios } from "@pipedream/platform"; +import prodatakey from "../../prodatakey.app.mjs"; + +export default { + key: "prodatakey-new-event-instant", + name: "New Event Instant", + description: "Emit new event when an event is triggered. [See the documentation](https://developer.pdk.io/web/2.0/introduction)", + version: "0.0.{{ts}}", + type: "source", + dedupe: "unique", + props: { + prodatakey, + http: { + type: "$.interface.http", + customResponse: true, + }, + db: "$.service.db", + organizationId: { + propDefinition: [ + prodatakey, + "organizationId", + ], + }, + name: { + propDefinition: [ + prodatakey, + "name", + ], + }, + url: { + propDefinition: [ + prodatakey, + "url", + ], + }, + scope: { + propDefinition: [ + prodatakey, + "scope", + ], + }, + authenticationType: { + propDefinition: [ + prodatakey, + "authenticationType", + ], + }, + events: { + propDefinition: [ + prodatakey, + "events", + ], + }, + authenticationUser: { + propDefinition: [ + prodatakey, + "authenticationUser", + ], + optional: true, + }, + authenticationPassword: { + propDefinition: [ + prodatakey, + "authenticationPassword", + ], + optional: true, + }, + secret: { + propDefinition: [ + prodatakey, + "secret", + ], + optional: true, + }, + }, + hooks: { + async deploy() { + const events = await this.prodatakey.emitEvent({ + organizationId: this.organizationId, + name: this.name, + url: this.url, + scope: this.scope, + authenticationType: this.authenticationType, + events: this.events, + authenticationUser: this.authenticationUser, + authenticationPassword: this.authenticationPassword, + secret: this.secret, + }); + + for (const event of events.slice(0, 50)) { + this.$emit(event, { + id: event.id, + summary: `Event triggered for ${event.name}`, + ts: Date.now(), + }); + } + }, + async activate() { + const webhookResponse = await axios(this, { + method: "POST", + url: "https://accounts.pdk.io/api/webhooks", + headers: { + Authorization: `Bearer ${this.prodatakey.$auth.oauth_access_token}`, + }, + data: { + organization_id: this.organizationId, + name: this.name, + url: this.url, + scope: this.scope, + authentication_type: this.authenticationType, + events: this.events, + authentication_user: this.authenticationUser, + authentication_password: this.authenticationPassword, + secret: this.secret, + }, + }); + this.db.set("webhookId", webhookResponse.id); + }, + async deactivate() { + const webhookId = this.db.get("webhookId"); + if (webhookId) { + await axios(this, { + method: "DELETE", + url: `https://accounts.pdk.io/api/webhooks/${webhookId}`, + headers: { + Authorization: `Bearer ${this.prodatakey.$auth.oauth_access_token}`, + }, + }); + this.db.set("webhookId", null); + } + }, + }, + async run(event) { + const { + headers, body, + } = event; + const computedSignature = require("crypto").createHmac("sha256", this.secret || "") + .update(JSON.stringify(body)) + .digest("base64"); + if (headers["x-pdk-signature"] !== computedSignature) { + this.http.respond({ + status: 401, + body: "Unauthorized", + }); + return; + } + this.http.respond({ + status: 200, + body: "OK", + }); + this.$emit(body, { + id: body.id || Date.now(), + summary: `New event: ${body.topic}`, + ts: Date.now(), + }); + }, +}; From be202a95ef3e3cfab8a4c56343069af114871d96 Mon Sep 17 00:00:00 2001 From: Luan Cazarine Date: Thu, 12 Jun 2025 12:38:43 -0300 Subject: [PATCH 2/3] [Components] prodatakey #16916 Sources - New Event (Instant) Actions - Open And Close Device - Retrieve Credential - Retrieve Organization --- .../create-customer/create-customer.mjs | 65 ---- .../open-and-close-device.mjs | 61 ++++ .../retrieve-credential.mjs | 31 +- .../retrieve-organization.mjs | 4 +- components/prodatakey/common/constants.mjs | 127 +++++++ components/prodatakey/package.json | 5 +- components/prodatakey/prodatakey.app.mjs | 328 ++++++++++-------- .../cloud-node-connected-instant.mjs | 101 ------ components/prodatakey/sources/common/base.mjs | 47 +++ .../new-event-instant/new-event-instant.mjs | 172 ++------- .../sources/new-event-instant/test-event.mjs | 21 ++ 11 files changed, 493 insertions(+), 469 deletions(-) delete mode 100644 components/prodatakey/actions/create-customer/create-customer.mjs create mode 100644 components/prodatakey/actions/open-and-close-device/open-and-close-device.mjs create mode 100644 components/prodatakey/common/constants.mjs delete mode 100644 components/prodatakey/sources/cloud-node-connected-instant/cloud-node-connected-instant.mjs create mode 100644 components/prodatakey/sources/common/base.mjs create mode 100644 components/prodatakey/sources/new-event-instant/test-event.mjs diff --git a/components/prodatakey/actions/create-customer/create-customer.mjs b/components/prodatakey/actions/create-customer/create-customer.mjs deleted file mode 100644 index 345da6c42bb19..0000000000000 --- a/components/prodatakey/actions/create-customer/create-customer.mjs +++ /dev/null @@ -1,65 +0,0 @@ -import prodatakey from "../../prodatakey.app.mjs"; -import { axios } from "@pipedream/platform"; - -export default { - key: "prodatakey-create-customer", - name: "Create Customer", - description: "Creates a new customer in the ProdataKey system. [See the documentation](https://developer.pdk.io/web/2.0/rest/organizations)", - version: "0.0.{{ts}}", - type: "action", - props: { - prodatakey, - dealerId: { - propDefinition: [ - prodatakey, - "dealerId", - ], - }, - name: { - type: "string", - label: "Name", - description: "The name of the customer to be created", - }, - useBluetoothCredentials: { - propDefinition: [ - prodatakey, - "useBluetoothCredentials", - ], - optional: true, - }, - useTouchMobileApp: { - propDefinition: [ - prodatakey, - "useTouchMobileApp", - ], - optional: true, - }, - allowCredentialResets: { - propDefinition: [ - prodatakey, - "allowCredentialResets", - ], - optional: true, - }, - type: { - propDefinition: [ - prodatakey, - "type", - ], - optional: true, - }, - }, - async run({ $ }) { - const response = await this.prodatakey.createCustomer({ - dealerId: this.dealerId, - name: this.name, - useBluetoothCredentials: this.useBluetoothCredentials, - useTouchMobileApp: this.useTouchMobileApp, - allowCredentialResets: this.allowCredentialResets, - type: this.type, - }); - - $.export("$summary", `Successfully created customer with ID: ${response.id}`); - return response; - }, -}; diff --git a/components/prodatakey/actions/open-and-close-device/open-and-close-device.mjs b/components/prodatakey/actions/open-and-close-device/open-and-close-device.mjs new file mode 100644 index 0000000000000..d2532852d5187 --- /dev/null +++ b/components/prodatakey/actions/open-and-close-device/open-and-close-device.mjs @@ -0,0 +1,61 @@ +import prodatakey from "../../prodatakey.app.mjs"; + +export default { + key: "prodatakey-open-and-close-device", + name: "Open and Close Device", + description: "Opens and closes a device in the ProdataKey system. [See the documentation](https://developer.pdk.io/web/2.0/rest/devices#open-and-close-a-device)", + version: "0.0.1", + type: "action", + props: { + prodatakey, + organizationId: { + propDefinition: [ + prodatakey, + "organizationId", + ], + }, + cloudNodeId: { + propDefinition: [ + prodatakey, + "cloudNodeId", + ({ organizationId }) => ({ + organizationId, + }), + ], + }, + deviceId: { + propDefinition: [ + prodatakey, + "deviceId", + ({ + organizationId, cloudNodeId, + }) => ({ + organizationId, + cloudNodeId, + }), + ], + }, + dwell: { + type: "integer", + label: "Dwell", + description: "The amount of time (in tenths of a second) the device will remain open before closing. This value will override the dwell time configured in pdk.io.", + min: 1, + max: 5400, + optional: true, + }, + }, + async run({ $ }) { + const response = await this.prodatakey.openAndCloseDevice({ + $, + organizationId: this.organizationId, + cloudNodeId: this.cloudNodeId, + deviceId: this.deviceId, + data: { + dwell: this.dwell, + }, + }); + + $.export("$summary", `Successfully sent command to device with ID: ${this.deviceId}`); + return response; + }, +}; diff --git a/components/prodatakey/actions/retrieve-credential/retrieve-credential.mjs b/components/prodatakey/actions/retrieve-credential/retrieve-credential.mjs index 4b7fd1cd6e910..769139ff923a3 100644 --- a/components/prodatakey/actions/retrieve-credential/retrieve-credential.mjs +++ b/components/prodatakey/actions/retrieve-credential/retrieve-credential.mjs @@ -1,36 +1,45 @@ import prodatakey from "../../prodatakey.app.mjs"; -import { axios } from "@pipedream/platform"; export default { key: "prodatakey-retrieve-credential", name: "Retrieve Credential", description: "Retrieve a specific credential using the system ID, holder ID, and credential ID. [See the documentation](https://developer.pdk.io/web/2.0/rest/credentials)", - version: "0.0.{{ts}}", + version: "0.0.1", type: "action", props: { prodatakey, - systemId: { - type: "string", - label: "System ID", + organizationId: { + propDefinition: [ + prodatakey, + "organizationId", + ], }, holderId: { - type: "string", - label: "Holder ID", + propDefinition: [ + prodatakey, + "holderId", + ({ organizationId }) => ({ + organizationId, + }), + ], }, credentialId: { propDefinition: [ prodatakey, "credentialId", - (c) => ({ - systemId: c.systemId, - holderId: c.holderId, + ({ + organizationId, holderId, + }) => ({ + organizationId, + holderId, }), ], }, }, async run({ $ }) { const response = await this.prodatakey.retrieveCredential({ - systemId: this.systemId, + $, + organizationId: this.organizationId, holderId: this.holderId, credentialId: this.credentialId, }); diff --git a/components/prodatakey/actions/retrieve-organization/retrieve-organization.mjs b/components/prodatakey/actions/retrieve-organization/retrieve-organization.mjs index 812bc22ecd900..0f11aaaa2a103 100644 --- a/components/prodatakey/actions/retrieve-organization/retrieve-organization.mjs +++ b/components/prodatakey/actions/retrieve-organization/retrieve-organization.mjs @@ -1,10 +1,9 @@ import prodatakey from "../../prodatakey.app.mjs"; -import { axios } from "@pipedream/platform"; export default { key: "prodatakey-retrieve-organization", name: "Retrieve Organization", - description: "Retrieve an existing organization. [See the documentation](https://developer.pdk.io/web/2.0/rest/organizations)", + description: "Retrieve an existing organization. [See the documentation](https://developer.pdk.io/web/2.0/rest/organizations#retrieve-an-organization)", version: "0.0.1", type: "action", props: { @@ -18,6 +17,7 @@ export default { }, async run({ $ }) { const response = await this.prodatakey.retrieveOrganization({ + $, organizationId: this.organizationId, }); diff --git a/components/prodatakey/common/constants.mjs b/components/prodatakey/common/constants.mjs new file mode 100644 index 0000000000000..2b4caa24d0099 --- /dev/null +++ b/components/prodatakey/common/constants.mjs @@ -0,0 +1,127 @@ +export const EVENT_OPTIONS = +[ + { + label: "Triggers when a controller's relay circuits stabilize.", + value: "device.alarm.circuitbreaker.off", + }, + { + label: "Triggers when a controller's relay circuits become unstable (e.g. due to loose wires).", + value: "device.alarm.circuitbreaker.on", + }, + { + label: "Triggers when a device equipped with a door position sensor (DPS) is forced open.", + value: "device.alarm.forced", + }, + { + label: "Triggers when a forced alarm has cleared.", + value: "device.alarm.forced.cleared", + }, + { + label: "Triggers when prop alarms are cleared for all devices.", + value: "device.alarm.propped.alloff", + }, + { + label: "Triggers when a prop alarm has cleared for a device.", + value: "device.alarm.propped.off", + }, + { + label: "Triggers when a device equipped with a door position sensor (DPS) is propped open.", + value: "device.alarm.propped.on", + }, + { + label: "Triggers when auto open is deactivated.", + value: "device.autoopen.off", + }, + { + label: "Triggers when auto open is activated.", + value: "device.autoopen.on", + }, + { + label: "Triggers when an auto open override is deactivated.", + value: "device.autoopen.override.off", + }, + { + label: "Triggers when an auto open override is activated.", + value: "device.autoopen.override.on", + }, + { + label: "Triggers when do not disturb (DND) is deactivated.", + value: "device.forceclose.off", + }, + { + label: "Triggers when do not disturb (DND) is activated.", + value: "device.forceclose.on", + }, + { + label: "Triggers when force unlock is deactivated.", + value: "device.forceopen.off", + }, + { + label: "Triggers when force unlock is activated.", + value: "device.forceopen.on", + }, + { + label: "Triggers when a door position sensor (DPS) changes to a closed state.", + value: "device.input.dps.closed", + }, + { + label: "Triggers when a door position sensor (DPS) changes to an open state.", + value: "device.input.dps.opened", + }, + { + label: "Triggers when a device is locked.", + value: "device.input.relay.off", + }, + { + label: "Triggers when a device is unlocked.", + value: "device.input.relay.on", + }, + { + label: "Triggers when the request to exit (REX) is deactivated.", + value: "device.input.rex.off", + }, + { + label: "Triggers when the request to exit (REX) is activated.", + value: "device.input.rex.on", + }, + { + label: "Triggers when a credential scan is emulated on a device (e.g. when opening a device using a software button).", + value: "device.input.virtualread", + }, + { + label: "Triggers when a holder is recognized and granted access.", + value: "device.request.allowed", + }, + { + label: "Triggers when a holder is recognized but denied access.", + value: "device.request.denied", + }, + { + label: "Triggers when a holder enters a duress PIN.", + value: "device.request.duress", + }, + { + label: "Triggers when access is granted using an emergency card. Since emergency cards can be used while a system is offline, these events may be delayed.", + value: "device.request.ecard.allowed", + }, + { + label: "Triggers when access is denied using an emergency card. Since emergency cards can be used while a system is offline, these events may be delayed.", + value: "device.request.ecard.denied", + }, + { + label: "Triggers when a holder is recognized.", + value: "device.request.found", + }, + { + label: "Triggers when a credential is denied due to facility code filtering.", + value: "device.request.filtered", + }, + { + label: "Triggers when a holder is granted access after multiple sequential scans. This behavior may be used to configure other actions, such as activating or deactivating an alarm system.", + value: "device.request.multiallowed", + }, + { + label: "Triggers when a credential is presented but the holder is not recognized.", + value: "device.request.unknown", + }, +]; diff --git a/components/prodatakey/package.json b/components/prodatakey/package.json index abb34071ed275..8aff569355976 100644 --- a/components/prodatakey/package.json +++ b/components/prodatakey/package.json @@ -1,6 +1,6 @@ { "name": "@pipedream/prodatakey", - "version": "0.0.1", + "version": "0.1.0", "description": "Pipedream ProdataKey Components", "main": "prodatakey.app.mjs", "keywords": [ @@ -11,5 +11,8 @@ "author": "Pipedream (https://pipedream.com/)", "publishConfig": { "access": "public" + }, + "dependencies": { + "@pipedream/platform": "^3.1.0" } } diff --git a/components/prodatakey/prodatakey.app.mjs b/components/prodatakey/prodatakey.app.mjs index 28adc5800633e..bb48735339d86 100644 --- a/components/prodatakey/prodatakey.app.mjs +++ b/components/prodatakey/prodatakey.app.mjs @@ -7,209 +7,251 @@ export default { organizationId: { type: "string", label: "Organization ID", + description: "The ID of the organization.", async options() { const organizations = await this.listOrganizations(); - return organizations.map((org) => ({ - label: org.name, - value: org.id, + return organizations.map(({ + id: value, name: label, + }) => ({ + label, + value, })); }, }, - dealerId: { + holderId: { type: "string", - label: "Dealer ID", - async options() { - const dealers = await this.listDealers(); - return dealers.map((dealer) => ({ - label: dealer.name, - value: dealer.id, + label: "Holder ID", + description: "The ID of the holder.", + async options({ organizationId }) { + const holders = await this.listHolders({ + organizationId, + }); + + return holders.map(({ + id: value, firstName, lastName, email, + }) => ({ + label: `${firstName} ${lastName} (${email})`, + value, })); }, }, credentialId: { type: "string", label: "Credential ID", + description: "The ID of the credential to retrieve.", async options({ - systemId, holderId, + organizationId, holderId, }) { const credentials = await this.listCredentials({ - systemId, + organizationId, holderId, }); - return credentials.map((credential) => ({ - label: credential.id, - value: credential.id, + + return credentials.map(({ + id: value, description: label, + }) => ({ + label, + value, })); }, }, - systemId: { - type: "string", - label: "System ID", - }, - holderId: { - type: "string", - label: "Holder ID", - }, - name: { - type: "string", - label: "Name", - }, - url: { - type: "string", - label: "URL", - }, - scope: { - type: "string", - label: "Scope", - }, - authenticationType: { - type: "string", - label: "Authentication Type", - }, - events: { - type: "string[]", - label: "Events", - optional: true, - }, - authenticationUser: { - type: "string", - label: "Authentication User", - optional: true, - }, - authenticationPassword: { + cloudNodeId: { type: "string", - label: "Authentication Password", - optional: true, + label: "Cloud Node ID", + description: "The ID of the cloud node.", + async options({ organizationId }) { + const cloudNodes = await this.listCloudNodes({ + organizationId, + }); + + return cloudNodes.map(({ + id: value, name: label, + }) => ({ + label, + value, + })); + }, }, - secret: { + deviceId: { type: "string", - label: "Secret", - optional: true, - }, - useBluetoothCredentials: { - type: "boolean", - label: "Use Bluetooth Credentials", - optional: true, - }, - useTouchMobileApp: { - type: "boolean", - label: "Use Touch Mobile App", - optional: true, - }, - allowCredentialResets: { - type: "boolean", - label: "Allow Credential Resets", - optional: true, + label: "Device ID", + description: "The ID of the device to retrieve.", + async options({ + organizationId, cloudNodeId, + }) { + const devices = await this.listDevices({ + organizationId, + cloudNodeId, + }); + + return devices.map(({ + id: value, name: label, + }) => ({ + label, + value, + })); + }, }, - type: { + dealerId: { type: "string", - label: "Type", - optional: true, + label: "Dealer ID", + description: "The ID of the dealer to retrieve.", + async options() { + const dealers = await this.listDealers(); + return dealers.map((dealer) => ({ + label: dealer.name, + value: dealer.id, + })); + }, }, }, methods: { - _baseUrl() { - return "https://accounts.pdk.io/api"; + _baseUrl(systemId = null) { + return `https://${systemId + ? "systems" + : "accounts"}.pdk.io`; + }, + async _headers({ systemId = null }) { + if (systemId) { + const { token } = await this.getSystemToken({ + systemId, + }); + return { + Authorization: `Bearer ${token}`, + }; + } + + return { + Authorization: `Bearer ${this.$auth.id_token}`, + }; }, - async _makeRequest(opts = {}) { - const { - $ = this, method = "GET", path, headers, ...otherOpts - } = opts; + async _makeRequest({ + $ = this, path, systemId, ...opts + }) { return axios($, { - ...otherOpts, - method, - url: this._baseUrl() + path, - headers: { - ...headers, - Authorization: `Bearer ${this.$auth.oauth_access_token}`, - }, + url: this._baseUrl(systemId) + path, + headers: await this._headers({ + systemId, + }), + ...opts, }); }, - async listOrganizations(opts = {}) { + getSystemToken({ + systemId, ...opts + }) { return this._makeRequest({ - path: "/organizations", + method: "POST", + path: `/api/systems/${systemId}/token`, ...opts, }); }, - async listDealers(opts = {}) { + listOrganizations(opts = {}) { return this._makeRequest({ - path: "/dealers", + path: "/api/organizations/mine", ...opts, }); }, - async listCredentials(opts = {}) { - const { - systemId, holderId, - } = opts; + retrieveOrganization({ + organizationId, ...opts + }) { return this._makeRequest({ - path: `/systems/${systemId}/holders/${holderId}/credentials`, + path: `/api/organizations/${organizationId}`, + ...opts, }); }, - async createCustomer({ - dealerId, name, useBluetoothCredentials, useTouchMobileApp, allowCredentialResets, type, + async listHolders({ + organizationId, ...opts }) { + const { systemId } = await this.retrieveOrganization({ + organizationId, + }); return this._makeRequest({ - method: "POST", - path: `/organizations/${dealerId}/children`, - data: { - name, - useBluetoothCredentials, - useTouchMobileApp, - allowCredentialResets, - type, - }, + path: `/${systemId}/holders`, + ...opts, }); }, - async retrieveOrganization({ organizationId }) { + async listCredentials({ + organizationId, holderId, ...opts + }) { + const { systemId } = await this.retrieveOrganization({ + organizationId, + }); return this._makeRequest({ - path: `/organizations/${organizationId}`, + path: `/${systemId}/holders/${holderId}/credentials`, + systemId, + ...opts, }); }, async retrieveCredential({ - systemId, holderId, credentialId, + organizationId, holderId, credentialId, + }) { + const { systemId } = await this.retrieveOrganization({ + organizationId, + }); + return this._makeRequest({ + path: `/${systemId}/holders/${holderId}/credentials/${credentialId}`, + systemId, + }); + }, + listDealers(opts = {}) { + return this._makeRequest({ + path: "/api/dealers", + ...opts, + }); + }, + async listCloudNodes({ + organizationId, ...opts + }) { + const { systemId } = await this.retrieveOrganization({ + organizationId, + }); + return this._makeRequest({ + path: `/${systemId}/cloud-nodes`, + systemId, + ...opts, + }); + }, + async listDevices({ + organizationId, cloudNodeId, ...opts }) { + const { systemId } = await this.retrieveOrganization({ + organizationId, + }); return this._makeRequest({ - path: `/systems/${systemId}/holders/${holderId}/credentials/${credentialId}`, + path: `/${systemId}/cloud-nodes/${cloudNodeId}/devices`, + systemId, + ...opts, }); }, - async emitEventCloudNodeConnected({ - organizationId, name, url, scope, authenticationType, authenticationUser, authenticationPassword, secret, + async openAndCloseDevice({ + organizationId, cloudNodeId, deviceId, data, }) { + const { systemId } = await this.retrieveOrganization({ + organizationId, + }); return this._makeRequest({ method: "POST", - path: "/events", - data: { - event_type: "cloudnode.connected", - organization_id: organizationId, - name, - url, - scope, - authentication_type: authenticationType, - authentication_user: authenticationUser, - authentication_password: authenticationPassword, - secret, - }, - }); - }, - async emitEvent({ - organizationId, name, url, scope, authenticationType, events, authenticationUser, authenticationPassword, secret, + path: `/${systemId}/cloud-nodes/${cloudNodeId}/devices/${deviceId}/try-open`, + systemId, + data, + }); + }, + createHook({ + organizationId, ...opts }) { - const eventPromises = events.map((event) => this._makeRequest({ + return this._makeRequest({ method: "POST", - path: "/events", - data: { - event_type: event, - organization_id: organizationId, - name, - url, - scope, - authentication_type: authenticationType, - authentication_user: authenticationUser, - authentication_password: authenticationPassword, - secret, - }, - })); - return Promise.all(eventPromises); + path: `/api/organizations/${organizationId}/subscriptions`, + ...opts, + }); + }, + deleteHook({ + organizationId, webhookId, + }) { + return this._makeRequest({ + method: "DELETE", + path: `/api/organizations/${organizationId}/subscriptions/${webhookId}`, + }); }, }, }; diff --git a/components/prodatakey/sources/cloud-node-connected-instant/cloud-node-connected-instant.mjs b/components/prodatakey/sources/cloud-node-connected-instant/cloud-node-connected-instant.mjs deleted file mode 100644 index 63c410dc74d46..0000000000000 --- a/components/prodatakey/sources/cloud-node-connected-instant/cloud-node-connected-instant.mjs +++ /dev/null @@ -1,101 +0,0 @@ -import prodatakey from "../../prodatakey.app.mjs"; -import { axios } from "@pipedream/platform"; - -export default { - key: "prodatakey-cloud-node-connected-instant", - name: "Cloud Node Connected", - description: "Emit a new event when a cloud node is connected to the ProdataKey system. [See the documentation](https://developer.pdk.io/web/2.0/rest/webhooks)", - version: "0.0.{{ts}}", - type: "source", - dedupe: "unique", - props: { - prodatakey: { - type: "app", - app: "prodatakey", - }, - http: { - type: "$.interface.http", - customResponse: false, - }, - db: "$.service.db", - organizationId: { - propDefinition: [ - prodatakey, - "organizationId", - ], - }, - name: { - propDefinition: [ - prodatakey, - "name", - ], - }, - url: { - propDefinition: [ - prodatakey, - "url", - ], - }, - scope: { - propDefinition: [ - prodatakey, - "scope", - ], - }, - authenticationType: { - propDefinition: [ - prodatakey, - "authenticationType", - ], - }, - authenticationUser: { - propDefinition: [ - prodatakey, - "authenticationUser", - ], - optional: true, - }, - authenticationPassword: { - propDefinition: [ - prodatakey, - "authenticationPassword", - ], - optional: true, - }, - secret: { - propDefinition: [ - prodatakey, - "secret", - ], - optional: true, - }, - }, - hooks: { - async deploy() { - // Fetch historical data for deployment-related setup - // For this specific event type, historical fetching is not applicable - }, - async activate() { - // Mechanism to create webhook if needed at app or integration level - // ProdataKey handles subscriptions for real-time event streaming - }, - async deactivate() { - // Mechanism to clear webhook if needed at app or integration level - // ProdataKey handles cleanup on webhooks - }, - }, - async run(event) { - if (event.body && event.body.topic === "cloudnode.connected") { - const { - id, name, metadata, - } = event.body; - const summary = `Cloud node ${name} connected`; - const ts = metadata.occurred || Date.now(); - this.$emit(event.body, { - id, - summary, - ts, - }); - } - }, -}; diff --git a/components/prodatakey/sources/common/base.mjs b/components/prodatakey/sources/common/base.mjs new file mode 100644 index 0000000000000..8f35de58bf649 --- /dev/null +++ b/components/prodatakey/sources/common/base.mjs @@ -0,0 +1,47 @@ +import prodatakey from "../../prodatakey.app.mjs"; + +export default { + props: { + prodatakey, + db: "$.service.db", + http: "$.interface.http", + organizationId: { + propDefinition: [ + prodatakey, + "organizationId", + ], + }, + name: { + type: "string", + label: "Webhook Name", + description: "The name of the webhook", + }, + }, + hooks: { + async activate() { + const response = await this.prodatakey.createHook({ + organizationId: this.organizationId, + data: { + name: this.name, + url: this.http.endpoint, + scope: "this", + authentication: { + type: "None", + }, + events: this.getEvent(), + }, + }); + this.db.set("webhookId", response.id); + }, + async deactivate() { + const webhookId = this.db.get("webhookId"); + await this.prodatakey.deleteHook({ + organizationId: this.organizationId, + webhookId, + }); + }, + }, + async run({ body }) { + this.$emit(body, this.generateMeta(body)); + }, +}; diff --git a/components/prodatakey/sources/new-event-instant/new-event-instant.mjs b/components/prodatakey/sources/new-event-instant/new-event-instant.mjs index f95cd23e70e2a..2446122c786f8 100644 --- a/components/prodatakey/sources/new-event-instant/new-event-instant.mjs +++ b/components/prodatakey/sources/new-event-instant/new-event-instant.mjs @@ -1,157 +1,37 @@ -import { axios } from "@pipedream/platform"; -import prodatakey from "../../prodatakey.app.mjs"; +import { EVENT_OPTIONS } from "../../common/constants.mjs"; +import common from "../common/base.mjs"; +import sampleEmit from "./test-event.mjs"; export default { + ...common, key: "prodatakey-new-event-instant", - name: "New Event Instant", - description: "Emit new event when an event is triggered. [See the documentation](https://developer.pdk.io/web/2.0/introduction)", - version: "0.0.{{ts}}", + name: "New Event (Instant)", + description: "Emit new event when an event is triggered. [See the documentation](https://developer.pdk.io/web/2.0/rest/webhooks/#introduction)", + version: "0.0.1", type: "source", dedupe: "unique", props: { - prodatakey, - http: { - type: "$.interface.http", - customResponse: true, - }, - db: "$.service.db", - organizationId: { - propDefinition: [ - prodatakey, - "organizationId", - ], - }, - name: { - propDefinition: [ - prodatakey, - "name", - ], - }, - url: { - propDefinition: [ - prodatakey, - "url", - ], - }, - scope: { - propDefinition: [ - prodatakey, - "scope", - ], - }, - authenticationType: { - propDefinition: [ - prodatakey, - "authenticationType", - ], - }, - events: { - propDefinition: [ - prodatakey, - "events", - ], - }, - authenticationUser: { - propDefinition: [ - prodatakey, - "authenticationUser", - ], - optional: true, - }, - authenticationPassword: { - propDefinition: [ - prodatakey, - "authenticationPassword", - ], - optional: true, - }, - secret: { - propDefinition: [ - prodatakey, - "secret", - ], - optional: true, + ...common.props, + event: { + type: "string", + label: "Event", + description: "The event to listen for", + options: EVENT_OPTIONS, }, }, - hooks: { - async deploy() { - const events = await this.prodatakey.emitEvent({ - organizationId: this.organizationId, - name: this.name, - url: this.url, - scope: this.scope, - authenticationType: this.authenticationType, - events: this.events, - authenticationUser: this.authenticationUser, - authenticationPassword: this.authenticationPassword, - secret: this.secret, - }); - - for (const event of events.slice(0, 50)) { - this.$emit(event, { - id: event.id, - summary: `Event triggered for ${event.name}`, - ts: Date.now(), - }); - } + methods: { + getEvent() { + return [ + this.event, + ]; + }, + generateMeta(event) { + return { + id: event.id, + summary: `Event triggered for ${event.cloudNodeSN}`, + ts: event.occurred.occurred || Date.now(), + }; }, - async activate() { - const webhookResponse = await axios(this, { - method: "POST", - url: "https://accounts.pdk.io/api/webhooks", - headers: { - Authorization: `Bearer ${this.prodatakey.$auth.oauth_access_token}`, - }, - data: { - organization_id: this.organizationId, - name: this.name, - url: this.url, - scope: this.scope, - authentication_type: this.authenticationType, - events: this.events, - authentication_user: this.authenticationUser, - authentication_password: this.authenticationPassword, - secret: this.secret, - }, - }); - this.db.set("webhookId", webhookResponse.id); - }, - async deactivate() { - const webhookId = this.db.get("webhookId"); - if (webhookId) { - await axios(this, { - method: "DELETE", - url: `https://accounts.pdk.io/api/webhooks/${webhookId}`, - headers: { - Authorization: `Bearer ${this.prodatakey.$auth.oauth_access_token}`, - }, - }); - this.db.set("webhookId", null); - } - }, - }, - async run(event) { - const { - headers, body, - } = event; - const computedSignature = require("crypto").createHmac("sha256", this.secret || "") - .update(JSON.stringify(body)) - .digest("base64"); - if (headers["x-pdk-signature"] !== computedSignature) { - this.http.respond({ - status: 401, - body: "Unauthorized", - }); - return; - } - this.http.respond({ - status: 200, - body: "OK", - }); - this.$emit(body, { - id: body.id || Date.now(), - summary: `New event: ${body.topic}`, - ts: Date.now(), - }); }, + sampleEmit, }; diff --git a/components/prodatakey/sources/new-event-instant/test-event.mjs b/components/prodatakey/sources/new-event-instant/test-event.mjs new file mode 100644 index 0000000000000..0ae7b626f7600 --- /dev/null +++ b/components/prodatakey/sources/new-event-instant/test-event.mjs @@ -0,0 +1,21 @@ +export default { + "body": { + "deviceId": "1bf7a641-4b1b-4d6c-9c57-818e7515575d", + "facilityCode": "123", + "holderId": "8129a254-dc3d-419f-b7e9-4ad772afd23f", + "requestFactor": "CARD_ONLY" + }, + "cloudNodeId": "91c2a7b7-28e9-4dec-e1f7-9966b1d3ca55", + "cloudNodeSN": "1234ABC", + "id": "90163e3c-a31f-4bc5-9f6f-88eea9fc5b6c", + "metadata": { + "deviceName": "Test Device", + "holderName": "John Wiegand", + "occurred": 1710201553630, + "offset": -14400000, + "rules": [], + "source": "board", + "systemId": "2b6c14b2-ce6d-4109-ab4e-b95a65b048d3" + }, + "topic": "device.request.allowed" +} \ No newline at end of file From 7cb69049dddb747a2bf93c7e5cd9e8f40c8d1843 Mon Sep 17 00:00:00 2001 From: Luan Cazarine Date: Thu, 12 Jun 2025 12:41:11 -0300 Subject: [PATCH 3/3] pnpm update --- pnpm-lock.yaml | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index cae0d57aeca40..1be4ed2cc7057 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -751,8 +751,7 @@ importers: components/amazon_polly: {} - components/amazon_redshift: - specifiers: {} + components/amazon_redshift: {} components/amazon_selling_partner: {} @@ -10448,7 +10447,11 @@ importers: components/procore_sandbox: {} - components/prodatakey: {} + components/prodatakey: + dependencies: + '@pipedream/platform': + specifier: ^3.1.0 + version: 3.1.0 components/prodpad: dependencies: @@ -44039,7 +44042,7 @@ snapshots: jest-util: 29.7.0 natural-compare: 1.4.0 pretty-format: 29.7.0 - semver: 7.7.1 + semver: 7.7.2 transitivePeerDependencies: - supports-color