Skip to content

Commit a94c3cd

Browse files
committed
Airtable handle de-activation
1 parent 1a1c5cc commit a94c3cd

1 file changed

Lines changed: 33 additions & 12 deletions

File tree

‎connectors/airtable/src/airtable.ts‎

Lines changed: 33 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -201,7 +201,7 @@ export class Airtable extends Connector<Airtable> {
201201
if (webhookId) {
202202
try {
203203
const api = await this.getAPI(baseId);
204-
await api.deleteWebhook(baseId, webhookId);
204+
if (api) await api.deleteWebhook(baseId, webhookId);
205205
} catch (error) {
206206
console.warn("Failed to delete Airtable webhook:", error);
207207
}
@@ -219,7 +219,9 @@ export class Airtable extends Connector<Airtable> {
219219
// ---- Detection + Backfill ----
220220

221221
async detectAndSync(baseId: string, initialSync: boolean): Promise<void> {
222+
if (!(await this.get<boolean>(`sync_enabled_${baseId}`))) return;
222223
const api = await this.getAPI(baseId);
224+
if (!api) return;
223225
const me = await api.whoami();
224226
await this.set<ViewerInfo>(`viewer_${baseId}`, {
225227
id: me.id,
@@ -328,6 +330,8 @@ export class Airtable extends Connector<Airtable> {
328330
}
329331

330332
async syncBatch(baseId: string): Promise<void> {
333+
if (!(await this.get<boolean>(`sync_enabled_${baseId}`))) return;
334+
331335
const state = await this.get<SyncState>(`sync_state_${baseId}`);
332336
if (!state) return;
333337

@@ -345,6 +349,7 @@ export class Airtable extends Connector<Airtable> {
345349

346350
const table = detected[state.tableIndex];
347351
const api = await this.getAPI(baseId);
352+
if (!api) return;
348353
const page = await api.listRecords(baseId, table.tableId, {
349354
offset: state.offset,
350355
pageSize: 50,
@@ -567,6 +572,7 @@ export class Airtable extends Connector<Airtable> {
567572
// ---- Webhooks ----
568573

569574
async setupWebhook(baseId: string): Promise<void> {
575+
if (!(await this.get<boolean>(`sync_enabled_${baseId}`))) return;
570576
try {
571577
const webhookUrl = await this.tools.network.createWebhook(
572578
{},
@@ -586,6 +592,7 @@ export class Airtable extends Connector<Airtable> {
586592
const tableIds = detected.map((d) => d.tableId);
587593

588594
const api = await this.getAPI(baseId);
595+
if (!api) return;
589596
// Airtable allows one notification scope per webhook; register one
590597
// webhook per base covering all detected tables. The
591598
// recordChangeScope filter only accepts a single table, so when we
@@ -665,6 +672,7 @@ export class Airtable extends Connector<Airtable> {
665672
if (!viewer || detected.length === 0) return;
666673

667674
const api = await this.getAPI(baseId);
675+
if (!api) return;
668676
let cursor = (await this.get<number>(`webhook_cursor_${baseId}`)) ?? 1;
669677
const tableMap = new Map(detected.map((d) => [d.tableId, d] as const));
670678

@@ -734,14 +742,16 @@ export class Airtable extends Connector<Airtable> {
734742
// ---- Reconciliation (catches comment edits/deletes webhooks miss) ----
735743

736744
async reconcileComments(baseId: string): Promise<void> {
737-
const enabled = await this.get<boolean>(`sync_enabled_${baseId}`);
738-
if (!enabled) return;
745+
// Stop the self-scheduling loop once the channel is disabled.
746+
// Returning without rescheduling lets the recurring task die out,
747+
// rather than spamming retries after teardown.
748+
if (!(await this.get<boolean>(`sync_enabled_${baseId}`))) return;
739749

740-
try {
750+
const api = await this.getAPI(baseId);
751+
if (api) {
741752
const tracked = (await this.get<TrackedRecord[]>(`tracked_records_${baseId}`)) ?? [];
742753
const detected = (await this.get<DetectedTable[]>(`task_tables_${baseId}`)) ?? [];
743754
const tableMap = new Map(detected.map((d) => [d.tableId, d] as const));
744-
const api = await this.getAPI(baseId);
745755

746756
for (const rec of tracked) {
747757
const table = tableMap.get(rec.tableId);
@@ -755,12 +765,15 @@ export class Airtable extends Connector<Airtable> {
755765
console.warn("Reconcile failed for record", rec.recordId, error);
756766
}
757767
}
758-
} finally {
759-
const cb = await this.callback(this.reconcileComments, baseId);
760-
await this.runTask(cb, {
761-
runAt: new Date(Date.now() + RECONCILE_INTERVAL_MS),
762-
});
763768
}
769+
770+
// Reschedule only while the channel is still enabled. Re-check after
771+
// the reconcile pass in case onChannelDisabled fired during the run.
772+
if (!(await this.get<boolean>(`sync_enabled_${baseId}`))) return;
773+
const cb = await this.callback(this.reconcileComments, baseId);
774+
await this.runTask(cb, {
775+
runAt: new Date(Date.now() + RECONCILE_INTERVAL_MS),
776+
});
764777
}
765778

766779
// ---- Write-backs ----
@@ -792,6 +805,7 @@ export class Airtable extends Connector<Airtable> {
792805
}
793806

794807
const api = await this.getAPI(baseId);
808+
if (!api) return;
795809
try {
796810
await api.patchRecord(baseId, tableId, recordId, update);
797811
} catch (error) {
@@ -810,6 +824,7 @@ export class Airtable extends Connector<Airtable> {
810824
if (text.length === 0) return;
811825

812826
const api = await this.getAPI(baseId);
827+
if (!api) return;
813828
try {
814829
const comment = await api.createComment(baseId, tableId, recordId, {
815830
text: this.translateMentionsOutbound(text),
@@ -834,9 +849,15 @@ export class Airtable extends Connector<Airtable> {
834849

835850
// ---- Helpers ----
836851

837-
private async getAPI(baseId: string): Promise<AirtableAPI> {
852+
/**
853+
* Returns an authenticated Airtable API client, or null when the channel
854+
* has no usable token (disconnected, revoked, or not yet refreshed).
855+
* Callers must bail silently on null rather than throwing, so scheduled
856+
* tasks don't retry-spam after the user disables the channel.
857+
*/
858+
private async getAPI(baseId: string): Promise<AirtableAPI | null> {
838859
const token = await this.tools.integrations.get(baseId);
839-
if (!token) throw new Error("No Airtable authentication token available");
860+
if (!token) return null;
840861
return new AirtableAPI(token.token);
841862
}
842863
}

0 commit comments

Comments
 (0)