@oomol-lab/open-connector 1.7.0 → 1.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +4 -0
- package/assets/open-connector/catalog/apps/airtable.json +134 -0
- package/assets/open-connector/catalog/apps/aisa.json +916 -76
- package/assets/open-connector/catalog/apps/cal.json +24 -6
- package/assets/open-connector/catalog/apps/discordbot.json +2 -2
- package/assets/open-connector/catalog/apps/enormail.json +411 -0
- package/assets/open-connector/catalog/apps/feishu_app_bot.json +158 -0
- package/assets/open-connector/catalog/apps/github.json +275 -0
- package/assets/open-connector/catalog/apps/gitlab.json +125 -0
- package/assets/open-connector/catalog/apps/gmail.json +151 -0
- package/assets/open-connector/catalog/apps/googlecalendar.json +206 -0
- package/assets/open-connector/catalog/apps/googledrive.json +479 -0
- package/assets/open-connector/catalog/apps/googlesheets.json +144 -0
- package/assets/open-connector/catalog/apps/hithink_finance.json +6 -3
- package/assets/open-connector/catalog/apps/indexed.json +1115 -0
- package/assets/open-connector/catalog/apps/linear.json +116 -0
- package/assets/open-connector/catalog/apps/notion.json +142 -0
- package/assets/open-connector/catalog/apps/one_drive.json +185 -0
- package/assets/open-connector/catalog/apps/outlook.json +182 -0
- package/assets/open-connector/catalog/apps/shopify.json +282 -0
- package/assets/open-connector/catalog/apps/slack.json +182 -0
- package/assets/open-connector/catalog/apps/spotify.json +46 -43
- package/assets/open-connector/catalog/apps/stripe.json +96 -0
- package/assets/open-connector/catalog/apps/telegram.json +138 -0
- package/assets/open-connector/catalog/apps/weixin_store.json +543 -2
- package/assets/open-connector/catalog/apps/woocommerce.json +127 -0
- package/assets/open-connector/catalog/apps/zendesk.json +152 -0
- package/assets/open-connector/catalog/apps/zerotier.json +9 -9
- package/assets/open-connector/catalog/apps-index.json +1 -1
- package/assets/open-connector/migrations/0014_saas_project.sql +55 -0
- package/assets/open-connector/migrations/0015_saas_cleanup_runtime.sql +8 -0
- package/assets/open-connector/migrations/0016_trigger_policy.sql +1 -0
- package/assets/open-connector/migrations/0017_trigger_subscriptions.sql +17 -0
- package/assets/open-connector/migrations/postgresql/0014_saas_project.sql +55 -0
- package/assets/open-connector/migrations/postgresql/0015_saas_cleanup_runtime.sql +8 -0
- package/assets/open-connector/migrations/postgresql/0016_trigger_policy.sql +1 -0
- package/assets/open-connector/migrations/postgresql/0017_trigger_subscriptions.sql +17 -0
- package/package.json +2 -2
- package/src/connection-service.d.ts +40 -7
- package/src/connection-service.js +60 -18
- package/src/core/action-policy.d.ts +8 -1
- package/src/core/action-policy.js +51 -0
- package/src/core/cast.d.ts +3 -0
- package/src/core/cast.js +4 -1
- package/src/core/guarded-fetch.d.ts +5 -0
- package/src/core/guarded-fetch.js +13 -4
- package/src/core/remote-http.d.ts +33 -0
- package/src/core/remote-http.js +104 -0
- package/src/core/request.d.ts +1 -0
- package/src/core/request.js +7 -0
- package/src/core/types.d.ts +4 -0
- package/src/marketplace/marketplace-service.d.ts +0 -1
- package/src/marketplace/marketplace-service.js +80 -92
- package/src/mcp.js +19 -5
- package/src/oauth/oauth-flow-service.d.ts +8 -1
- package/src/oauth/oauth-flow-service.js +10 -4
- package/src/providers/action-contracts.generated.d.ts +4 -2
- package/src/providers/airtable/executors.d.ts +3 -0
- package/src/providers/airtable/executors.js +2 -0
- package/src/providers/airtable/trigger-on-record-changed.d.ts +2 -0
- package/src/providers/airtable/trigger-on-record-changed.definition.d.ts +4 -0
- package/src/providers/airtable/trigger-on-record-changed.definition.js +54 -0
- package/src/providers/airtable/trigger-on-record-changed.js +235 -0
- package/src/providers/aisa/executors.js +196 -22
- package/src/providers/cal/actions.js +5 -1
- package/src/providers/enormail/executors.d.ts +4 -0
- package/src/providers/enormail/executors.js +97 -0
- package/src/providers/feishu_app_bot/executors.d.ts +3 -0
- package/src/providers/feishu_app_bot/executors.js +2 -0
- package/src/providers/feishu_app_bot/trigger-config.d.ts +1 -0
- package/src/providers/feishu_app_bot/trigger-config.js +10 -0
- package/src/providers/feishu_app_bot/trigger-on-event.d.ts +2 -0
- package/src/providers/feishu_app_bot/trigger-on-event.definition.d.ts +4 -0
- package/src/providers/feishu_app_bot/trigger-on-event.definition.js +63 -0
- package/src/providers/feishu_app_bot/trigger-on-event.js +29 -0
- package/src/providers/feishu_app_bot/trigger-subscriptions.d.ts +5 -0
- package/src/providers/feishu_app_bot/trigger-subscriptions.js +70 -0
- package/src/providers/github/executors.d.ts +3 -0
- package/src/providers/github/executors.js +6 -0
- package/src/providers/github/trigger-on-repo-event.d.ts +2 -0
- package/src/providers/github/trigger-on-repo-event.definition.d.ts +4 -0
- package/src/providers/github/trigger-on-repo-event.definition.js +94 -0
- package/src/providers/github/trigger-on-repo-event.js +168 -0
- package/src/providers/github/trigger-watch-pull-request.d.ts +2 -0
- package/src/providers/github/trigger-watch-pull-request.definition.d.ts +4 -0
- package/src/providers/github/trigger-watch-pull-request.definition.js +49 -0
- package/src/providers/github/trigger-watch-pull-request.js +106 -0
- package/src/providers/gitlab/executors.d.ts +3 -0
- package/src/providers/gitlab/executors.js +2 -0
- package/src/providers/gitlab/trigger-on-project-event.d.ts +2 -0
- package/src/providers/gitlab/trigger-on-project-event.definition.d.ts +5 -0
- package/src/providers/gitlab/trigger-on-project-event.definition.js +63 -0
- package/src/providers/gitlab/trigger-on-project-event.js +186 -0
- package/src/providers/gmail/executors.d.ts +3 -0
- package/src/providers/gmail/executors.js +2 -0
- package/src/providers/gmail/trigger-on-message-received.d.ts +2 -0
- package/src/providers/gmail/trigger-on-message-received.definition.d.ts +4 -0
- package/src/providers/gmail/trigger-on-message-received.definition.js +74 -0
- package/src/providers/gmail/trigger-on-message-received.js +232 -0
- package/src/providers/googlecalendar/executors.d.ts +3 -0
- package/src/providers/googlecalendar/executors.js +2 -0
- package/src/providers/googlecalendar/trigger-on-event-changed.d.ts +2 -0
- package/src/providers/googlecalendar/trigger-on-event-changed.definition.d.ts +4 -0
- package/src/providers/googlecalendar/trigger-on-event-changed.definition.js +74 -0
- package/src/providers/googlecalendar/trigger-on-event-changed.js +233 -0
- package/src/providers/googledrive/executors.d.ts +3 -0
- package/src/providers/googledrive/executors.js +7 -0
- package/src/providers/googledrive/trigger-changes.d.ts +12 -0
- package/src/providers/googledrive/trigger-changes.definition.d.ts +7 -0
- package/src/providers/googledrive/trigger-changes.definition.js +101 -0
- package/src/providers/googledrive/trigger-changes.js +452 -0
- package/src/providers/googledrive/trigger-on-file-change.d.ts +2 -0
- package/src/providers/googledrive/trigger-on-file-change.definition.d.ts +4 -0
- package/src/providers/googledrive/trigger-on-file-change.definition.js +77 -0
- package/src/providers/googledrive/trigger-on-file-change.js +258 -0
- package/src/providers/googlesheets/executors.d.ts +3 -0
- package/src/providers/googlesheets/executors.js +2 -0
- package/src/providers/googlesheets/trigger-on-row-added.d.ts +2 -0
- package/src/providers/googlesheets/trigger-on-row-added.definition.d.ts +4 -0
- package/src/providers/googlesheets/trigger-on-row-added.definition.js +54 -0
- package/src/providers/googlesheets/trigger-on-row-added.js +234 -0
- package/src/providers/hithink_finance/runtime.js +1 -1
- package/src/providers/indexed/executors.d.ts +4 -0
- package/src/providers/indexed/executors.js +16 -0
- package/src/providers/indexed/runtime.d.ts +26 -0
- package/src/providers/indexed/runtime.js +129 -0
- package/src/providers/linear/executors.d.ts +3 -0
- package/src/providers/linear/executors.js +2 -0
- package/src/providers/linear/trigger-on-issue-changed.d.ts +2 -0
- package/src/providers/linear/trigger-on-issue-changed.definition.d.ts +5 -0
- package/src/providers/linear/trigger-on-issue-changed.definition.js +43 -0
- package/src/providers/linear/trigger-on-issue-changed.js +215 -0
- package/src/providers/notion/executors.d.ts +3 -0
- package/src/providers/notion/executors.js +2 -0
- package/src/providers/notion/trigger-on-database-page-event.d.ts +2 -0
- package/src/providers/notion/trigger-on-database-page-event.definition.d.ts +4 -0
- package/src/providers/notion/trigger-on-database-page-event.definition.js +63 -0
- package/src/providers/notion/trigger-on-database-page-event.js +293 -0
- package/src/providers/one_drive/executors.d.ts +3 -0
- package/src/providers/one_drive/executors.js +2 -0
- package/src/providers/one_drive/trigger-on-item-changed.d.ts +2 -0
- package/src/providers/one_drive/trigger-on-item-changed.definition.d.ts +4 -0
- package/src/providers/one_drive/trigger-on-item-changed.definition.js +63 -0
- package/src/providers/one_drive/trigger-on-item-changed.js +214 -0
- package/src/providers/outlook/executors.d.ts +3 -0
- package/src/providers/outlook/executors.js +2 -0
- package/src/providers/outlook/trigger-on-message-received.d.ts +2 -0
- package/src/providers/outlook/trigger-on-message-received.definition.d.ts +4 -0
- package/src/providers/outlook/trigger-on-message-received.definition.js +93 -0
- package/src/providers/outlook/trigger-on-message-received.js +226 -0
- package/src/providers/outlook_calendar/executors.js +6 -0
- package/src/providers/provider-loader.d.ts +5 -0
- package/src/providers/provider-loader.js +4 -0
- package/src/providers/registry.generated.js +2 -0
- package/src/providers/shopify/executors.d.ts +3 -0
- package/src/providers/shopify/executors.js +2 -0
- package/src/providers/shopify/trigger-on-shop-event.d.ts +2 -0
- package/src/providers/shopify/trigger-on-shop-event.definition.d.ts +4 -0
- package/src/providers/shopify/trigger-on-shop-event.definition.js +214 -0
- package/src/providers/shopify/trigger-on-shop-event.js +165 -0
- package/src/providers/slack/executors.d.ts +3 -0
- package/src/providers/slack/executors.js +2 -0
- package/src/providers/slack/trigger-on-message-posted.d.ts +2 -0
- package/src/providers/slack/trigger-on-message-posted.definition.d.ts +4 -0
- package/src/providers/slack/trigger-on-message-posted.definition.js +80 -0
- package/src/providers/slack/trigger-on-message-posted.js +176 -0
- package/src/providers/spotify/runtime.js +15 -4
- package/src/providers/stripe/executors.d.ts +3 -0
- package/src/providers/stripe/executors.js +2 -0
- package/src/providers/stripe/trigger-on-event.d.ts +2 -0
- package/src/providers/stripe/trigger-on-event.definition.d.ts +4 -0
- package/src/providers/stripe/trigger-on-event.definition.js +43 -0
- package/src/providers/stripe/trigger-on-event.js +212 -0
- package/src/providers/telegram/executors.d.ts +3 -0
- package/src/providers/telegram/executors.js +2 -0
- package/src/providers/telegram/trigger-on-update.d.ts +2 -0
- package/src/providers/telegram/trigger-on-update.definition.d.ts +5 -0
- package/src/providers/telegram/trigger-on-update.definition.js +78 -0
- package/src/providers/telegram/trigger-on-update.js +141 -0
- package/src/providers/weixin_store/access-token.d.ts +2 -1
- package/src/providers/weixin_store/access-token.js +15 -4
- package/src/providers/weixin_store/executors.js +97 -12
- package/src/providers/woocommerce/executors.d.ts +3 -0
- package/src/providers/woocommerce/executors.js +2 -0
- package/src/providers/woocommerce/trigger-on-store-event.d.ts +2 -0
- package/src/providers/woocommerce/trigger-on-store-event.definition.d.ts +4 -0
- package/src/providers/woocommerce/trigger-on-store-event.definition.js +55 -0
- package/src/providers/woocommerce/trigger-on-store-event.js +179 -0
- package/src/providers/zendesk/executors.d.ts +3 -0
- package/src/providers/zendesk/executors.js +2 -0
- package/src/providers/zendesk/trigger-on-event.d.ts +2 -0
- package/src/providers/zendesk/trigger-on-event.definition.d.ts +5 -0
- package/src/providers/zendesk/trigger-on-event.definition.js +101 -0
- package/src/providers/zendesk/trigger-on-event.js +188 -0
- package/src/saas/saas-cleanup-service.d.ts +26 -0
- package/src/saas/saas-cleanup-service.js +124 -0
- package/src/saas/saas-client.d.ts +86 -0
- package/src/saas/saas-client.js +369 -0
- package/src/saas/saas-execution-service.d.ts +15 -0
- package/src/saas/saas-execution-service.js +21 -0
- package/src/saas/saas-oauth-service.d.ts +25 -0
- package/src/saas/saas-oauth-service.js +197 -0
- package/src/saas/saas-project-service.d.ts +61 -0
- package/src/saas/saas-project-service.js +211 -0
- package/src/server/actions/action-idempotency.d.ts +1 -0
- package/src/server/actions/action-idempotency.js +1 -0
- package/src/server/actions/action-runner.d.ts +7 -0
- package/src/server/actions/action-runner.js +43 -7
- package/src/server/actions/run-log-summary.js +9 -0
- package/src/server/api/auth.d.ts +1 -0
- package/src/server/api/auth.js +13 -2
- package/src/server/api/cache-policy.js +4 -1
- package/src/server/api/connection-input.d.ts +7 -1
- package/src/server/api/connection-input.js +7 -1
- package/src/server/api/connection-routes.d.ts +3 -1
- package/src/server/api/connection-routes.js +21 -3
- package/src/server/api/http-utils.d.ts +4 -3
- package/src/server/api/oauth-completion-page.d.ts +124 -0
- package/src/server/api/oauth-completion-page.js +113 -18
- package/src/server/api/openapi.js +112 -14
- package/src/server/api/policy-input.js +6 -0
- package/src/server/api/runtime-api.d.ts +10 -2
- package/src/server/api/runtime-api.js +17 -4
- package/src/server/api/saas-completion-page.d.ts +2 -0
- package/src/server/api/saas-completion-page.js +103 -0
- package/src/server/connect-app.d.ts +5 -0
- package/src/server/connect-app.js +50 -0
- package/src/server/connect-server.d.ts +12 -0
- package/src/server/connect-server.js +147 -13
- package/src/server/connector-runtime.d.ts +3 -1
- package/src/server/connector-runtime.js +5 -2
- package/src/server/files/transit-files.js +16 -7
- package/src/server/proxy/proxy-runner.d.ts +5 -1
- package/src/server/proxy/proxy-runner.js +72 -26
- package/src/server/storage/connection-request-store.d.ts +41 -1
- package/src/server/storage/connection-request-store.js +245 -20
- package/src/server/storage/connection-store.d.ts +16 -0
- package/src/server/storage/connection-store.js +119 -0
- package/src/server/storage/node-runtime-database.js +3 -3
- package/src/server/storage/{postgres-migrations.d.ts → postgres/migrations.d.ts} +2 -2
- package/src/server/storage/{postgres-migrations.js → postgres/migrations.js} +1 -1
- package/src/server/storage/postgres/runtime-store.d.ts +43 -0
- package/src/server/storage/{postgres-runtime-store.js → postgres/runtime-store.js} +59 -94
- package/src/server/storage/runtime-database.d.ts +4 -0
- package/src/server/storage/runtime-sql.d.ts +1 -1
- package/src/server/storage/runtime-sql.js +2 -1
- package/src/server/storage/runtime-store.d.ts +1 -0
- package/src/server/storage/runtime-token-service.d.ts +2 -0
- package/src/server/storage/runtime-token-service.js +3 -0
- package/src/server/storage/saas-project-store.d.ts +77 -0
- package/src/server/storage/saas-project-store.js +273 -0
- package/src/server/storage/{sqlite-runtime-store.d.ts → sqlite/runtime-store.d.ts} +21 -25
- package/src/server/storage/{sqlite-runtime-store.js → sqlite/runtime-store.js} +100 -115
- package/src/server/storage/trigger-store.d.ts +16 -0
- package/src/server/storage/trigger-store.js +96 -0
- package/src/triggers/common/config.d.ts +2 -0
- package/src/triggers/common/config.js +17 -0
- package/src/triggers/common/configOptions.d.ts +13 -0
- package/src/triggers/common/configOptions.js +1 -0
- package/src/triggers/common/encoding.d.ts +3 -0
- package/src/triggers/common/encoding.js +19 -0
- package/src/triggers/common/integration.d.ts +115 -0
- package/src/triggers/common/integration.js +31 -0
- package/src/triggers/common/json.d.ts +6 -0
- package/src/triggers/common/json.js +91 -0
- package/src/triggers/common/poll.d.ts +41 -0
- package/src/triggers/common/poll.js +15 -0
- package/src/triggers/common/proxy.d.ts +14 -0
- package/src/triggers/common/proxy.js +1 -0
- package/src/triggers/common/types.d.ts +42 -0
- package/src/triggers/common/types.js +1 -0
- package/src/triggers/maintenance.d.ts +40 -0
- package/src/triggers/maintenance.js +120 -0
- package/src/triggers/metadata.d.ts +8 -0
- package/src/triggers/metadata.js +1 -0
- package/src/triggers/request.d.ts +33 -0
- package/src/triggers/request.js +52 -0
- package/src/triggers/resources.d.ts +12 -0
- package/src/triggers/resources.js +121 -0
- package/src/triggers/signature.d.ts +4 -0
- package/src/triggers/signature.js +48 -0
- package/src/triggers/store.d.ts +29 -0
- package/src/triggers/store.js +1 -0
- package/src/triggers/subscriptions.d.ts +22 -0
- package/src/triggers/subscriptions.js +220 -0
- package/src/triggers/trigger-runner.d.ts +34 -0
- package/src/triggers/trigger-runner.js +169 -0
- package/src/server/storage/postgres-runtime-store.d.ts +0 -39
|
@@ -0,0 +1,452 @@
|
|
|
1
|
+
import { resolveTriggerConfig } from "../../triggers/common/config.js";
|
|
2
|
+
import { IntegrationConnectionError, PermanentIntegrationError, TransientIntegrationError, } from "../../triggers/common/integration.js";
|
|
3
|
+
import { snapshot, additionalSnapshot0 } from "./trigger-changes.definition.js";
|
|
4
|
+
const channelLifetimeMs = 7 * 24 * 60 * 60 * 1000 - 60 * 60 * 1000;
|
|
5
|
+
const channelRenewalLeadMs = 60 * 60 * 1000;
|
|
6
|
+
const creatingGraceMs = 60 * 1000;
|
|
7
|
+
const retryMs = 60 * 1000;
|
|
8
|
+
const maximumUnresolvedChannels = 2;
|
|
9
|
+
const encoder = new TextEncoder();
|
|
10
|
+
const changeFields = "nextPageToken,newStartPageToken,changes(fileId,kind,changeType,removed,time,driveId,file(id,name,mimeType,webViewLink,createdTime,modifiedTime,size,driveId,parents,shared,starred,trashed))";
|
|
11
|
+
export const googleDriveChanges = {
|
|
12
|
+
initialState: { checkpoint: null, subscription: { channels: [] } },
|
|
13
|
+
snapshot,
|
|
14
|
+
async receive(context) {
|
|
15
|
+
const verified = await receiveNotification(context);
|
|
16
|
+
if (verified.outcome != "wake")
|
|
17
|
+
return verified;
|
|
18
|
+
const state = requireState(context.state);
|
|
19
|
+
const resourceState = context.header("x-goog-resource-state");
|
|
20
|
+
if (!context.admit) {
|
|
21
|
+
return context.current
|
|
22
|
+
? { body: "", contentType: "text/plain", outcome: "respond", status: 503 }
|
|
23
|
+
: { outcome: "ignored", reason: "Google Drive channel belongs to a retiring runtime." };
|
|
24
|
+
}
|
|
25
|
+
if (context.allow != null && !(await context.allow()))
|
|
26
|
+
return { outcome: "ignored", reason: "The Team cannot admit Trigger events." };
|
|
27
|
+
const page = await readGoogleDriveChanges(context, state.checkpoint);
|
|
28
|
+
const notification = {
|
|
29
|
+
changedTypes: (context.header("x-goog-changed") ?? "")
|
|
30
|
+
.split(",")
|
|
31
|
+
.map((value) => value.trim())
|
|
32
|
+
.filter((value) => value.length > 0),
|
|
33
|
+
messageNumber: context.header("x-goog-message-number") ?? null,
|
|
34
|
+
resourceState,
|
|
35
|
+
resourceUri: context.header("x-goog-resource-uri") ?? null,
|
|
36
|
+
};
|
|
37
|
+
const events = page.changes.map((change) => normalizeChange(change, notification));
|
|
38
|
+
const delivery = {
|
|
39
|
+
checkpoint: page.checkpoint,
|
|
40
|
+
continue: page.hasMore,
|
|
41
|
+
dedupeKey: page.dedupeKey,
|
|
42
|
+
};
|
|
43
|
+
return events.length == 0
|
|
44
|
+
? { ...delivery, outcome: "ignored", reason: "Google Drive change page is empty." }
|
|
45
|
+
: { ...delivery, outcome: "event", outputs: { events } };
|
|
46
|
+
},
|
|
47
|
+
async reconcile(context) {
|
|
48
|
+
const state = requireState(context.state);
|
|
49
|
+
let channels = readChannels(state);
|
|
50
|
+
const currentTime = context.now.getTime();
|
|
51
|
+
const current = channels.filter((channel) => Date.parse(channel.expiration) > currentTime);
|
|
52
|
+
if (current.length != channels.length) {
|
|
53
|
+
channels = current;
|
|
54
|
+
await saveChannels(state, channels, context.now);
|
|
55
|
+
}
|
|
56
|
+
if (!context.active)
|
|
57
|
+
return await retire(context, channels);
|
|
58
|
+
const adopted = channels
|
|
59
|
+
.filter((channel) => channel.state == "creating" && channel.resourceId != null)
|
|
60
|
+
.toSorted((left, right) => Date.parse(right.createdAt) - Date.parse(left.createdAt))[0];
|
|
61
|
+
if (adopted != null) {
|
|
62
|
+
channels = channels.map((channel) => ({
|
|
63
|
+
...channel,
|
|
64
|
+
state: channel.id == adopted.id ? "active" : "retiring",
|
|
65
|
+
}));
|
|
66
|
+
await saveChannels(state, channels, context.now);
|
|
67
|
+
}
|
|
68
|
+
let active = channels.find((channel) => channel.state == "active");
|
|
69
|
+
const creating = channels.filter((channel) => channel.state == "creating");
|
|
70
|
+
const staleCreating = creating.filter((channel) => Date.parse(channel.createdAt) + creatingGraceMs <= currentTime);
|
|
71
|
+
if (staleCreating.length > 0) {
|
|
72
|
+
const staleIds = new Set(staleCreating.map((channel) => channel.id));
|
|
73
|
+
channels = channels.map((channel) => (staleIds.has(channel.id) ? { ...channel, state: "retiring" } : channel));
|
|
74
|
+
await saveChannels(state, channels, context.now);
|
|
75
|
+
}
|
|
76
|
+
active = channels.find((channel) => channel.state == "active");
|
|
77
|
+
if (active != null) {
|
|
78
|
+
try {
|
|
79
|
+
channels = await stopRetiring(context, channels);
|
|
80
|
+
}
|
|
81
|
+
catch {
|
|
82
|
+
return { outcome: "ready" };
|
|
83
|
+
}
|
|
84
|
+
active = channels.find((channel) => channel.state == "active");
|
|
85
|
+
if (active != null && Date.parse(active.expiration) - channelRenewalLeadMs > currentTime) {
|
|
86
|
+
await saveChannels(state, channels, nextReconcileAt(channels, context.now));
|
|
87
|
+
return { outcome: "ready" };
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
if (channels.some((channel) => channel.state == "creating")) {
|
|
91
|
+
await saveChannels(state, channels, nextReconcileAt(channels, context.now));
|
|
92
|
+
return { outcome: active == null ? "pending" : "ready" };
|
|
93
|
+
}
|
|
94
|
+
const unresolved = channels.filter((channel) => channel.resourceId == null);
|
|
95
|
+
if (unresolved.length >= maximumUnresolvedChannels) {
|
|
96
|
+
await saveChannels(state, channels, nextReconcileAt(channels, context.now));
|
|
97
|
+
return { outcome: active == null ? "pending" : "ready" };
|
|
98
|
+
}
|
|
99
|
+
return await watch(context, channels);
|
|
100
|
+
},
|
|
101
|
+
};
|
|
102
|
+
function requireState(state) {
|
|
103
|
+
if (state == null)
|
|
104
|
+
throw new PermanentIntegrationError("Google Drive Integration state is missing.");
|
|
105
|
+
return state;
|
|
106
|
+
}
|
|
107
|
+
function resolveConfig(config) {
|
|
108
|
+
const value = resolveTriggerConfig(snapshot.configInputs, config);
|
|
109
|
+
return {
|
|
110
|
+
...(typeof value.driveId == "string" ? { driveId: value.driveId } : {}),
|
|
111
|
+
includeCorpusRemovals: value.includeCorpusRemovals,
|
|
112
|
+
includeItemsFromAllDrives: value.includeItemsFromAllDrives,
|
|
113
|
+
includeRemoved: value.includeRemoved,
|
|
114
|
+
pageSize: value.pageSize,
|
|
115
|
+
restrictToMyDrive: value.restrictToMyDrive,
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
function checkpoint(value) {
|
|
119
|
+
const pageToken = isRecord(value) ? optionalString(value.pageToken) : undefined;
|
|
120
|
+
if (pageToken == null)
|
|
121
|
+
throw new PermanentIntegrationError("Google Drive checkpoint pageToken is missing.");
|
|
122
|
+
return pageToken;
|
|
123
|
+
}
|
|
124
|
+
function readChannels(state) {
|
|
125
|
+
const value = state.subscription.channels;
|
|
126
|
+
if (!Array.isArray(value))
|
|
127
|
+
throw new PermanentIntegrationError("Google Drive channel state is invalid.");
|
|
128
|
+
const channels = value.map((item) => {
|
|
129
|
+
if (item == null || typeof item != "object" || Array.isArray(item))
|
|
130
|
+
throw new PermanentIntegrationError("Google Drive channel is invalid.");
|
|
131
|
+
const channel = item;
|
|
132
|
+
if (typeof channel.createdAt != "string" ||
|
|
133
|
+
!Number.isFinite(Date.parse(channel.createdAt)) ||
|
|
134
|
+
typeof channel.expiration != "string" ||
|
|
135
|
+
!Number.isFinite(Date.parse(channel.expiration)) ||
|
|
136
|
+
typeof channel.id != "string" ||
|
|
137
|
+
!["active", "creating", "retiring", "stopping"].includes(String(channel.state)) ||
|
|
138
|
+
(channel.resourceId != null && typeof channel.resourceId != "string")) {
|
|
139
|
+
throw new PermanentIntegrationError("Google Drive channel fields are invalid.");
|
|
140
|
+
}
|
|
141
|
+
const parsed = {
|
|
142
|
+
createdAt: channel.createdAt,
|
|
143
|
+
expiration: channel.expiration,
|
|
144
|
+
id: channel.id,
|
|
145
|
+
state: channel.state,
|
|
146
|
+
};
|
|
147
|
+
return typeof channel.resourceId == "string" ? Object.assign(parsed, { resourceId: channel.resourceId }) : parsed;
|
|
148
|
+
});
|
|
149
|
+
if (new Set(channels.map((channel) => channel.id)).size != channels.length) {
|
|
150
|
+
throw new PermanentIntegrationError("Google Drive channel IDs are duplicated.");
|
|
151
|
+
}
|
|
152
|
+
return channels;
|
|
153
|
+
}
|
|
154
|
+
async function watch(context, channels) {
|
|
155
|
+
const state = requireState(context.state);
|
|
156
|
+
const config = resolveConfig(context.config);
|
|
157
|
+
let pageToken;
|
|
158
|
+
if (state.checkpoint === null) {
|
|
159
|
+
pageToken = await googleDriveStartPageToken(context);
|
|
160
|
+
await state.saveCheckpoint({ pageToken });
|
|
161
|
+
}
|
|
162
|
+
else {
|
|
163
|
+
pageToken = checkpoint(state.checkpoint);
|
|
164
|
+
}
|
|
165
|
+
const channel = {
|
|
166
|
+
createdAt: context.now.toISOString(),
|
|
167
|
+
expiration: new Date(context.now.getTime() + channelLifetimeMs).toISOString(),
|
|
168
|
+
id: crypto.randomUUID(),
|
|
169
|
+
state: "creating",
|
|
170
|
+
};
|
|
171
|
+
channels = [...channels, channel];
|
|
172
|
+
await saveChannels(state, channels, new Date(context.now.getTime() + creatingGraceMs));
|
|
173
|
+
let response;
|
|
174
|
+
try {
|
|
175
|
+
response = await execute(context, {
|
|
176
|
+
body: {
|
|
177
|
+
address: context.endpointUrl,
|
|
178
|
+
expiration: String(Date.parse(channel.expiration)),
|
|
179
|
+
id: channel.id,
|
|
180
|
+
token: await channelToken(context.callbackSecret, channel.id),
|
|
181
|
+
type: "web_hook",
|
|
182
|
+
},
|
|
183
|
+
endpoint: "/drive/v3/changes/watch",
|
|
184
|
+
method: "POST",
|
|
185
|
+
query: {
|
|
186
|
+
...(config.driveId == null ? {} : { driveId: config.driveId }),
|
|
187
|
+
includeItemsFromAllDrives: config.driveId == null ? config.includeItemsFromAllDrives : true,
|
|
188
|
+
pageToken,
|
|
189
|
+
restrictToMyDrive: config.restrictToMyDrive,
|
|
190
|
+
supportsAllDrives: true,
|
|
191
|
+
},
|
|
192
|
+
});
|
|
193
|
+
}
|
|
194
|
+
catch (error) {
|
|
195
|
+
if (error instanceof IntegrationConnectionError || error instanceof PermanentIntegrationError) {
|
|
196
|
+
await saveChannels(state, channels.filter((candidate) => candidate.id != channel.id), context.now);
|
|
197
|
+
}
|
|
198
|
+
throw error;
|
|
199
|
+
}
|
|
200
|
+
if (!(response.status >= 200 && response.status < 300 && isRecord(response.data))) {
|
|
201
|
+
if (response.status >= 400 && response.status < 500 && response.status !== 408)
|
|
202
|
+
await saveChannels(state, channels.filter((candidate) => candidate.id !== channel.id), context.now);
|
|
203
|
+
failure(response, "changes.watch");
|
|
204
|
+
}
|
|
205
|
+
const result = response.data;
|
|
206
|
+
const resourceId = optionalString(result.resourceId);
|
|
207
|
+
if (resourceId == null)
|
|
208
|
+
throw new TransientIntegrationError("Google Drive changes.watch response is missing resourceId.");
|
|
209
|
+
const expiration = epoch(result.expiration) ?? channel.expiration;
|
|
210
|
+
channels = channels.map((candidate) => {
|
|
211
|
+
if (candidate.id == channel.id)
|
|
212
|
+
return { ...candidate, expiration, resourceId, state: "active" };
|
|
213
|
+
return candidate.state == "active" ? { ...candidate, state: "retiring" } : candidate;
|
|
214
|
+
});
|
|
215
|
+
await saveChannels(state, channels, nextReconcileAt(channels, context.now));
|
|
216
|
+
try {
|
|
217
|
+
channels = await stopRetiring(context, channels);
|
|
218
|
+
}
|
|
219
|
+
catch {
|
|
220
|
+
return { outcome: "ready" };
|
|
221
|
+
}
|
|
222
|
+
await saveChannels(state, channels, nextReconcileAt(channels, context.now));
|
|
223
|
+
return { outcome: "ready" };
|
|
224
|
+
}
|
|
225
|
+
async function retire(context, channels) {
|
|
226
|
+
const state = requireState(context.state);
|
|
227
|
+
if (channels.some((channel) => channel.state == "active" || channel.state == "creating")) {
|
|
228
|
+
channels = channels.map((channel) => ({ ...channel, state: "retiring" }));
|
|
229
|
+
await saveChannels(state, channels, context.now);
|
|
230
|
+
}
|
|
231
|
+
channels = await stopRetiring(context, channels);
|
|
232
|
+
if (channels.length == 0)
|
|
233
|
+
return { outcome: "ready" };
|
|
234
|
+
await saveChannels(state, channels, nextReconcileAt(channels, context.now));
|
|
235
|
+
return { outcome: "pending" };
|
|
236
|
+
}
|
|
237
|
+
async function stopRetiring(context, channels) {
|
|
238
|
+
const state = requireState(context.state);
|
|
239
|
+
for (const channel of channels) {
|
|
240
|
+
if ((channel.state != "retiring" && channel.state != "stopping") || channel.resourceId == null)
|
|
241
|
+
continue;
|
|
242
|
+
const stopping = channels.map((candidate) => candidate.id == channel.id ? { ...candidate, state: "stopping" } : candidate);
|
|
243
|
+
await saveChannels(state, stopping, new Date(context.now.getTime() + retryMs));
|
|
244
|
+
await stopChannel(context, channel);
|
|
245
|
+
channels = stopping.filter((candidate) => candidate.id != channel.id);
|
|
246
|
+
await saveChannels(state, channels, context.now);
|
|
247
|
+
}
|
|
248
|
+
return channels;
|
|
249
|
+
}
|
|
250
|
+
export async function googleDriveStartPageToken(context) {
|
|
251
|
+
const config = resolveConfig(context.config);
|
|
252
|
+
const result = await request(context, {
|
|
253
|
+
endpoint: "/drive/v3/changes/startPageToken",
|
|
254
|
+
method: "GET",
|
|
255
|
+
query: {
|
|
256
|
+
...(config.driveId == null ? {} : { driveId: config.driveId }),
|
|
257
|
+
supportsAllDrives: true,
|
|
258
|
+
},
|
|
259
|
+
}, "changes.getStartPageToken");
|
|
260
|
+
const token = isRecord(result) ? optionalString(result.startPageToken) : undefined;
|
|
261
|
+
if (token == null)
|
|
262
|
+
throw new TransientIntegrationError("Google Drive startPageToken response is invalid.");
|
|
263
|
+
return token;
|
|
264
|
+
}
|
|
265
|
+
async function readGoogleDriveChanges(context, cursor) {
|
|
266
|
+
const config = resolveConfig(context.config);
|
|
267
|
+
const pageToken = checkpoint(cursor);
|
|
268
|
+
const result = await request(context, {
|
|
269
|
+
endpoint: "/drive/v3/changes",
|
|
270
|
+
method: "GET",
|
|
271
|
+
query: {
|
|
272
|
+
...(config.driveId == null ? {} : { driveId: config.driveId }),
|
|
273
|
+
fields: changeFields,
|
|
274
|
+
includeCorpusRemovals: config.includeCorpusRemovals,
|
|
275
|
+
includeItemsFromAllDrives: config.driveId == null ? config.includeItemsFromAllDrives : true,
|
|
276
|
+
includeRemoved: config.includeCorpusRemovals || config.includeRemoved,
|
|
277
|
+
pageSize: config.pageSize,
|
|
278
|
+
pageToken,
|
|
279
|
+
restrictToMyDrive: config.restrictToMyDrive,
|
|
280
|
+
supportsAllDrives: true,
|
|
281
|
+
},
|
|
282
|
+
}, "changes.list");
|
|
283
|
+
for (const field of ["nextPageToken", "newStartPageToken"]) {
|
|
284
|
+
if (result[field] !== undefined && optionalString(result[field]) == null) {
|
|
285
|
+
throw new TransientIntegrationError(`Google Drive changes response contains an invalid ${field}.`);
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
const nextPageToken = optionalString(result.nextPageToken);
|
|
289
|
+
const nextCheckpoint = nextPageToken ?? optionalString(result.newStartPageToken);
|
|
290
|
+
if (nextCheckpoint == null)
|
|
291
|
+
throw new TransientIntegrationError("Google Drive changes response is missing its next checkpoint.");
|
|
292
|
+
if (result.changes != null && (!Array.isArray(result.changes) || !result.changes.every(isRecord))) {
|
|
293
|
+
throw new TransientIntegrationError("Google Drive changes response contains invalid changes.");
|
|
294
|
+
}
|
|
295
|
+
if (nextPageToken === pageToken)
|
|
296
|
+
throw new TransientIntegrationError("Google Drive changes continuation did not advance.");
|
|
297
|
+
return {
|
|
298
|
+
changes: (result.changes ?? []),
|
|
299
|
+
checkpoint: { pageToken: nextCheckpoint },
|
|
300
|
+
dedupeKey: `${config.driveId ?? "my-drive"}:${pageToken}`,
|
|
301
|
+
hasMore: nextPageToken != null,
|
|
302
|
+
};
|
|
303
|
+
}
|
|
304
|
+
async function stopChannel(context, channel) {
|
|
305
|
+
const result = await execute(context, {
|
|
306
|
+
body: { id: channel.id, resourceId: channel.resourceId },
|
|
307
|
+
endpoint: "/drive/v3/channels/stop",
|
|
308
|
+
method: "POST",
|
|
309
|
+
});
|
|
310
|
+
if ((result.status >= 200 && result.status < 300) || result.status == 404)
|
|
311
|
+
return;
|
|
312
|
+
failure(result, "channels.stop");
|
|
313
|
+
}
|
|
314
|
+
async function request(context, input, operation) {
|
|
315
|
+
const result = await execute(context, input);
|
|
316
|
+
if (result.status >= 200 && result.status < 300 && isRecord(result.data))
|
|
317
|
+
return result.data;
|
|
318
|
+
failure(result, operation);
|
|
319
|
+
}
|
|
320
|
+
async function execute(context, input) {
|
|
321
|
+
try {
|
|
322
|
+
return await context.connector.execute(input, context.signal);
|
|
323
|
+
}
|
|
324
|
+
catch (cause) {
|
|
325
|
+
if (cause instanceof IntegrationConnectionError)
|
|
326
|
+
throw cause;
|
|
327
|
+
throw new TransientIntegrationError("Google Drive proxy request failed.", { cause });
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
function failure(result, operation) {
|
|
331
|
+
const reason = googleReason(result.data);
|
|
332
|
+
if (result.status == 401 || (result.status == 403 && !reason.toLowerCase().includes("ratelimit"))) {
|
|
333
|
+
throw new IntegrationConnectionError(`Google Drive ${operation} requires Connection reauthorization.`);
|
|
334
|
+
}
|
|
335
|
+
if (result.status == 408 || result.status == 429 || result.status >= 500) {
|
|
336
|
+
throw new TransientIntegrationError(`Google Drive ${operation} is temporarily unavailable.`);
|
|
337
|
+
}
|
|
338
|
+
throw new PermanentIntegrationError(`Google Drive ${operation} rejected the subscription${reason == "" ? "." : ` with ${reason}.`}`);
|
|
339
|
+
}
|
|
340
|
+
function googleReason(value) {
|
|
341
|
+
if (!isRecord(value) || !isRecord(value.error))
|
|
342
|
+
return "";
|
|
343
|
+
const errors = value.error.errors;
|
|
344
|
+
if (Array.isArray(errors) && isRecord(errors[0]) && typeof errors[0].reason == "string")
|
|
345
|
+
return errors[0].reason;
|
|
346
|
+
return typeof value.error.status == "string" ? value.error.status : "";
|
|
347
|
+
}
|
|
348
|
+
function normalizeChange(change, notification) {
|
|
349
|
+
const driveId = optionalString(change.driveId);
|
|
350
|
+
const fileId = optionalString(change.fileId);
|
|
351
|
+
const time = optionalString(change.time);
|
|
352
|
+
const changeType = optionalString(change.changeType);
|
|
353
|
+
const removed = change.removed === true;
|
|
354
|
+
return {
|
|
355
|
+
changeId: [
|
|
356
|
+
driveId ?? "my-drive",
|
|
357
|
+
fileId ?? "unknown-file",
|
|
358
|
+
time ?? "unknown-time",
|
|
359
|
+
changeType ?? "unknown-change",
|
|
360
|
+
removed ? "removed" : "present",
|
|
361
|
+
].join(":"),
|
|
362
|
+
changeType: changeType ?? null,
|
|
363
|
+
driveId: driveId ?? null,
|
|
364
|
+
file: isRecord(change.file) ? change.file : null,
|
|
365
|
+
fileId: fileId ?? null,
|
|
366
|
+
notification,
|
|
367
|
+
removed,
|
|
368
|
+
time: time ?? null,
|
|
369
|
+
};
|
|
370
|
+
}
|
|
371
|
+
async function saveChannels(state, channels, reconcileAt) {
|
|
372
|
+
await state.saveSubscription({ channels: channels }, reconcileAt);
|
|
373
|
+
}
|
|
374
|
+
function nextReconcileAt(channels, now) {
|
|
375
|
+
const times = channels.map((channel) => {
|
|
376
|
+
if (channel.state == "active")
|
|
377
|
+
return Date.parse(channel.expiration) - channelRenewalLeadMs;
|
|
378
|
+
if (channel.state == "creating")
|
|
379
|
+
return Date.parse(channel.createdAt) + creatingGraceMs;
|
|
380
|
+
return channel.resourceId == null ? Date.parse(channel.expiration) : now.getTime() + retryMs;
|
|
381
|
+
});
|
|
382
|
+
const next = times.length == 0 ? now.getTime() + retryMs : Math.min(...times);
|
|
383
|
+
return new Date(Math.max(now.getTime(), next));
|
|
384
|
+
}
|
|
385
|
+
async function channelToken(secret, channelId) {
|
|
386
|
+
const key = await crypto.subtle.importKey("raw", encoder.encode(secret), { hash: "SHA-256", name: "HMAC" }, false, [
|
|
387
|
+
"sign",
|
|
388
|
+
]);
|
|
389
|
+
const signature = new Uint8Array(await crypto.subtle.sign("HMAC", key, encoder.encode(`google-drive-channel/v1/${channelId}`)));
|
|
390
|
+
return Buffer.from(signature).toString("base64").replaceAll("+", "-").replaceAll("/", "_").replaceAll("=", "");
|
|
391
|
+
}
|
|
392
|
+
function sameSecret(candidate, expected) {
|
|
393
|
+
if (candidate.length != expected.length)
|
|
394
|
+
return false;
|
|
395
|
+
let difference = 0;
|
|
396
|
+
for (let index = 0; index < expected.length; index += 1)
|
|
397
|
+
difference |= candidate.charCodeAt(index) ^ expected.charCodeAt(index);
|
|
398
|
+
return difference == 0;
|
|
399
|
+
}
|
|
400
|
+
function epoch(value) {
|
|
401
|
+
const timestamp = typeof value == "string" || typeof value == "number" ? Number(value) : Number.NaN;
|
|
402
|
+
return Number.isFinite(timestamp) && timestamp > 0 ? new Date(timestamp).toISOString() : undefined;
|
|
403
|
+
}
|
|
404
|
+
function optionalString(value) {
|
|
405
|
+
return typeof value == "string" && value.length > 0 ? value : undefined;
|
|
406
|
+
}
|
|
407
|
+
function isRecord(value) {
|
|
408
|
+
return value != null && typeof value == "object" && !Array.isArray(value);
|
|
409
|
+
}
|
|
410
|
+
async function receiveNotification(context) {
|
|
411
|
+
const state = requireState(context.state);
|
|
412
|
+
const channelId = context.header("x-goog-channel-id");
|
|
413
|
+
const resourceId = context.header("x-goog-resource-id");
|
|
414
|
+
const resourceState = context.header("x-goog-resource-state");
|
|
415
|
+
const token = context.header("x-goog-channel-token");
|
|
416
|
+
if (channelId == null || resourceId == null || resourceState == null || token == null) {
|
|
417
|
+
return { body: "", contentType: "text/plain", outcome: "respond", status: 404 };
|
|
418
|
+
}
|
|
419
|
+
let channels = readChannels(state);
|
|
420
|
+
const channel = channels.find((candidate) => candidate.id == channelId && Date.parse(candidate.expiration) > context.now.getTime());
|
|
421
|
+
if (channel == null ||
|
|
422
|
+
!sameSecret(token, await channelToken(context.callbackSecret, channelId)) ||
|
|
423
|
+
(channel.resourceId != null && channel.resourceId != resourceId)) {
|
|
424
|
+
return { body: "", contentType: "text/plain", outcome: "respond", status: 404 };
|
|
425
|
+
}
|
|
426
|
+
if (channel.resourceId == null) {
|
|
427
|
+
channels = channels.map((candidate) => (candidate.id == channelId ? { ...candidate, resourceId } : candidate));
|
|
428
|
+
await saveChannels(state, channels, context.now);
|
|
429
|
+
}
|
|
430
|
+
if (resourceState != "sync" && resourceState != "change") {
|
|
431
|
+
return { outcome: "ignored", reason: "Google Drive notification state is not actionable." };
|
|
432
|
+
}
|
|
433
|
+
return { outcome: "wake" };
|
|
434
|
+
}
|
|
435
|
+
export const googleDriveChangeListener = {
|
|
436
|
+
initialState: googleDriveChanges.initialState,
|
|
437
|
+
reconcile: googleDriveChanges.reconcile,
|
|
438
|
+
receive: receiveNotification,
|
|
439
|
+
listener: {
|
|
440
|
+
intervalMs: 300_000,
|
|
441
|
+
async read(context) {
|
|
442
|
+
const page = await readGoogleDriveChanges(context, context.checkpoint);
|
|
443
|
+
return {
|
|
444
|
+
checkpoint: page.checkpoint,
|
|
445
|
+
dedupeKey: page.dedupeKey,
|
|
446
|
+
hasMore: page.hasMore,
|
|
447
|
+
outputs: page.changes.length == 0 ? null : { events: page.changes },
|
|
448
|
+
};
|
|
449
|
+
},
|
|
450
|
+
},
|
|
451
|
+
snapshot: additionalSnapshot0,
|
|
452
|
+
};
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { jsonSchema as s } from "../../core/json-schema.js";
|
|
2
|
+
const eventSchema = s.object({
|
|
3
|
+
createdTime: { type: ["string", "null"] },
|
|
4
|
+
driveId: { type: ["string", "null"] },
|
|
5
|
+
eventType: s.stringEnum(["file_created", "file_updated", "folder_created", "folder_updated"]),
|
|
6
|
+
fileId: s.string(),
|
|
7
|
+
itemType: s.stringEnum(["file", "folder"]),
|
|
8
|
+
lastModifyingUser: { type: ["object", "null"] },
|
|
9
|
+
mimeType: s.string(),
|
|
10
|
+
modifiedTime: { type: ["string", "null"] },
|
|
11
|
+
name: s.string(),
|
|
12
|
+
parents: s.array(s.string()),
|
|
13
|
+
size: s.string(),
|
|
14
|
+
webViewLink: { type: ["string", "null"] },
|
|
15
|
+
}, {
|
|
16
|
+
additionalProperties: false,
|
|
17
|
+
required: [
|
|
18
|
+
"eventType",
|
|
19
|
+
"itemType",
|
|
20
|
+
"fileId",
|
|
21
|
+
"name",
|
|
22
|
+
"mimeType",
|
|
23
|
+
"createdTime",
|
|
24
|
+
"modifiedTime",
|
|
25
|
+
"parents",
|
|
26
|
+
"webViewLink",
|
|
27
|
+
"driveId",
|
|
28
|
+
"lastModifyingUser",
|
|
29
|
+
],
|
|
30
|
+
});
|
|
31
|
+
export const snapshot = {
|
|
32
|
+
configInputs: [
|
|
33
|
+
{
|
|
34
|
+
handle: "changeType",
|
|
35
|
+
jsonSchema: s.stringEnum(["created", "updated"]),
|
|
36
|
+
nullable: false,
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
handle: "driveId",
|
|
40
|
+
jsonSchema: s.string({ pattern: "^[A-Za-z0-9_-]{0,512}$" }),
|
|
41
|
+
nullable: false,
|
|
42
|
+
value: "",
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
handle: "folderId",
|
|
46
|
+
jsonSchema: s.string({ pattern: "^[A-Za-z0-9_-]{2,512}$" }),
|
|
47
|
+
nullable: false,
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
handle: "itemTypes",
|
|
51
|
+
jsonSchema: s.array(s.stringEnum(["file", "folder"]), { minItems: 1, uniqueItems: true }),
|
|
52
|
+
nullable: false,
|
|
53
|
+
value: ["file"],
|
|
54
|
+
},
|
|
55
|
+
{
|
|
56
|
+
handle: "maxFilesPerPoll",
|
|
57
|
+
jsonSchema: s.integer({ maximum: 200, minimum: 1 }),
|
|
58
|
+
nullable: false,
|
|
59
|
+
value: 50,
|
|
60
|
+
},
|
|
61
|
+
{
|
|
62
|
+
handle: "mimeTypes",
|
|
63
|
+
jsonSchema: s.array(s.string({ minLength: 1 })),
|
|
64
|
+
nullable: false,
|
|
65
|
+
value: [],
|
|
66
|
+
},
|
|
67
|
+
{ handle: "namePrefix", jsonSchema: s.string(), nullable: false, value: "" },
|
|
68
|
+
],
|
|
69
|
+
definitionVersion: 2,
|
|
70
|
+
description: "Polls one Google Drive folder and triggers when a file or folder directly inside it is created or updated.",
|
|
71
|
+
displayName: "File or Folder Change in a Folder",
|
|
72
|
+
key: "googledrive.on_file_change",
|
|
73
|
+
name: "on_file_change",
|
|
74
|
+
outputs: [{ handle: "events", jsonSchema: s.array(eventSchema), nullable: false }],
|
|
75
|
+
provider: "googledrive",
|
|
76
|
+
type: "poll",
|
|
77
|
+
};
|