2022-08-17 08:50:24 -07:00
import { randomBytes } from 'crypto' ;
2023-01-27 03:22:44 -08:00
import type {
2023-03-09 09:13:15 -08:00
IHookFunctions ,
IWebhookFunctions ,
2023-01-27 03:22:44 -08:00
IDataObject ,
INodeType ,
INodeTypeDescription ,
IWebhookResponseData ,
} from 'n8n-workflow' ;
2020-10-22 01:17:39 -07:00
2022-08-17 08:50:24 -07:00
import { stravaApiRequest } from './GenericFunctions' ;
2020-10-22 01:17:39 -07:00
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 : [
{
2022-05-20 14:47:24 -07:00
name : '[All]' ,
2020-10-22 01:17:39 -07:00
value : '*' ,
} ,
{
name : 'Activity' ,
value : 'activity' ,
} ,
{
name : 'Athlete' ,
value : 'athlete' ,
} ,
] ,
default : '*' ,
} ,
{
displayName : 'Event' ,
name : 'event' ,
type : 'options' ,
options : [
{
2022-05-20 14:47:24 -07:00
name : '[All]' ,
2020-10-22 01:17:39 -07:00
value : '*' ,
} ,
{
2022-06-03 10:23:49 -07:00
name : 'Created' ,
2020-10-22 01:17:39 -07:00
value : 'create' ,
} ,
{
name : 'Deleted' ,
value : 'delete' ,
} ,
{
name : 'Updated' ,
value : 'update' ,
} ,
] ,
default : '*' ,
} ,
{
displayName : 'Resolve Data' ,
name : 'resolveData' ,
type : 'boolean' ,
default : true ,
2022-06-20 07:54:01 -07:00
// eslint-disable-next-line n8n-nodes-base/node-param-description-boolean-without-whether
2022-08-17 08:50:24 -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-06-20 07:54:01 -07:00
// eslint-disable-next-line n8n-nodes-base/node-param-description-boolean-without-whether
2022-08-17 08:50:24 -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
} ,
] ,
} ,
] ,
} ;
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 ,
2022-12-02 12:54:28 -08:00
verify_token : randomBytes ( 20 ) . toString ( 'hex' ) ,
2020-10-22 01:17:39 -07:00
} ;
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
2022-08-17 08:50:24 -07:00
const webhooks = await stravaApiRequest . call (
this ,
'GET' ,
2022-12-29 03:20:43 -08:00
'/push_subscriptions' ,
2022-08-17 08:50:24 -07:00
{ } ,
) ;
2020-10-22 01:19:17 -07:00
if ( options . deleteIfExist ) {
// delete the subscription
2022-08-17 08:50:24 -07:00
await stravaApiRequest . call (
this ,
'DELETE' ,
` /push_subscriptions/ ${ webhooks [ 0 ] . id } ` ,
) ;
2020-10-22 01:19:17 -07:00
// now there is room create a subscription with the n8n data
2022-12-02 12:54:28 -08:00
const requestBody = {
2020-10-22 01:19:17 -07:00
callback_url : webhookUrl ,
2022-12-02 12:54:28 -08:00
verify_token : randomBytes ( 20 ) . toString ( 'hex' ) ,
2020-10-22 01:19:17 -07:00
} ;
2022-08-17 08:50:24 -07:00
responseData = await stravaApiRequest . call (
this ,
'POST' ,
2022-12-29 03:20:43 -08:00
'/push_subscriptions' ,
2022-12-02 12:54:28 -08:00
requestBody ,
2022-08-17 08:50:24 -07:00
) ;
2020-10-22 01:19:17 -07:00
} 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
2023-03-03 09:49:19 -08:00
// that no webhooks are registered anymore
2020-10-22 01:17:39 -07:00
delete webhookData . webhookId ;
}
return true ;
} ,
} ,
} ;
async webhook ( this : IWebhookFunctions ) : Promise < IWebhookResponseData > {
2022-12-02 12:54:28 -08:00
const body = this . getBodyData ( ) ;
2020-10-22 01:17:39 -07:00
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 { } ;
}
2023-05-19 06:28:57 -07:00
if ( resolveData && body . aspect_type !== 'delete' ) {
2020-10-22 01:17:39 -07:00
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 {
2022-08-17 08:50:24 -07:00
workflowData : [ this . helpers . returnJsonArray ( body ) ] ,
2020-10-22 01:17:39 -07:00
} ;
}
}