diff --git a/packages/nodes-base/credentials/BlueIrisApi.credentials.ts b/packages/nodes-base/credentials/BlueIrisApi.credentials.ts new file mode 100644 index 0000000000000..276d7a99205a6 --- /dev/null +++ b/packages/nodes-base/credentials/BlueIrisApi.credentials.ts @@ -0,0 +1,27 @@ +import { + ICredentialType, + INodeProperties, +} from 'n8n-workflow'; + +export class BlueIrisApi implements ICredentialType { + name = 'blueIrisApi'; + displayName = 'BlueIris Api'; + documentationUrl = 'BlueIris'; + properties: INodeProperties[] = [ + { + displayName: 'Username', + name: 'username', + type: 'string', + default: '', + }, + { + displayName: 'Password', + name: 'password', + type: 'string', + typeOptions: { + password: true, + }, + default: '', + }, + ]; +} diff --git a/packages/nodes-base/nodes/BlueIris/BlueIris.node.ts b/packages/nodes-base/nodes/BlueIris/BlueIris.node.ts new file mode 100644 index 0000000000000..096f7e4038047 --- /dev/null +++ b/packages/nodes-base/nodes/BlueIris/BlueIris.node.ts @@ -0,0 +1,90 @@ +import { + IExecuteFunctions, +} from 'n8n-core'; + +import { + IDataObject, + INodeExecutionData, + INodeType, + INodeTypeDescription, +} from 'n8n-workflow'; + +import { performOperation } from './GenericFunctions'; + +export class BlueIris implements INodeType { + description: INodeTypeDescription = { + displayName: 'BlueIris', + name: 'BlueIris', + icon: 'file:BlueIris.png', + group: ['input'], + version: 1, + description: 'Node to consume BlueIris API', + defaults: { + name: 'BlueIris', + color: '#517db7', + }, + inputs: ['main'], + outputs: ['main'], + credentials: [ + { + name: 'blueIrisApi', + required: true, + }, + ], + properties: [ + { + displayName: 'API URL', + name: 'apiUrl', + type: 'string', + required: true, + description: 'The URL of the API. For example https://yourdomain:81/json', + default: '', + }, + { + displayName: 'Operation', + name: 'operation', + type: 'options', + default: 'status', + required: true, + description: 'Operation to perform. Currently only some read operation are supported', + options: [ + { + name: 'Status', + value: 'status', + description: 'Get (and optionally set) the state of the shield, active global profile as well as the schedule\'shold/run state and other system vitals', + }, + { + name: 'Cameras List', + value: 'camlist', + description: 'Returns a list of cameras on the system ordered by group. Cameras not belonging to any' + + 'group are shown beneath the \"all cameras\" group. Disabled cameras are placed at the end of' + + 'the list.', + }, + ], + }, + ], + }; + + async execute(this: IExecuteFunctions): Promise { + const returnData: IDataObject[] = []; + const items = this.getInputData(); + const length = items.length as unknown as number; + + for (let i = 0; i < length; i++) { + try { + const apiUrl = this.getNodeParameter('apiUrl', i) as string; + const responseData = await performOperation.call(this, apiUrl); + returnData.push(responseData); + } catch (error) { + if (this.continueOnFail()) { + returnData.push({ error: error.description }); + } else { + throw error; + } + } + } + + return [this.helpers.returnJsonArray(returnData)]; + } + +} diff --git a/packages/nodes-base/nodes/BlueIris/BlueIris.png b/packages/nodes-base/nodes/BlueIris/BlueIris.png new file mode 100644 index 0000000000000..ed9e5220b9292 Binary files /dev/null and b/packages/nodes-base/nodes/BlueIris/BlueIris.png differ diff --git a/packages/nodes-base/nodes/BlueIris/GenericFunctions.ts b/packages/nodes-base/nodes/BlueIris/GenericFunctions.ts new file mode 100644 index 0000000000000..c748419a22f5a --- /dev/null +++ b/packages/nodes-base/nodes/BlueIris/GenericFunctions.ts @@ -0,0 +1,97 @@ +import { + OptionsWithUri, +} from 'request'; + +import { + IExecuteFunctions, + IExecuteSingleFunctions, + IHookFunctions, + ILoadOptionsFunctions, +} from 'n8n-core'; + +import { INode, NodeOperationError } from 'n8n-workflow'; +import { createHash } from 'crypto'; + +export async function performOperation(this: IHookFunctions | IExecuteFunctions | IExecuteSingleFunctions | ILoadOptionsFunctions, apiUrl: string): Promise { // tslint:disable-line:no-any + const credentials = await this.getCredentials('blueIrisApi'); + if (!credentials) { + throw new NodeOperationError(this.getNode(), 'Credentials are mandatory!'); + } + const operation = this.getNodeParameter('operation', 0) as string; + + const initSessionRequest = getInitializeSessionRequest(apiUrl); + let responseData = await this.helpers.request!(initSessionRequest); + + const session = responseData.session; + const authenticatioRequest = `${credentials.username}:${session}:${credentials.password}`; + const hashedAuthentication = createHash('md5').update(authenticatioRequest).digest('hex'); + + const loginRequest = getLoginRequest(apiUrl, session, hashedAuthentication); + responseData = await this.helpers.request!(loginRequest); + checkResponseErrors(this.getNode(), responseData); + + const operationRequest = getOperationRequest(apiUrl, session, operation); + responseData = await this.helpers.request!(operationRequest); + checkResponseErrors(this.getNode(), responseData); + + const logoutRequest = getCloseSessionRequest(apiUrl, session); + await this.helpers.request!(logoutRequest); + + return responseData; +} + +function checkResponseErrors(node: INode, responseData: any) { // tslint:disable-line:no-any + if (responseData.result !== 'success') { + throw new NodeOperationError(node, `Received the following error from BlueIris API: ${responseData.data.reason}`); + } +} + +function getInitializeSessionRequest(apiUrl: string) { + const body = { cmd: 'login' }; + return { + body, + method: 'POST', + uri: apiUrl, + json: true, + } as OptionsWithUri; +} + +function getLoginRequest(apiUrl: string, session: string, hashedAuthentication: string) { + const body = { + cmd: 'login', + session, + response: hashedAuthentication, + }; + return { + body, + method: 'POST', + uri: apiUrl, + json: true, + } as OptionsWithUri; +} + +function getOperationRequest(apiUrl: string, session: string, operation: string) { + const body = { + cmd: operation, + session, + }; + return { + body, + method: 'POST', + uri: apiUrl, + json: true, + } as OptionsWithUri; +} + +function getCloseSessionRequest(apiUrl: string, session: string) { + const body = { + cmd: 'logout', + session, + }; + return { + body, + method: 'POST', + uri: apiUrl, + json: true, + } as OptionsWithUri; +} diff --git a/packages/nodes-base/package.json b/packages/nodes-base/package.json index 27092cca26f6f..bbd1a1ff87d51 100644 --- a/packages/nodes-base/package.json +++ b/packages/nodes-base/package.json @@ -309,7 +309,8 @@ "dist/credentials/ZohoOAuth2Api.credentials.js", "dist/credentials/ZoomApi.credentials.js", "dist/credentials/ZoomOAuth2Api.credentials.js", - "dist/credentials/ZulipApi.credentials.js" + "dist/credentials/ZulipApi.credentials.js", + "dist/credentials/BlueIrisApi.credentials.js" ], "nodes": [ "dist/nodes/ActionNetwork/ActionNetwork.node.js", @@ -648,7 +649,8 @@ "dist/nodes/Zendesk/ZendeskTrigger.node.js", "dist/nodes/Zoho/ZohoCrm.node.js", "dist/nodes/Zoom/Zoom.node.js", - "dist/nodes/Zulip/Zulip.node.js" + "dist/nodes/Zulip/Zulip.node.js", + "dist/nodes/BlueIris/BlueIris.node.js" ] }, "devDependencies": {