Skip to content

WIP: webhook demo #508

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
Oct 30, 2021
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
configure webhook with token
Signed-off-by: shmck <[email protected]>
  • Loading branch information
ShMcK committed Aug 29, 2021
commit ae5345ce9db4578ec029e339addce35d82a7279b
2 changes: 2 additions & 0 deletions docs/docs/env-vars.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ CodeRoad has a number of configurations:

- `CODEROAD_CONTENT_SECURITY_POLICY_EXEMPTIONS` - a list of CSP exemption hashes. For multiples, separate the list with a space.

- `CODEROAD_WEBHOOK_TOKEN` - an optional token for authenticating/authorizing webhook endpoints. Passed to the webhook endpoint in a `CodeRoad-User-Token` header.

## How to Use Variables

### Local
Expand Down
6 changes: 6 additions & 0 deletions src/actions/onTutorialConfigContinue.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import Context from '../services/context/context'
import tutorialConfig from './utils/tutorialConfig'
import { COMMANDS, send } from '../commands'
import logger from '../services/logger'
import { setupWebhook } from '../services/hooks/webhooks'

const onTutorialConfigContinue = async (action: T.Action, context: Context): Promise<void> => {
logger('onTutorialConfigContinue', action)
Expand All @@ -19,6 +20,11 @@ const onTutorialConfigContinue = async (action: T.Action, context: Context): Pro
data: tutorialToContinue,
alreadyConfigured: true,
})

// configure webhook
if (tutorialToContinue.config?.webhook) {
setupWebhook(tutorialToContinue.config.webhook)
}
} catch (e) {
const error = {
type: 'UnknownError',
Expand Down
6 changes: 6 additions & 0 deletions src/actions/onTutorialConfigNew.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { version, compareVersions } from '../services/dependencies'
import Context from '../services/context/context'
import tutorialConfig from './utils/tutorialConfig'
import { send } from '../commands'
import { setupWebhook } from '../services/hooks/webhooks'

const onTutorialConfigNew = async (action: T.Action, context: Context): Promise<void> => {
try {
Expand Down Expand Up @@ -108,6 +109,11 @@ const onTutorialConfigNew = async (action: T.Action, context: Context): Promise<
return
}

// configure webhook
if (data.config?.webhook) {
setupWebhook(data.config.webhook)
}

// report back to the webview that setup is complete
send({ type: 'TUTORIAL_CONFIGURED' })
} catch (e) {
Expand Down
3 changes: 3 additions & 0 deletions src/environment.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,3 +43,6 @@ export const DISABLE_RUN_ON_SAVE = (process.env.CODEROAD_DISABLE_RUN_ON_SAVE ||
// for multiple exemptions, separate each with a space "a1 b1"
export const CONTENT_SECURITY_POLICY_EXEMPTIONS: string | null =
process.env.CODEROAD_CONTENT_SECURITY_POLICY_EXEMPTIONS || null

// optional token for authorization/authentication of webhook calls
export const WEBHOOK_TOKEN = process.env.CODEROAD_WEBHOOK_TOKEN || null
59 changes: 44 additions & 15 deletions src/services/hooks/webhooks.ts
Original file line number Diff line number Diff line change
@@ -1,28 +1,57 @@
import * as TT from 'typings/tutorial'
import fetch from 'node-fetch'
import logger from '../logger'
import { WEBHOOK_TOKEN } from '../../environment'

const WEBHOOKS = {
init: true,
reset: true,
step_complete: true,
level_complete: true,
tutorial_complete: true,
const WEBHOOK_EVENTS = {
init: false,
reset: false,
step_complete: false,
level_complete: false,
tutorial_complete: false,
}

// varaibles set on init
let WEBHOOK_URI: string | undefined

export const setupWebhook = (webhookConfig: TT.WebhookConfig) => {
if (!webhookConfig.url) {
return
}
// set webhook uri
WEBHOOK_URI = webhookConfig.url

// set webhook event triggers
const events = webhookConfig.events as TT.WebhookConfigEvents
for (const eventName of Object.keys(events || {})) {
WEBHOOK_EVENTS[eventName] = events[eventName]
}
}

const callWebhookEndpoint = async <B>(bodyObject: B): Promise<void> => {
const endpoint = 'http://localhost:3000'
if (!WEBHOOK_URI) {
return
}

const headers = { 'Content-Type': 'application/json' }
// if the webhook token is specified as env var, sends a token with the request
if (WEBHOOK_TOKEN) {
headers['CodeRoad-User-Token'] = WEBHOOK_TOKEN
}

const body = JSON.stringify(bodyObject)

try {
const sendEvent = await fetch(endpoint, {
const sendEvent = await fetch(WEBHOOK_URI, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
headers,
body,
})
if (!sendEvent.ok) {
throw new Error('Error sending event')
}
} catch (err: unknown) {
logger(`Failed to call webhook endpoint ${endpoint} with body ${body}`)
logger(`Failed to call webhook endpoint ${WEBHOOK_URI} with body ${body}`)
}
}

Expand All @@ -32,7 +61,7 @@ type WebhookEventInit = {
}

export const onInit = (event: WebhookEventInit): void => {
if (WEBHOOKS.init) {
if (WEBHOOK_EVENTS.init) {
callWebhookEndpoint<WebhookEventInit>(event)
}
}
Expand All @@ -42,31 +71,31 @@ type WebhookEventReset = {
}

export const onReset = (event: WebhookEventReset): void => {
if (WEBHOOKS.reset) {
if (WEBHOOK_EVENTS.reset) {
callWebhookEndpoint<WebhookEventReset>(event)
}
}

type WebhookEventStepComplete = { tutorialId: string; version: string; levelId: string; stepId: string }
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
type WebhookEventStepComplete = { tutorialId: string; version: string; levelId: string; stepId: string }
type WebhookEventStepComplete = { tutorialId: string; version?: string; levelId: string; stepId: string }


export const onStepComplete = (event: WebhookEventStepComplete): void => {
if (WEBHOOKS.step_complete) {
if (WEBHOOK_EVENTS.step_complete) {
callWebhookEndpoint<WebhookEventStepComplete>(event)
}
}

type WebhookEventLevelComplete = { tutorialId: string; version: string; levelId: string }
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
type WebhookEventLevelComplete = { tutorialId: string; version: string; levelId: string }
type WebhookEventLevelComplete = { tutorialId: string; version?: string; levelId: string }


export const onLevelComplete = (event: WebhookEventLevelComplete): void => {
if (WEBHOOKS.level_complete) {
if (WEBHOOK_EVENTS.level_complete) {
callWebhookEndpoint<WebhookEventLevelComplete>(event)
}
}

type WebhookEevntTutorialComplete = { tutorialId: string; version: string }

export const onTutorialComplete = (event: WebhookEevntTutorialComplete): void => {
if (WEBHOOKS.tutorial_complete) {
if (WEBHOOK_EVENTS.tutorial_complete) {
callWebhookEndpoint<WebhookEevntTutorialComplete>(event)
}
}
18 changes: 8 additions & 10 deletions typings/tutorial.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -94,16 +94,14 @@ export interface TutorialAppVersions {

export type VSCodeCommand = string | [string, any]

export interface WebhookConfigEvents {
init?: boolean
reset?: boolean
step_complete?: boolean
level_complete?: boolean
tutorial_complete?: boolean
}
export interface WebhookConfig {
url: string
config: {
token: boolean
}
events: {
init?: boolean
reset?: boolean
step_complete?: boolean
level_complete?: boolean
tutorial_complete?: boolean
}
events?: WebhookConfigEvents
}