2020-10-22 01:17:39 -07:00
import {
IHookFunctions ,
IWebhookFunctions ,
} from 'n8n-core' ;
import {
IDataObject ,
INodeType ,
INodeTypeDescription ,
IWebhookResponseData ,
2021-04-16 09:33:36 -07:00
NodeApiError ,
2020-10-22 01:17:39 -07:00
} from 'n8n-workflow' ;
import {
stravaApiRequest ,
} from './GenericFunctions' ;
import {
randomBytes ,
} from 'crypto' ;
export class StravaTrigger implements INodeType {
description : INodeTypeDescription = {
displayName : 'Strava Trigger' ,
2020-10-22 03:08:17 -07:00
name : 'stravaTrigger' ,
2020-10-22 01:17:39 -07:00
icon : 'file:strava.svg' ,
group : [ 'trigger' ] ,
version : 1 ,
2021-07-03 05:40:16 -07:00
description : 'Starts the workflow when Strava events occur' ,
2020-10-22 01:17:39 -07:00
defaults : {
name : 'Strava Trigger' ,
} ,
inputs : [ ] ,
outputs : [ 'main' ] ,
credentials : [
{
name : 'stravaOAuth2Api' ,
required : true ,
} ,
] ,
webhooks : [
{
name : 'setup' ,
httpMethod : 'GET' ,
responseMode : 'onReceived' ,
path : 'webhook' ,
} ,
{
name : 'default' ,
httpMethod : 'POST' ,
responseMode : 'onReceived' ,
path : 'webhook' ,
} ,
] ,
properties : [
{
displayName : 'Object' ,
name : 'object' ,
type : 'options' ,
options : [
{
name : '*' ,
value : '*' ,
} ,
{
name : 'Activity' ,
value : 'activity' ,
} ,
{
name : 'Athlete' ,
value : 'athlete' ,
} ,
] ,
default : '*' ,
} ,
{
displayName : 'Event' ,
name : 'event' ,
type : 'options' ,
options : [
{
name : '*' ,
value : '*' ,
} ,
{
name : 'created' ,
value : 'create' ,
} ,
{
name : 'Deleted' ,
value : 'delete' ,
} ,
{
name : 'Updated' ,
value : 'update' ,
} ,
] ,
default : '*' ,
} ,
{
displayName : 'Resolve Data' ,
name : 'resolveData' ,
type : 'boolean' ,
default : true ,
2021-10-27 13:00:13 -07:00
description : 'By default the webhook-data only contain the Object ID. If this option gets activated, it will resolve the data automatically.' ,
2020-10-22 01:17:39 -07:00
} ,
{
displayName : 'Options' ,
name : 'options' ,
type : 'collection' ,
placeholder : 'Add Option' ,
default : { } ,
options : [
{
displayName : 'Delete If Exist' ,
name : 'deleteIfExist' ,
type : 'boolean' ,
default : false ,
2022-05-06 14:01:25 -07:00
description : 'Strava allows just one subscription at all times. If you want to delete the current subscription to make room for a new subcription with the current parameters, set this parameter to true. Keep in mind this is a destructive operation.' ,
2020-10-22 01:17:39 -07:00
} ,
] ,
} ,
] ,
} ;
// @ts-ignore (because of request)
webhookMethods = {
default : {
async checkExists ( this : IHookFunctions ) : Promise < boolean > {
const webhookUrl = this . getNodeWebhookUrl ( 'default' ) ;
const webhookData = this . getWorkflowStaticData ( 'node' ) ;
// Check all the webhooks which exist already if it is identical to the
// one that is supposed to get created.
const endpoint = '/push_subscriptions' ;
const webhooks = await stravaApiRequest . call ( this , 'GET' , endpoint , { } ) ;
for ( const webhook of webhooks ) {
if ( webhook . callback_url === webhookUrl ) {
webhookData . webhookId = webhook . id ;
return true ;
}
}
return false ;
} ,
async create ( this : IHookFunctions ) : Promise < boolean > {
const webhookData = this . getWorkflowStaticData ( 'node' ) ;
const webhookUrl = this . getNodeWebhookUrl ( 'default' ) ;
const endpoint = '/push_subscriptions' ;
const body = {
callback_url : webhookUrl ,
verify_token : randomBytes ( 20 ) . toString ( 'hex' ) as string ,
} ;
let responseData ;
try {
responseData = await stravaApiRequest . call ( this , 'POST' , endpoint , body ) ;
} catch ( error ) {
2021-04-16 09:33:36 -07:00
const apiErrorResponse = error . cause . response ;
if ( apiErrorResponse ? . body ? . errors ) {
const errors = apiErrorResponse . body . errors ;
2020-10-22 01:19:17 -07:00
for ( error of errors ) {
// if there is a subscription already created
if ( error . resource === 'PushSubscription' && error . code === 'already exists' ) {
const options = this . getNodeParameter ( 'options' ) as IDataObject ;
//get the current subscription
const webhooks = await stravaApiRequest . call ( this , 'GET' , ` /push_subscriptions ` , { } ) ;
if ( options . deleteIfExist ) {
// delete the subscription
await stravaApiRequest . call ( this , 'DELETE' , ` /push_subscriptions/ ${ webhooks [ 0 ] . id } ` ) ;
// now there is room create a subscription with the n8n data
const body = {
callback_url : webhookUrl ,
verify_token : randomBytes ( 20 ) . toString ( 'hex' ) as string ,
} ;
responseData = await stravaApiRequest . call ( this , 'POST' , ` /push_subscriptions ` , body ) ;
} else {
2021-04-16 09:33:36 -07:00
error . message = ` A subscription already exists [ ${ webhooks [ 0 ] . callback_url } ]. If you want to delete this subcription and create a new one with the current parameters please go to options and set delete if exist to true ` ;
throw error ;
2020-10-22 01:19:17 -07:00
}
2020-10-22 01:17:39 -07:00
}
}
}
2020-10-22 01:19:17 -07:00
if ( ! responseData ) {
2021-04-16 09:33:36 -07:00
throw error ;
2020-10-22 01:19:17 -07:00
}
2020-10-22 01:17:39 -07:00
}
if ( responseData . id === undefined ) {
// Required data is missing so was not successful
return false ;
}
webhookData . webhookId = responseData . id as string ;
return true ;
} ,
async delete ( this : IHookFunctions ) : Promise < boolean > {
const webhookData = this . getWorkflowStaticData ( 'node' ) ;
if ( webhookData . webhookId !== undefined ) {
const endpoint = ` /push_subscriptions/ ${ webhookData . webhookId } ` ;
try {
await stravaApiRequest . call ( this , 'DELETE' , endpoint ) ;
2021-04-16 09:33:36 -07:00
} catch ( error ) {
2020-10-22 01:17:39 -07:00
return false ;
}
// Remove from the static workflow data so that it is clear
// that no webhooks are registred anymore
delete webhookData . webhookId ;
}
return true ;
} ,
} ,
} ;
async webhook ( this : IWebhookFunctions ) : Promise < IWebhookResponseData > {
const body = this . getBodyData ( ) as IDataObject ;
const query = this . getQueryData ( ) as IDataObject ;
const object = this . getNodeParameter ( 'object' ) ;
const event = this . getNodeParameter ( 'event' ) ;
const resolveData = this . getNodeParameter ( 'resolveData' ) as boolean ;
let objectType , eventType ;
if ( object === '*' ) {
objectType = [ 'activity' , 'athlete' ] ;
} else {
objectType = [ object ] ;
}
if ( event === '*' ) {
eventType = [ 'create' , 'update' , 'delete' ] ;
} else {
eventType = [ event ] ;
}
if ( this . getWebhookName ( ) === 'setup' ) {
if ( query [ 'hub.challenge' ] ) {
// Is a create webhook confirmation request
const res = this . getResponseObject ( ) ;
res . status ( 200 ) . json ( { 'hub.challenge' : query [ 'hub.challenge' ] } ) . end ( ) ;
return {
noWebhookResponse : true ,
} ;
}
}
if ( object !== '*' && ! objectType . includes ( body . object_type as string ) ) {
return { } ;
}
if ( event !== '*' && ! eventType . includes ( body . aspect_type as string ) ) {
return { } ;
}
if ( resolveData ) {
let endpoint = ` /athletes/ ${ body . object_id } /stats ` ;
if ( body . object_type === 'activity' ) {
endpoint = ` /activities/ ${ body . object_id } ` ;
}
body . object_data = await stravaApiRequest . call ( this , 'GET' , endpoint ) ;
}
return {
workflowData : [
2020-10-22 06:46:03 -07:00
this . helpers . returnJsonArray ( body ) ,
2020-10-22 01:17:39 -07:00
] ,
} ;
}
}