Skip to content

Commit

Permalink
added: facebook workplace integration (hyperjumptech#219)
Browse files Browse the repository at this point in the history
  • Loading branch information
adeyahya authored Jun 11, 2021
1 parent 5ae3902 commit d7636ee
Show file tree
Hide file tree
Showing 10 changed files with 244 additions and 0 deletions.
21 changes: 21 additions & 0 deletions config_sample/config.workplace.example.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
{
"notifications": [
{
"id": "unique-workplace-id",
"type": "workplace",
"data": {
"thread_id": "12345678910",
"access_token": "your_custom_integration_access_token"
}
}
],
"probes": [
{
"requests": [
{
"url": "https://github.com"
}
]
}
]
}
23 changes: 23 additions & 0 deletions docs/src/pages/guides/notifications.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ At this moment, Monika support these channel of notifications (You can use just
7. [WhatsApp](https://hyperjumptech.github.io/monika/guides/notifications#whatsapp)
8. [Microsoft Teams](https://hyperjumptech.github.io/monika/guides/notifications#microsoft-teams)
9. [Discord](https://hyperjumptech.github.io/monika/guides/notifications#discord)
10. [Facebook Workplace](https://hyperjumptech.github.io/monika/guides/notifications#facebook-workplace)

We are working on more notifications like Telegram, and many more. You can help!

Expand Down Expand Up @@ -298,6 +299,28 @@ Monika supports Discord. To enable notification via Discord, you must create a d
| Type | Notification types | `discord` |
| Url | The URL of the Discord Webhook that will receive notification | `https://discord.com/api/webhook/<webhook.id>/<webhook.token>` |

## Facebook Workplace

Monika supports Facebook Workplace. To enable notifiation via Workplace, you must create custom integration first. More info at [Facebook Workplace Custom Integrations](https://developers.facebook.com/docs/workplace/custom-integrations-new/)

```json
{
"id": "unique-workplace-id",
"type": "workplace",
"data": {
"thread_id": "12345678910",
"access_token": "your_custom_integration_access_token"
}
}
```

| Key | Description | Example |
| ----------- | ----------------------------------------------- | ------------------------------- |
| ID | Notification identity number | `Workplace12345` |
| Type | Notification types | `workplace` |
| ThreadID | It's located at thread url, in the last segment | `6367478493277649` |
| AccessToken | Workplace access token for custom integration | `DQVJzYWtsdHRJRWIxUk9uOG5VV...` |

## Monika Whatsapp Notifier (COMING SOON!)

Monika now have its own notification channel, sent through your whatsapp number. To enable notification via Monika Whatsapp Notifier, you must create a Monika Notifier account first.
Expand Down
17 changes: 17 additions & 0 deletions src/components/config/validate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ import {
TeamsData,
DiscordData,
MonikaNotifData,
WorkplaceData,
} from '../../interfaces/data'
import { Config } from '../../interfaces/config'
import { RequestConfig } from '../../interfaces/request'
Expand Down Expand Up @@ -112,6 +113,14 @@ const WHATSAPP_NO_URL = setInvalidResponse('Whatsapp URL not found')
const WHATSAPP_NO_USERNAME = setInvalidResponse('Whatsapp Username not found')
const WHATSAPP_NO_PASSWORD = setInvalidResponse('Whatsapp Password not found')

// Workplace
const WORKPLACE_NO_ACCESS_TOKEN = setInvalidResponse(
'Workplace Access Token not found'
)
const WORKPLACE_NO_THREAD_ID = setInvalidResponse(
'Workplace Thread ID not found'
)

function validateNotification(notifications: Notification[]): Validation {
// Check notifications properties
for (const notification of notifications) {
Expand Down Expand Up @@ -189,6 +198,14 @@ function validateNotification(notifications: Notification[]): Validation {
break
}

case 'workplace': {
if (!(data as WorkplaceData).access_token)
return WORKPLACE_NO_ACCESS_TOKEN
if (!(data as WorkplaceData).thread_id) return WORKPLACE_NO_THREAD_ID

break
}

default:
return NOTIFICATION_INVALID_TYPE
}
Expand Down
54 changes: 54 additions & 0 deletions src/components/notification/channel/workplace.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
/**********************************************************************************
* MIT License *
* *
* Copyright (c) 2021 Hyperjump Technology *
* *
* Permission is hereby granted, free of charge, to any person obtaining a copy *
* of this software and associated documentation files (the "Software"), to deal *
* in the Software without restriction, including without limitation the rights *
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell *
* copies of the Software, and to permit persons to whom the Software is *
* furnished to do so, subject to the following conditions: *
* *
* The above copyright notice and this permission notice shall be included in all *
* copies or substantial portions of the Software. *
* *
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR *
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, *
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE *
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER *
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, *
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE *
* SOFTWARE. *
**********************************************************************************/

import axios from 'axios'
import { WorkplaceData } from '../../../interfaces/data'

export const sendWorkplace = async (data: WorkplaceData) => {
try {
const httpClient = axios.create({
baseURL: 'https://graph.workplace.com',
headers: {
Authorization: `Bearer ${data.access_token}`,
},
})

const res = await httpClient({
method: 'POST',
url: '/me/messages',
data: {
recipient: {
thread_key: data.thread_id,
},
message: {
text: `*${data.body.alert}*\n\n*URL*: ${data.body.url}\n*TIME*: ${data.body.time}\n`,
},
},
})

return res
} catch (error) {
throw error
}
}
29 changes: 29 additions & 0 deletions src/components/notification/checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ import {
TeamsData,
TelegramData,
WebhookData,
WorkplaceData,
} from '../../interfaces/data'
import { Notification } from '../../interfaces/notification'
import getIp from '../../utils/ip'
Expand All @@ -43,6 +44,7 @@ import { sendTeams } from './channel/teams'
import { sendTelegram } from './channel/telegram'
import { sendWebhook } from './channel/webhook'
import { sendMonikaNotif } from './channel/monika-notif'
import { sendWorkplace } from './channel/workplace'
import {
dataDiscordSchemaValidator,
dataMailgunSchemaValidator,
Expand All @@ -53,6 +55,7 @@ import {
dataTeamsSchemaValidator,
dataTelegramSchemaValidator,
dataWebhookSchemaValidator,
dataWorkplaceSchemaValidator,
} from './validator'

const subject = 'Monika is started'
Expand Down Expand Up @@ -249,6 +252,26 @@ const monikaNotificationInitialChecker = async (data: MonikaNotifData) => {
}
}

const workplaceNotificationInitialChecker = async (data: WorkplaceData) => {
try {
await dataWorkplaceSchemaValidator.validateAsync(data)

await sendWorkplace({
thread_id: data.thread_id,
access_token: data.access_token,
body: {
url: '-',
alert: body,
time: new Date().toLocaleString(),
},
})

return 'success'
} catch (error) {
throw errorMessage('Workplace', error?.message)
}
}

export const notificationChecker = async (notifications: Notification[]) => {
const smtpNotification = notifications
.filter((notif) => notif.type === 'smtp')
Expand Down Expand Up @@ -294,6 +317,11 @@ export const notificationChecker = async (notifications: Notification[]) => {
.map((notif) => notif.data as MonikaNotifData)
.map(monikaNotificationInitialChecker)

const workplaceNotification = notifications
.filter((notif) => notif.type === 'workplace')
.map((notif) => notif.data as WorkplaceData)
.map(workplaceNotificationInitialChecker)

return Promise.all([
Promise.all(smtpNotification),
Promise.all(mailgunNotification),
Expand All @@ -304,5 +332,6 @@ export const notificationChecker = async (notifications: Notification[]) => {
Promise.all(telegramNotification),
Promise.all(discordNotification),
Promise.all(monikaNotification),
Promise.all(workplaceNotification),
])
}
16 changes: 16 additions & 0 deletions src/components/notification/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ import {
WebhookData,
WhatsappData,
DiscordData,
WorkplaceData,
MonikaNotifData,
} from '../../interfaces/data'
import { Notification } from '../../interfaces/notification'
Expand All @@ -44,6 +45,7 @@ import { sendWebhook } from './channel/webhook'
import { sendWhatsapp } from './channel/whatsapp'
import { sendDiscord } from './channel/discord'
import { sendMonikaNotif } from './channel/monika-notif'
import { sendWorkplace } from './channel/workplace'

export type ValidateResponseStatus = { alert: string; status: boolean }

Expand Down Expand Up @@ -213,6 +215,20 @@ export async function sendAlerts({
url,
}))
}
case 'workplace': {
return sendWorkplace({
...notification.data,
body: {
url,
alert: validation.alert,
time: new Date().toLocaleString(),
},
} as WorkplaceData).then(() => ({
notification: 'workplace',
alert: validation.alert,
url,
}))
}
default: {
return Promise.resolve({
notification: '',
Expand Down
7 changes: 7 additions & 0 deletions src/components/notification/validator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -101,3 +101,10 @@ export const dataMonikaNotifSchemaValidator = dataBaseEmailSchemaValidator(
).keys({
url: Joi.string().uri().required().label('Monika Notification URL'),
})

export const dataWorkplaceSchemaValidator = dataBaseEmailSchemaValidator(
'Workplace'
).keys({
thread_id: Joi.string().required().label('Workplace Thread ID'),
access_token: Joi.string().required().label('Workplace Access Token'),
})
12 changes: 12 additions & 0 deletions src/interfaces/data.ts
Original file line number Diff line number Diff line change
Expand Up @@ -99,3 +99,15 @@ export interface DiscordDataBody {
time: string
alert: string
}

export interface WorkplaceData {
thread_id: string
access_token: string
body: WorkplaceDataBody
}

export interface WorkplaceDataBody {
url: string
time: string
alert: string
}
3 changes: 3 additions & 0 deletions src/interfaces/notification.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ import {
TeamsData,
DiscordData,
MonikaNotifData,
WorkplaceData,
} from './data'

export interface Notification {
Expand All @@ -47,6 +48,7 @@ export interface Notification {
| 'telegram'
| 'discord'
| 'monika-notif'
| 'workplace'
data:
| MailgunData
| SMTPData
Expand All @@ -57,4 +59,5 @@ export interface Notification {
| TelegramData
| DiscordData
| MonikaNotifData
| WorkplaceData
}
62 changes: 62 additions & 0 deletions test/components/notification/checker.workplace.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import chai, { expect } from 'chai'
import spies from 'chai-spies'

import {
errorMessage,
notificationChecker,
} from '../../../src/components/notification/checker'
import { WorkplaceData } from '../../../src/interfaces/data'
import { Notification } from '../../../src/interfaces/notification'

chai.use(spies)

describe('notificationChecker - workplaceNotification', () => {
afterEach(() => {
chai.spy.restore()
})

const notificationConfig = {
id: 'workplace',
type: 'workplace',
} as Notification

it('should handle validation error - without Thread ID', async () => {
try {
await notificationChecker([
{
...notificationConfig,
data: {
access_token: 'ABC-EFG-HIJ-KLM-NOP-QRS-TUV-WXY-Z',
} as WorkplaceData,
},
])
} catch (error) {
const originalErrorMessage = '"Workplace Thread ID" is required'
const { message } = errorMessage('Workplace', originalErrorMessage)

expect(() => {
throw error
}).to.throw(message)
}
})

it('should handle validation error - without Access Token', async () => {
try {
await notificationChecker([
{
...notificationConfig,
data: {
thread_id: 'ABC-EFG-HIJ-KLM-NOP-QRS-TUV-WXY-Z',
} as WorkplaceData,
},
])
} catch (error) {
const originalErrorMessage = '"Workplace Access Token" is required'
const { message } = errorMessage('Workplace', originalErrorMessage)

expect(() => {
throw error
}).to.throw(message)
}
})
})

0 comments on commit d7636ee

Please sign in to comment.