Skip to content

Commit

Permalink
Add headers to webhook requests
Browse files Browse the repository at this point in the history
  • Loading branch information
JensForstmann committed Sep 1, 2024
1 parent e8a2b30 commit cf7e3bf
Show file tree
Hide file tree
Showing 8 changed files with 181 additions and 9 deletions.
1 change: 1 addition & 0 deletions backend/src/events.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ const send = (match: Match.Match, data: Event, isSystemEvent?: boolean) => {
fetch(url, {
method: 'POST',
headers: {
...match.data.webhookHeaders,
'Content-Type': 'application/json',
},
body: JSON.stringify(data),
Expand Down
5 changes: 5 additions & 0 deletions backend/src/match.ts
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,7 @@ export const createFromCreateDto = async (dto: IMatchCreateDto, id: string, logS
createdAt: Date.now(),
lastSavedAt: 0,
webhookUrl: dto.webhookUrl ?? null,
webhookHeaders: dto.webhookHeaders ?? null,
mode: dto.mode ?? 'SINGLE',
};
try {
Expand Down Expand Up @@ -1036,6 +1037,10 @@ export const update = async (match: Match, dto: IMatchUpdateDto) => {
match.data.webhookUrl = dto.webhookUrl;
}

if (dto.webhookHeaders !== undefined) {
match.data.webhookHeaders = dto.webhookHeaders;
}

if (dto.logSecret) {
const previous = match.data.logSecret;
match.data.logSecret = dto.logSecret;
Expand Down
2 changes: 1 addition & 1 deletion backend/src/matchesController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -204,7 +204,7 @@ export class MatchesController extends Controller {
}

/**
* Stop supervising a specific match. TMT will no longer listen to the game server and will not execute any rcon commands.
* Stop supervising a specific match. TMT will execute the "end rcon commands" and afterwards no longer listen to the game server.
*/
@Delete('{id}')
async deleteMatch(id: string, @Request() req: ExpressRequest<IAuthResponse>): Promise<void> {
Expand Down
40 changes: 40 additions & 0 deletions backend/src/routes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -494,6 +494,18 @@ const models: TsoaRoute.Models = {
subSchemas: [{ dataType: 'string' }, { dataType: 'enum', enums: [null] }],
required: true,
},
webhookHeaders: {
dataType: 'union',
subSchemas: [
{
dataType: 'nestedObjectLiteral',
nestedProperties: {},
additionalProperties: { dataType: 'string' },
},
{ dataType: 'enum', enums: [null] },
],
required: true,
},
rconCommands: {
dataType: 'nestedObjectLiteral',
nestedProperties: {
Expand Down Expand Up @@ -561,6 +573,17 @@ const models: TsoaRoute.Models = {
dataType: 'union',
subSchemas: [{ dataType: 'string' }, { dataType: 'enum', enums: [null] }],
},
webhookHeaders: {
dataType: 'union',
subSchemas: [
{
dataType: 'nestedObjectLiteral',
nestedProperties: {},
additionalProperties: { dataType: 'string' },
},
{ dataType: 'enum', enums: [null] },
],
},
rconCommands: {
dataType: 'nestedObjectLiteral',
nestedProperties: {
Expand Down Expand Up @@ -607,6 +630,18 @@ const models: TsoaRoute.Models = {
subSchemas: [{ dataType: 'string' }, { dataType: 'enum', enums: [null] }],
required: true,
},
webhookHeaders: {
dataType: 'union',
subSchemas: [
{
dataType: 'nestedObjectLiteral',
nestedProperties: {},
additionalProperties: { dataType: 'string' },
},
{ dataType: 'enum', enums: [null] },
],
required: true,
},
rconCommands: {
dataType: 'nestedObjectLiteral',
nestedProperties: {
Expand Down Expand Up @@ -1007,6 +1042,11 @@ const models: TsoaRoute.Models = {
},
gameServer: { ref: 'IGameServer' },
webhookUrl: { dataType: 'string' },
webhookHeaders: {
dataType: 'nestedObjectLiteral',
nestedProperties: {},
additionalProperties: { dataType: 'string' },
},
rconCommands: {
dataType: 'nestedObjectLiteral',
nestedProperties: {
Expand Down
47 changes: 42 additions & 5 deletions backend/swagger.json
Original file line number Diff line number Diff line change
Expand Up @@ -735,7 +735,16 @@
"webhookUrl": {
"type": "string",
"nullable": true,
"description": "if set various events will be posted to this url"
"description": "Send various events to this url (HTTP POST)"
},
"webhookHeaders": {
"properties": {},
"additionalProperties": {
"type": "string"
},
"type": "object",
"nullable": true,
"description": "Additional headers that will be added to each webhook request"
},
"rconCommands": {
"properties": {
Expand Down Expand Up @@ -841,6 +850,7 @@
"matchMaps",
"currentMap",
"webhookUrl",
"webhookHeaders",
"rconCommands",
"canClinch",
"matchEndAction",
Expand Down Expand Up @@ -920,7 +930,16 @@
"webhookUrl": {
"type": "string",
"nullable": true,
"description": "if set various events will be posted to this url"
"description": "Send various events to this url (HTTP POST)"
},
"webhookHeaders": {
"properties": {},
"additionalProperties": {
"type": "string"
},
"type": "object",
"nullable": true,
"description": "Additional headers that will be added to each webhook request"
},
"rconCommands": {
"properties": {
Expand Down Expand Up @@ -1047,7 +1066,16 @@
"webhookUrl": {
"type": "string",
"nullable": true,
"description": "if set various events will be posted to this url"
"description": "Send various events to this url (HTTP POST)"
},
"webhookHeaders": {
"properties": {},
"additionalProperties": {
"type": "string"
},
"type": "object",
"nullable": true,
"description": "Additional headers that will be added to each webhook request"
},
"rconCommands": {
"properties": {
Expand Down Expand Up @@ -1156,6 +1184,7 @@
"matchMaps",
"currentMap",
"webhookUrl",
"webhookHeaders",
"rconCommands",
"canClinch",
"matchEndAction",
Expand Down Expand Up @@ -1910,7 +1939,15 @@
},
"webhookUrl": {
"type": "string",
"description": "if set various events will be posted to this url"
"description": "Send various events to this url (HTTP POST)"
},
"webhookHeaders": {
"properties": {},
"additionalProperties": {
"type": "string"
},
"type": "object",
"description": "Additional headers that will be added to each webhook request"
},
"rconCommands": {
"properties": {
Expand Down Expand Up @@ -2499,7 +2536,7 @@
"description": "No content"
}
},
"description": "Stop supervising a specific match. TMT will no longer listen to the game server and will not execute any rcon commands.",
"description": "Stop supervising a specific match. TMT will execute the \"end rcon commands\" and afterwards no longer listen to the game server.",
"security": [
{
"bearer_token": []
Expand Down
8 changes: 6 additions & 2 deletions common/types/match.ts
Original file line number Diff line number Diff line change
Expand Up @@ -78,8 +78,10 @@ export interface IMatch {
matchMaps: IMatchMap[];
/** Index of the matchMaps array indicating the current map. */
currentMap: number;
/** if set various events will be posted to this url */
/** Send various events to this url (HTTP POST) */
webhookUrl: string | null;
/** Additional headers that will be added to each webhook request */
webhookHeaders: { [key: string]: string } | null;
rconCommands: {
/** executed exactly once on match init */
init: string[];
Expand Down Expand Up @@ -145,8 +147,10 @@ export interface IMatchCreateDto {
teamB: ITeamCreateDto;
electionSteps: Array<IElectionStepAdd | IElectionStepSkip>;
gameServer: IGameServer | null;
/** if set various events will be posted to this url */
/** Send various events to this url (HTTP POST) */
webhookUrl?: string | null;
/** Additional headers that will be added to each webhook request */
webhookHeaders?: { [key: string]: string } | null;
rconCommands?: {
/** executed exactly once on match init */
init?: string[];
Expand Down
86 changes: 85 additions & 1 deletion frontend/src/components/CreateUpdateMatch.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,14 @@
import autoAnimate from '@formkit/auto-animate';
import { Component, For, Match, Show, Switch, createEffect, createSignal, onMount } from 'solid-js';
import {
Component,
For,
Match,
Show,
Switch,
createEffect,
createSignal,
onMount
} from 'solid-js';
import { createStore } from 'solid-js/store';
import {
IConfig,
Expand Down Expand Up @@ -240,6 +249,7 @@ export const CreateUpdateMatch: Component<
const [dto, setDto] = createStore<IMatchUpdateDto & IMatchCreateDto>(copyObject(props.match));
const [showAdvanced, setShowAdvanced] = createSignal(false);
const [json, setJson] = createSignal('');
const [webhookHeadersErrorMessage, setWebhookHeadersErrorMessage] = createSignal('');

createEffect(() => {
try {
Expand Down Expand Up @@ -686,6 +696,80 @@ export const CreateUpdateMatch: Component<
)}
onInput={(e) => setDto('webhookUrl', e.currentTarget.value)}
/>
<TextArea
label={t('Webhook Headers')}
labelTopRight={t(
'Additional headers that will be added to each webhook request'
)}
rows="4"
value={Object.entries(dto.webhookHeaders ?? {})
.map((entry) => entry.join(': '))
.join('\n')}
class={
'font-mono border-2 ' +
getChangedClasses(
JSON.stringify(props.match.webhookHeaders ?? {}),
JSON.stringify(dto.webhookHeaders ?? {}),
'input-accent'
)
}
onInput={(e) => {
const newWebhookHeaders: IMatchCreateDto['webhookHeaders'] = {};
const lines = e.currentTarget.value.split('\n');
for (let i = 0; i < lines.length; i++) {
const line = lines[i].trim();
if (line === '') {
continue;
}
const colonIndex = line.indexOf(':');
if (colonIndex === -1) {
setWebhookHeadersErrorMessage(
'Headers must be in the format of "key: value"'
);
return;
}
const key = line.substring(0, colonIndex).trim();
const value = line.substring(colonIndex + 1).trimStart();
if (newWebhookHeaders[key] !== undefined) {
setWebhookHeadersErrorMessage(
'Multiple headers with the same key are not possible.'
);
return;
}
newWebhookHeaders[key] = value;
}
setWebhookHeadersErrorMessage('');
setDto('webhookHeaders', (prev) => {
if (prev) {
Object.keys(prev).forEach((key) => {
if (newWebhookHeaders[key] === undefined) {
newWebhookHeaders[key] = undefined!; // delete previous key/value pair which does exist any more
}
});
}
return newWebhookHeaders;
});
}}
/>
<Show when={webhookHeadersErrorMessage()}>
<div class="h-4"></div>
<div class="alert alert-error">
<svg
xmlns="http://www.w3.org/2000/svg"
class="h-6 w-6 shrink-0 stroke-current"
fill="none"
viewBox="0 0 24 24"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M10 14l2-2m0 0l2-2m-2 2l-2-2m2 2l2 2m7-2a9 9 0 11-18 0 9 9 0 0118 0z"
/>
</svg>
<span>{webhookHeadersErrorMessage()}</span>
</div>
</Show>
<TextInput
label={t('Match Passthrough')}
labelTopRight={t('Custom value to identify the match in 3rd party tools')}
Expand Down
1 change: 1 addition & 0 deletions frontend/src/pages/matchEdit.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ const getUpdateDto = (
electionSteps: getUpdateDtoAttribute(match.electionSteps, dto.electionSteps),
gameServer: getUpdateDtoAttribute(match.gameServer, dto.gameServer),
webhookUrl: getUpdateDtoAttribute(match.webhookUrl, dto.webhookUrl),
webhookHeaders: getUpdateDtoAttribute(match.webhookHeaders, dto.webhookHeaders),
rconCommands: dto.rconCommands
? {
init: getUpdateDtoAttribute(match.rconCommands.init, dto.rconCommands.init),
Expand Down

0 comments on commit cf7e3bf

Please sign in to comment.