From a2cf19d7ae447a4b70ae3e24f7824e038c0392c9 Mon Sep 17 00:00:00 2001 From: Grzegorz Godlewski Date: Tue, 24 Sep 2024 14:14:03 +0200 Subject: [PATCH] Add changes webhook support --- .../changes/WatchChangesContainer.ts | 4 ++ src/containers/server/routes/Controller.ts | 26 +++++++++- .../server/routes/DriveController.ts | 26 +++++----- .../server/routes/SearchController.ts | 8 +-- .../server/routes/WebHookController.ts | 6 +-- src/google/GoogleDriveService.ts | 52 +++++++++++++------ 6 files changed, 84 insertions(+), 38 deletions(-) diff --git a/src/containers/changes/WatchChangesContainer.ts b/src/containers/changes/WatchChangesContainer.ts index bc365855..d48630a6 100644 --- a/src/containers/changes/WatchChangesContainer.ts +++ b/src/containers/changes/WatchChangesContainer.ts @@ -113,6 +113,9 @@ export class WatchChangesContainer extends Container { if (this.intervals[driveId]) { return; } + + this.logger.info('Starting watching: ' + driveId); + this.intervals[driveId] = setInterval(async () => { if (!this.auth) { return; @@ -124,6 +127,7 @@ export class WatchChangesContainer extends Container { try { if (!this.lastToken[driveId]) { this.lastToken[driveId] = await this.googleDriveService.getStartTrackToken(this.auth, driveId); + // await this.googleDriveService.setupWatchChannel(this.auth, this.lastToken[driveId], driveId); return; } diff --git a/src/containers/server/routes/Controller.ts b/src/containers/server/routes/Controller.ts index 3ae57ca2..23498fc6 100644 --- a/src/containers/server/routes/Controller.ts +++ b/src/containers/server/routes/Controller.ts @@ -30,6 +30,12 @@ export interface ControllerRouteParamBody { docs?: RouteDoc; } +export interface ControllerRouteParamHeaders { + type: 'headers'; + parameterIndex: number; + docs?: RouteDoc; +} + export interface ControllerRouteParamStream { type: 'stream'; parameterIndex: number; @@ -70,7 +76,7 @@ export interface ControllerRouteParamPath { } type ControllerRouteParam = ControllerRouteParamGetAll | ControllerRouteParamQuery - | ControllerRouteParamBody | ControllerRouteParamPath | ControllerRouteParamStream + | ControllerRouteParamBody | ControllerRouteParamHeaders | ControllerRouteParamPath | ControllerRouteParamStream | ControllerRouteParamRelated | ControllerRouteParamUser | ControllerRouteParamMethod; export interface RouteDoc { @@ -245,6 +251,12 @@ export class Controller implements ControllerCallContext { args[param.parameterIndex] = body; } break; + case 'headers': + { + const headers = req.headers; + args[param.parameterIndex] = headers; + } + break; case 'stream': args[param.parameterIndex] = req; break; @@ -450,6 +462,18 @@ export function RouteParamBody(docs: RouteDoc = {}) { }; } +export function RouteParamHeaders(docs: RouteDoc = {}) { + return function (targetClass: Controller, methodProp: string, parameterIndex: number) { + const route = targetClass.getRoute(targetClass, methodProp); + const param: ControllerRouteParamHeaders = { + type: 'headers', + parameterIndex, + docs + }; + route.params.push(param); + }; +} + export function RouteParamStream(docs: RouteDoc = {}) { return function (targetClass: Controller, methodProp: string, parameterIndex: number) { const route = targetClass.getRoute(targetClass, methodProp); diff --git a/src/containers/server/routes/DriveController.ts b/src/containers/server/routes/DriveController.ts index 48c4acba..cc7cf319 100644 --- a/src/containers/server/routes/DriveController.ts +++ b/src/containers/server/routes/DriveController.ts @@ -1,16 +1,16 @@ -import {Controller, RouteGet, RouteParamPath, RouteParamUser, RouteResponse} from './Controller'; -import {GitScanner} from '../../../git/GitScanner'; -import {FolderRegistryContainer} from '../../folder_registry/FolderRegistryContainer'; -import {UserConfigService} from '../../google_folder/UserConfigService'; -import {FileContentService} from '../../../utils/FileContentService'; -import {GoogleDriveService} from '../../../google/GoogleDriveService'; -import {MarkdownTreeProcessor} from '../../transform/MarkdownTreeProcessor'; -import {AuthConfig} from '../../../model/AccountJson'; -import {googleMimeToExt} from '../../transform/TaskLocalFileTransform'; -import {Container} from '../../../ContainerEngine'; -import {GoogleTreeProcessor} from '../../google_folder/GoogleTreeProcessor'; -import {getContentFileService} from '../../transform/utils'; -import {redirError} from '../auth'; +import {Controller, RouteGet, RouteParamPath, RouteParamUser, RouteResponse} from './Controller.ts'; +import {GitScanner} from '../../../git/GitScanner.ts'; +import {FolderRegistryContainer} from '../../folder_registry/FolderRegistryContainer.ts'; +import {UserConfigService} from '../../google_folder/UserConfigService.ts'; +import {FileContentService} from '../../../utils/FileContentService.ts'; +import {GoogleDriveService} from '../../../google/GoogleDriveService.ts'; +import {MarkdownTreeProcessor} from '../../transform/MarkdownTreeProcessor.ts'; +import {AuthConfig} from '../../../model/AccountJson.ts'; +import {googleMimeToExt} from '../../transform/TaskLocalFileTransform.ts'; +import {Container} from '../../../ContainerEngine.ts'; +import {GoogleTreeProcessor} from '../../google_folder/GoogleTreeProcessor.ts'; +import {getContentFileService} from '../../transform/utils.ts'; +import {redirError} from '../auth.ts'; export class DriveController extends Controller { constructor(subPath: string, diff --git a/src/containers/server/routes/SearchController.ts b/src/containers/server/routes/SearchController.ts index a972a0e3..0688c91e 100644 --- a/src/containers/server/routes/SearchController.ts +++ b/src/containers/server/routes/SearchController.ts @@ -6,10 +6,10 @@ import { RouteGet, RouteParamPath, RouteParamQuery -} from './Controller'; -import {FileContentService} from '../../../utils/FileContentService'; -import {ShareErrorHandler} from './FolderController'; -import {UserConfigService} from '../../google_folder/UserConfigService'; +} from './Controller.ts'; +import {FileContentService} from '../../../utils/FileContentService.ts'; +import {ShareErrorHandler} from './FolderController.ts'; +import {UserConfigService} from '../../google_folder/UserConfigService.ts'; export class SearchController extends Controller { constructor(subPath: string, private filesService: FileContentService) { diff --git a/src/containers/server/routes/WebHookController.ts b/src/containers/server/routes/WebHookController.ts index 52b8c8c6..1d5523ac 100644 --- a/src/containers/server/routes/WebHookController.ts +++ b/src/containers/server/routes/WebHookController.ts @@ -1,7 +1,7 @@ import {Logger} from 'winston'; import { - Controller, RouteParamBody, RoutePost, + Controller, RouteParamBody, RouteParamHeaders, RoutePost, } from './Controller.ts'; export class WebHookController extends Controller { @@ -11,8 +11,8 @@ export class WebHookController extends Controller { } @RoutePost('/') - async postEvent(@RouteParamBody() body: undefined) { - this.queryLogger.info(`WebHookController.postEvent ${JSON.stringify(body)}`); + async postEvent(@RouteParamBody() body: undefined, @RouteParamHeaders() headers: undefined) { + this.queryLogger.info(`WebHookController.postEvent ${JSON.stringify(headers)} ${JSON.stringify(body)}`); return {}; } diff --git a/src/google/GoogleDriveService.ts b/src/google/GoogleDriveService.ts index a250c0ab..762022ea 100644 --- a/src/google/GoogleDriveService.ts +++ b/src/google/GoogleDriveService.ts @@ -4,6 +4,7 @@ import {Logger} from 'winston'; import {Writable} from 'stream'; +import crypto from 'crypto'; import {GoogleFile, MimeToExt, MimeTypes, SimpleFile} from '../model/GoogleFile.ts'; import {FileId} from '../model/model.ts'; @@ -47,6 +48,40 @@ export class GoogleDriveService { constructor(private logger: Logger, private quotaLimiter: QuotaLimiter) { } + async setupWatchChannel(auth: HasAccessToken, startPageToken: string, driveId: string) { + // This API does not work as intended, no webhook is executed on change + + const hexstring = crypto.randomBytes(16).toString('hex'); + const uuid = hexstring.substring(0,8) + '-' + hexstring.substring(8,12) + '-' + hexstring.substring(12,16) + '-' + hexstring.substring(16,20) + '-' + hexstring.substring(20); + + const params = { + pageToken: startPageToken, + supportsAllDrives: true, + includeItemsFromAllDrives: true, + // fields: '*', // file(id, name, mimeType, modifiedTime, size, md5Checksum, lastModifyingUser, parents, version, exportLinks, trashed)', + includeRemoved: true, + driveId: driveId ? driveId : undefined + }; + + const body = { + id: uuid, + type: 'web_hook', + address: process.env.DOMAIN + '/webhook', // Your receiving URL. + expiration: String(Date.now() + 10 * 60 * 1000) + }; + + await driveFetch(this.quotaLimiter, await auth.getAccessToken(), 'POST', 'https://www.googleapis.com/drive/v3/changes/watch', params, body); +/* + { + kind: 'api#channel', + id: '1873c104-3f34-07e2-086e-c97e8c23cb55', + resourceId: 'VZoPsZrgUX6TNl0BxbV2rN_zUIU', + resourceUri: 'https://www.googleapis.com/drive/v3/changes?alt=json&driveId=0AI7ud-sa0EAJUk9PVA&fields=*&includeItemsFromAllDrives=true&includeRemoved=true&pageToken=78&supportsAllDrives=true', + expiration: '1727025863000' + } +*/ + } + async getStartTrackToken(auth: HasAccessToken, driveId?: string): Promise { const params = { supportsAllDrives: true, @@ -61,23 +96,6 @@ export class GoogleDriveService { return res.startPageToken; } - async subscribeWatch(auth: HasAccessToken, pageToken: string, driveId?: string) { - try { - const params = { - pageToken, - supportsAllDrives: true, - includeItemsFromAllDrives: true, - fields: 'newStartPageToken, nextPageToken, changes( file(id, name, mimeType, modifiedTime, size, md5Checksum, lastModifyingUser, parents, version, exportLinks, trashed), removed)', - includeRemoved: true, - driveId: driveId ? driveId : undefined - }; - return await driveFetch(this.quotaLimiter, await auth.getAccessToken(), 'POST', 'https://www.googleapis.com/drive/v3/changes/watch', params); - } catch (err) { - err.message = 'Error watching: ' + err.message; - throw err; - } - } - async watchChanges(auth: HasAccessToken, pageToken: string, driveId?: string): Promise { try { const params = {