Skip to content

Commit

Permalink
feat(webhook): Add allowed methods to webhooks
Browse files Browse the repository at this point in the history
  • Loading branch information
zachowj committed Jul 12, 2023
1 parent 9f2e6db commit 48ebdd8
Show file tree
Hide file tree
Showing 12 changed files with 256 additions and 127 deletions.
14 changes: 4 additions & 10 deletions docs/node/webhook.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,23 +9,17 @@ in Home Assistant for this node to function_

## Configuration

### ID
### ID <Badge text="required"/>

- Type: `string`

A string to be used for the webhook URL in Home Assistant.

### Payload
### Allowed Methods <Badge text="required"/>

- Type: `string`

Customizable location for the webhook payload. Defaults to msg.payload

### Headers

- Type: `number`
- Type: `list`

Customizable location for the webhook request headers.
A list of allowed methods that Home Assistant will accept for the webhook. At least one method must be selected.

## Outputs

Expand Down
1 change: 1 addition & 0 deletions locales/en-US/common.json
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@
"off": "off",
"on": "on",
"pressed": "pressed",
"received": "received",
"registered": "registered",
"running": "running",
"sending": "sending",
Expand Down
11 changes: 11 additions & 0 deletions locales/en-US/webhook.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"ha-webhook": {
"label": {
"allowed_methods": "Allowed Methods",
"put": "PUT",
"post": "POST",
"get": "GET",
"head": "HEAD"
}
}
}
3 changes: 2 additions & 1 deletion src/common/integration/Integration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,9 @@ export enum MessageType {
Discovery = 'nodered/discovery',
Entity = 'nodered/entity',
RemoveDevice = 'nodered/device/remove',
UpdateConfig = 'nodered/entity/update_config',
SentenseTrigger = 'nodered/sentence',
UpdateConfig = 'nodered/entity/update_config',
Webhook = 'nodered/webhook',
}

export interface MessageBase {
Expand Down
44 changes: 44 additions & 0 deletions src/nodes/webhook/WebhookController.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import OutputController, {
OutputControllerOptions,
} from '../../common/controllers/OutputController';
import { IntegrationEvent } from '../../common/integration/Integration';
import { NodeMessage } from '../../types/nodes';
import { WebhookNode } from '.';

interface WebhookResponse {
payload: any;
headers: Record<string, any>;
params: Record<string, any>;
}

type WebhookNodeOptions = OutputControllerOptions<WebhookNode>;

export default class WebhookController extends OutputController<WebhookNode> {
constructor(props: WebhookNodeOptions) {
super(props);

this.node.addListener(
IntegrationEvent.Trigger,
this.#onReceivedMessage.bind(this)
);
}

#onReceivedMessage(data: WebhookResponse) {
const message: NodeMessage = {};
try {
this.setCustomOutputs(this.node.config.outputProperties, message, {
config: this.node.config,
data: data.payload,
headers: data.headers,
params: data.params,
});
} catch (e) {
this.node.error(e);
this.status.setFailed('error');
return;
}

this.status.setSuccess('home-assistant.status.received');
this.node.send(message);
}
}
42 changes: 42 additions & 0 deletions src/nodes/webhook/WebhookIntegration.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import BidirectionalIntegration, {
DiscoveryBaseMessage,
} from '../../common/integration/BidrectionalIntegration';
import { MessageType } from '../../common/integration/Integration';
import { WebhookNodeProperties } from '.';

export interface WebhookDiscoveryPayload extends DiscoveryBaseMessage {
type: MessageType.Webhook;
server_id: string;
webhook_id: string;
name: string;
allowed_methods: string[];
}

export default class WebhookIntegration extends BidirectionalIntegration {
protected getDiscoveryPayload(
config: WebhookNodeProperties
): WebhookDiscoveryPayload {
const methods = [
'method_post',
'method_get',
'method_put',
'method_head',
] as const;

const allowedMethods = methods.reduce((acc, method) => {
if (config[method]) {
acc.push(method.replace('method_', '').toUpperCase());
}
return acc;
}, [] as string[]);

return {
type: MessageType.Webhook,
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
server_id: config.server!,
webhook_id: config.webhookId,
name: config.id,
allowed_methods: allowedMethods,
};
}
}
102 changes: 0 additions & 102 deletions src/nodes/webhook/controller.js

This file was deleted.

32 changes: 32 additions & 0 deletions src/nodes/webhook/editor.html
Original file line number Diff line number Diff line change
Expand Up @@ -24,4 +24,36 @@
</div>
</div>

<div class="form-row">
<p data-i18n="ha-webhook.label.allowed_methods"></p>
<ul class="ha-ignore-state">
<li>
<input type="checkbox" id="node-input-method_post" />
<label
for="node-input-method_post"
data-i18n="ha-webhook.label.post"
>
</label>
</li>
<li>
<input type="checkbox" id="node-input-method_put" />
<label for="node-input-method_put" data-i18n="ha-webhook.label.put">
</label>
</li>
<li>
<input type="checkbox" id="node-input-method_get" />
<label for="node-input-method_get" data-i18n="ha-webhook.label.get">
</label>
</li>
<li>
<input type="checkbox" id="node-input-method_head" />
<label
for="node-input-method_head"
data-i18n="ha-webhook.label.head"
>
</label>
</li>
</ul>
</div>

<div class="form-row"><ol id="custom-outputs"></ol></div>
27 changes: 27 additions & 0 deletions src/nodes/webhook/editor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,10 @@ interface WebhookEditorNodeProperties extends EditorNodeProperties {
server: string;
version: number;
webhookId: string;
method_get: boolean;
method_head: boolean;
method_post: boolean;
method_put: boolean;
outputProperties: OutputProperty[];

// deprecated but needed for imports
Expand All @@ -32,6 +36,12 @@ function generateId(length: number) {
).join('');
}

// Check that at least one method is enabled
function validateMethods(): boolean {
const methods = ['method_post', 'method_get', 'method_put', 'method_head'];
return methods.some((method) => this[method]);
}

const WebhookEditor: EditorNodeDef<WebhookEditorNodeProperties> = {
category: NodeCategory.HomeAssistant,
color: NodeColor.HaBlue,
Expand All @@ -49,6 +59,10 @@ const WebhookEditor: EditorNodeDef<WebhookEditorNodeProperties> = {
version: { value: RED.settings.get('haWebhookVersion', 0) },
outputs: { value: 1 },
webhookId: { value: generateId(32), required: true },
method_get: { value: false, validate: validateMethods },
method_head: { value: false, validate: validateMethods },
method_post: { value: true, validate: validateMethods },
method_put: { value: true, validate: validateMethods },
outputProperties: {
value: [
{
Expand Down Expand Up @@ -95,6 +109,19 @@ const WebhookEditor: EditorNodeDef<WebhookEditorNodeProperties> = {
$webhookId.val(generateId(32));
});

$('[id^=node-input-method_]')
.on('change', function () {
const isMethodSelected = $('[id^=node-input-method_]').is(
':checked'
);
if (!isMethodSelected) {
$(this).closest('div').addClass('input-error');
} else {
$(this).closest('div').removeClass('input-error');
}
})
.trigger('change');

haOutputs.createOutputs(this.outputProperties, {
extraTypes: ['receivedData', 'headers', 'params', 'triggerId'],
});
Expand Down
Loading

0 comments on commit 48ebdd8

Please sign in to comment.