runline 0.29.0 → 0.30.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/dist/auth/errors.d.ts +22 -0
- package/dist/auth/errors.js +25 -0
- package/dist/auth/oauth2.d.ts +21 -0
- package/dist/auth/oauth2.js +154 -0
- package/dist/auth/token.d.ts +11 -0
- package/dist/auth/token.js +231 -0
- package/dist/auth/types.d.ts +93 -0
- package/dist/auth/types.js +1 -0
- package/dist/commands/auth.js +26 -13
- package/dist/commands/connection.js +3 -6
- package/dist/commands/exec.js +7 -2
- package/dist/commands/init.js +9 -2
- package/dist/config/loader.d.ts +7 -13
- package/dist/config/loader.js +58 -88
- package/dist/config/store.d.ts +17 -0
- package/dist/config/store.js +85 -0
- package/dist/connections/file.d.ts +13 -0
- package/dist/connections/file.js +53 -0
- package/dist/connections/memory.d.ts +15 -0
- package/dist/connections/memory.js +70 -0
- package/dist/connections/types.d.ts +47 -0
- package/dist/connections/types.js +10 -0
- package/dist/core/engine.d.ts +5 -2
- package/dist/core/engine.js +48 -55
- package/dist/core/oauth.d.ts +20 -15
- package/dist/core/oauth.js +195 -98
- package/dist/credentials/http.d.ts +19 -0
- package/dist/credentials/http.js +109 -0
- package/dist/credentials/policy.d.ts +5 -0
- package/dist/credentials/policy.js +92 -0
- package/dist/credentials/registry.d.ts +31 -0
- package/dist/credentials/registry.js +171 -0
- package/dist/credentials/transport.d.ts +38 -0
- package/dist/credentials/transport.js +283 -0
- package/dist/credentials/types.d.ts +80 -0
- package/dist/credentials/types.js +1 -0
- package/dist/index.d.ts +12 -0
- package/dist/index.js +8 -0
- package/dist/plugin/types.d.ts +30 -17
- package/dist/plugins/_shared/credentialAdapter.js +88 -0
- package/dist/plugins/_shared/googleAuth.js +89 -103
- package/dist/plugins/_shared/googleCredentials.js +160 -0
- package/dist/plugins/_shared/microsoftAuth.js +81 -74
- package/dist/plugins/_shared/microsoftCredentials.js +90 -0
- package/dist/plugins/_shared/tokenRefresh.js +39 -0
- package/dist/plugins/bitwarden/src/index.js +13 -31
- package/dist/plugins/gmail/src/index.js +8 -44
- package/dist/plugins/googleAppsScript/src/index.js +3 -25
- package/dist/plugins/googleCalendar/src/index.js +3 -41
- package/dist/plugins/googleContacts/src/index.js +3 -36
- package/dist/plugins/googleDocs/src/index.js +5 -0
- package/dist/plugins/googleDocs/src/shared.js +2 -30
- package/dist/plugins/googleDrive/src/index.js +52 -98
- package/dist/plugins/googleSheets/src/index.js +3 -40
- package/dist/plugins/googleSlides/src/index.js +5 -39
- package/dist/plugins/googleTasks/src/index.js +3 -30
- package/dist/plugins/halopsa/src/index.js +16 -31
- package/dist/plugins/microsoftCalendar/src/index.js +2 -1
- package/dist/plugins/microsoftFiles/src/index.js +10 -24
- package/dist/plugins/microsoftMail/src/index.js +2 -1
- package/dist/plugins/paypal/src/index.js +14 -29
- package/dist/plugins/plaud/src/index.js +210 -0
- package/dist/plugins/plaud/src/shared.js +221 -0
- package/dist/plugins/salesforce/src/shared.js +17 -32
- package/dist/sdk.d.ts +13 -5
- package/dist/sdk.js +35 -29
- package/dist/utils/observer.d.ts +2 -0
- package/dist/utils/observer.js +11 -0
- package/package.json +1 -1
|
@@ -55,7 +55,7 @@
|
|
|
55
55
|
*/
|
|
56
56
|
import { createReadStream, readFileSync, statSync, writeFileSync } from "node:fs";
|
|
57
57
|
import * as t from "typebox";
|
|
58
|
-
import {
|
|
58
|
+
import { googleJsonRequest, googleResponse } from "../../_shared/googleAuth.js";
|
|
59
59
|
import { Id, NonEmptyString, RawGoogleObject, StringMap, StringOrStringArray, stringEnum, } from "../../_shared/googleSchemas.js";
|
|
60
60
|
const PermissionRole = stringEnum([
|
|
61
61
|
"owner",
|
|
@@ -105,46 +105,23 @@ const DRIVE = {
|
|
|
105
105
|
UNKNOWN: "application/vnd.google-apps.unknown",
|
|
106
106
|
};
|
|
107
107
|
// ─── Auth ────────────────────────────────────────────────────────
|
|
108
|
-
|
|
109
|
-
return
|
|
108
|
+
function driveResponse(ctx, url, init = {}) {
|
|
109
|
+
return googleResponse(ctx, "googleDrive", SCOPES, url, init);
|
|
110
110
|
}
|
|
111
111
|
// ─── Request ─────────────────────────────────────────────────────
|
|
112
112
|
const API_BASE = "https://www.googleapis.com";
|
|
113
|
+
function filePathId(id) {
|
|
114
|
+
if (!/^[a-zA-Z0-9_-]+$/.test(id))
|
|
115
|
+
throw new Error("googleDrive: invalid file ID");
|
|
116
|
+
return id;
|
|
117
|
+
}
|
|
118
|
+
function uploadSession(value) {
|
|
119
|
+
if (!value || !/^https:\/\/www\.googleapis\.com\/upload\/drive\/v3\/files(?:\/[a-zA-Z0-9_-]+)?\?/.test(value))
|
|
120
|
+
throw new Error("googleDrive: invalid resumable session Location");
|
|
121
|
+
return value;
|
|
122
|
+
}
|
|
113
123
|
async function driveRequest(ctx, method, path, body, qs) {
|
|
114
|
-
|
|
115
|
-
const url = new URL(`${API_BASE}${path}`);
|
|
116
|
-
if (qs) {
|
|
117
|
-
for (const [k, v] of Object.entries(qs)) {
|
|
118
|
-
if (v === undefined || v === null)
|
|
119
|
-
continue;
|
|
120
|
-
if (Array.isArray(v)) {
|
|
121
|
-
for (const entry of v)
|
|
122
|
-
url.searchParams.append(k, String(entry));
|
|
123
|
-
}
|
|
124
|
-
else {
|
|
125
|
-
url.searchParams.set(k, String(v));
|
|
126
|
-
}
|
|
127
|
-
}
|
|
128
|
-
}
|
|
129
|
-
const init = {
|
|
130
|
-
method,
|
|
131
|
-
headers: {
|
|
132
|
-
Authorization: `Bearer ${token}`,
|
|
133
|
-
Accept: "application/json",
|
|
134
|
-
},
|
|
135
|
-
};
|
|
136
|
-
if (body && Object.keys(body).length > 0) {
|
|
137
|
-
init.headers["Content-Type"] = "application/json";
|
|
138
|
-
init.body = JSON.stringify(body);
|
|
139
|
-
}
|
|
140
|
-
const res = await fetch(url.toString(), init);
|
|
141
|
-
if (res.status === 204)
|
|
142
|
-
return { success: true };
|
|
143
|
-
const text = await res.text();
|
|
144
|
-
if (!res.ok) {
|
|
145
|
-
throw new Error(`googleDrive: ${method} ${path} → ${res.status} ${text}`);
|
|
146
|
-
}
|
|
147
|
-
return text ? JSON.parse(text) : { success: true };
|
|
124
|
+
return googleJsonRequest(ctx, "googleDrive", SCOPES, method, `${API_BASE}${path}`, body, qs);
|
|
148
125
|
}
|
|
149
126
|
/**
|
|
150
127
|
* Paginate any Drive list endpoint by repeatedly following
|
|
@@ -233,7 +210,6 @@ function buildMultipart(metadata, content, mimeType) {
|
|
|
233
210
|
*/
|
|
234
211
|
async function uploadBytes(ctx, metadata, c, extraQs = {}) {
|
|
235
212
|
const mimeType = c.mimeType ?? "application/octet-stream";
|
|
236
|
-
const token = await accessToken(ctx);
|
|
237
213
|
if (c.buffer) {
|
|
238
214
|
const body = buildMultipart(metadata, c.buffer, mimeType);
|
|
239
215
|
const url = new URL(`${API_BASE}/upload/drive/v3/files`);
|
|
@@ -243,19 +219,17 @@ async function uploadBytes(ctx, metadata, c, extraQs = {}) {
|
|
|
243
219
|
if (v !== undefined)
|
|
244
220
|
url.searchParams.set(k, String(v));
|
|
245
221
|
}
|
|
246
|
-
const res = await
|
|
222
|
+
const res = await driveResponse(ctx, url.toString(), {
|
|
247
223
|
method: "POST",
|
|
248
224
|
headers: {
|
|
249
|
-
Authorization: `Bearer ${token}`,
|
|
250
225
|
"Content-Type": `multipart/related; boundary=${MULTIPART_BOUNDARY}`,
|
|
251
|
-
"Content-Length": String(body.byteLength),
|
|
252
226
|
},
|
|
253
227
|
// Buffer is a Uint8Array so it's a valid BodyInit.
|
|
254
228
|
body: new Uint8Array(body),
|
|
255
229
|
});
|
|
256
230
|
const text = await res.text();
|
|
257
231
|
if (!res.ok) {
|
|
258
|
-
throw new Error(`googleDrive: upload failed (${res.status})
|
|
232
|
+
throw new Error(`googleDrive: upload failed (HTTP ${res.status})`);
|
|
259
233
|
}
|
|
260
234
|
return JSON.parse(text);
|
|
261
235
|
}
|
|
@@ -267,10 +241,9 @@ async function uploadBytes(ctx, metadata, c, extraQs = {}) {
|
|
|
267
241
|
if (v !== undefined)
|
|
268
242
|
initUrl.searchParams.set(k, String(v));
|
|
269
243
|
}
|
|
270
|
-
const initRes = await
|
|
244
|
+
const initRes = await driveResponse(ctx, initUrl.toString(), {
|
|
271
245
|
method: "POST",
|
|
272
246
|
headers: {
|
|
273
|
-
Authorization: `Bearer ${token}`,
|
|
274
247
|
"Content-Type": "application/json; charset=UTF-8",
|
|
275
248
|
"X-Upload-Content-Type": mimeType,
|
|
276
249
|
"X-Upload-Content-Length": String(c.size),
|
|
@@ -278,12 +251,9 @@ async function uploadBytes(ctx, metadata, c, extraQs = {}) {
|
|
|
278
251
|
body: JSON.stringify(metadata),
|
|
279
252
|
});
|
|
280
253
|
if (!initRes.ok) {
|
|
281
|
-
|
|
282
|
-
throw new Error(`googleDrive: resumable init failed (${initRes.status}): ${t}`);
|
|
254
|
+
throw new Error(`googleDrive: resumable init failed (HTTP ${initRes.status})`);
|
|
283
255
|
}
|
|
284
|
-
const uploadUrl = initRes.headers.get("location");
|
|
285
|
-
if (!uploadUrl)
|
|
286
|
-
throw new Error("googleDrive: resumable session missing Location header");
|
|
256
|
+
const uploadUrl = uploadSession(initRes.headers.get("location"));
|
|
287
257
|
// Stream in 2 MiB chunks. Must be a multiple of 256 KiB per Drive docs.
|
|
288
258
|
const CHUNK_SIZE = 2 * 1024 * 1024;
|
|
289
259
|
const stream = createReadStream(c.path, { highWaterMark: CHUNK_SIZE });
|
|
@@ -294,10 +264,9 @@ async function uploadBytes(ctx, metadata, c, extraQs = {}) {
|
|
|
294
264
|
const flushChunk = async (chunk, isLast) => {
|
|
295
265
|
const start = offset;
|
|
296
266
|
const end = offset + chunk.byteLength - 1;
|
|
297
|
-
const res = await
|
|
267
|
+
const res = await driveResponse(ctx, uploadUrl, {
|
|
298
268
|
method: "PUT",
|
|
299
269
|
headers: {
|
|
300
|
-
"Content-Length": String(chunk.byteLength),
|
|
301
270
|
"Content-Range": `bytes ${start}-${end}/${c.size}`,
|
|
302
271
|
},
|
|
303
272
|
body: new Uint8Array(chunk),
|
|
@@ -313,8 +282,7 @@ async function uploadBytes(ctx, metadata, c, extraQs = {}) {
|
|
|
313
282
|
await res.text();
|
|
314
283
|
}
|
|
315
284
|
else {
|
|
316
|
-
|
|
317
|
-
throw new Error(`googleDrive: resumable chunk failed (${res.status}): ${t}`);
|
|
285
|
+
throw new Error(`googleDrive: resumable chunk failed (HTTP ${res.status})`);
|
|
318
286
|
}
|
|
319
287
|
void isLast;
|
|
320
288
|
};
|
|
@@ -418,6 +386,7 @@ export default function googleDrive(rl) {
|
|
|
418
386
|
],
|
|
419
387
|
});
|
|
420
388
|
rl.setConnectionSchema({
|
|
389
|
+
authMethod: { type: "string", required: false, description: "delegated or serviceAccount (legacy configs infer the method)" },
|
|
421
390
|
clientId: {
|
|
422
391
|
type: "string",
|
|
423
392
|
required: false,
|
|
@@ -554,11 +523,9 @@ export default function googleDrive(rl) {
|
|
|
554
523
|
const doc = (await driveRequest(ctx, "POST", "/drive/v3/files", metadata, {
|
|
555
524
|
supportsAllDrives: true,
|
|
556
525
|
}));
|
|
557
|
-
const
|
|
558
|
-
const res = await fetch(`https://docs.googleapis.com/v1/documents/${doc.id}:batchUpdate`, {
|
|
526
|
+
const res = await driveResponse(ctx, `https://docs.googleapis.com/v1/documents/${encodeURIComponent(doc.id)}:batchUpdate`, {
|
|
559
527
|
method: "POST",
|
|
560
528
|
headers: {
|
|
561
|
-
Authorization: `Bearer ${token}`,
|
|
562
529
|
"Content-Type": "application/json",
|
|
563
530
|
},
|
|
564
531
|
body: JSON.stringify({
|
|
@@ -573,7 +540,7 @@ export default function googleDrive(rl) {
|
|
|
573
540
|
}),
|
|
574
541
|
});
|
|
575
542
|
if (!res.ok) {
|
|
576
|
-
throw new Error(`googleDrive: docs.batchUpdate failed (${res.status})
|
|
543
|
+
throw new Error(`googleDrive: docs.batchUpdate failed (HTTP ${res.status})`);
|
|
577
544
|
}
|
|
578
545
|
return { id: doc.id };
|
|
579
546
|
}
|
|
@@ -608,7 +575,6 @@ export default function googleDrive(rl) {
|
|
|
608
575
|
const fileId = p.fileId;
|
|
609
576
|
const meta = (await driveRequest(ctx, "GET", `/drive/v3/files/${fileId}`, undefined, { fields: "mimeType,name", supportsAllDrives: true }));
|
|
610
577
|
const isGoogleNative = meta.mimeType?.includes("vnd.google-apps");
|
|
611
|
-
const token = await accessToken(ctx);
|
|
612
578
|
let url;
|
|
613
579
|
let contentType = meta.mimeType;
|
|
614
580
|
if (isGoogleNative) {
|
|
@@ -621,22 +587,20 @@ export default function googleDrive(rl) {
|
|
|
621
587
|
};
|
|
622
588
|
const mime = p.googleDocFormat ?? defaults[type] ?? "application/pdf";
|
|
623
589
|
contentType = mime;
|
|
624
|
-
const u = new URL(`${API_BASE}/drive/v3/files/${fileId}/export`);
|
|
590
|
+
const u = new URL(`${API_BASE}/drive/v3/files/${filePathId(fileId)}/export`);
|
|
625
591
|
u.searchParams.set("mimeType", mime);
|
|
626
592
|
u.searchParams.set("supportsAllDrives", "true");
|
|
627
593
|
url = u.toString();
|
|
628
594
|
}
|
|
629
595
|
else {
|
|
630
|
-
const u = new URL(`${API_BASE}/drive/v3/files/${fileId}`);
|
|
596
|
+
const u = new URL(`${API_BASE}/drive/v3/files/${filePathId(fileId)}`);
|
|
631
597
|
u.searchParams.set("alt", "media");
|
|
632
598
|
u.searchParams.set("supportsAllDrives", "true");
|
|
633
599
|
url = u.toString();
|
|
634
600
|
}
|
|
635
|
-
const res = await
|
|
636
|
-
headers: { Authorization: `Bearer ${token}` },
|
|
637
|
-
});
|
|
601
|
+
const res = await driveResponse(ctx, url);
|
|
638
602
|
if (!res.ok) {
|
|
639
|
-
throw new Error(`googleDrive: download failed (${res.status})
|
|
603
|
+
throw new Error(`googleDrive: download failed (HTTP ${res.status})`);
|
|
640
604
|
}
|
|
641
605
|
const bytes = Buffer.from(await res.arrayBuffer());
|
|
642
606
|
const fileName = meta.name;
|
|
@@ -754,9 +718,8 @@ export default function googleDrive(rl) {
|
|
|
754
718
|
if (hasBytes) {
|
|
755
719
|
const c = resolveContent(p);
|
|
756
720
|
const mimeType = c.mimeType ?? p.mimeType ?? "application/octet-stream";
|
|
757
|
-
const token = await accessToken(ctx);
|
|
758
721
|
if (c.buffer) {
|
|
759
|
-
const url = new URL(`${API_BASE}/upload/drive/v3/files/${fileId}`);
|
|
722
|
+
const url = new URL(`${API_BASE}/upload/drive/v3/files/${filePathId(fileId)}`);
|
|
760
723
|
url.searchParams.set("uploadType", "media");
|
|
761
724
|
url.searchParams.set("supportsAllDrives", "true");
|
|
762
725
|
for (const key of [
|
|
@@ -768,22 +731,20 @@ export default function googleDrive(rl) {
|
|
|
768
731
|
url.searchParams.set(key, String(p[key]));
|
|
769
732
|
}
|
|
770
733
|
}
|
|
771
|
-
const res = await
|
|
734
|
+
const res = await driveResponse(ctx, url.toString(), {
|
|
772
735
|
method: "PATCH",
|
|
773
736
|
headers: {
|
|
774
|
-
Authorization: `Bearer ${token}`,
|
|
775
737
|
"Content-Type": mimeType,
|
|
776
|
-
"Content-Length": String(c.buffer.byteLength),
|
|
777
738
|
},
|
|
778
739
|
body: new Uint8Array(c.buffer),
|
|
779
740
|
});
|
|
780
741
|
if (!res.ok) {
|
|
781
|
-
throw new Error(`googleDrive: content update failed (${res.status})
|
|
742
|
+
throw new Error(`googleDrive: content update failed (HTTP ${res.status})`);
|
|
782
743
|
}
|
|
783
744
|
}
|
|
784
745
|
else if (c.path) {
|
|
785
746
|
// Resumable PATCH
|
|
786
|
-
const initUrl = new URL(`${API_BASE}/upload/drive/v3/files/${fileId}`);
|
|
747
|
+
const initUrl = new URL(`${API_BASE}/upload/drive/v3/files/${filePathId(fileId)}`);
|
|
787
748
|
initUrl.searchParams.set("uploadType", "resumable");
|
|
788
749
|
initUrl.searchParams.set("supportsAllDrives", "true");
|
|
789
750
|
for (const key of [
|
|
@@ -795,36 +756,34 @@ export default function googleDrive(rl) {
|
|
|
795
756
|
initUrl.searchParams.set(key, String(p[key]));
|
|
796
757
|
}
|
|
797
758
|
}
|
|
798
|
-
const initRes = await
|
|
759
|
+
const initRes = await driveResponse(ctx, initUrl.toString(), {
|
|
799
760
|
method: "PATCH",
|
|
800
761
|
headers: {
|
|
801
|
-
Authorization: `Bearer ${token}`,
|
|
802
762
|
"X-Upload-Content-Type": mimeType,
|
|
803
763
|
"X-Upload-Content-Length": String(c.size),
|
|
804
764
|
},
|
|
805
765
|
});
|
|
806
766
|
if (!initRes.ok) {
|
|
807
|
-
throw new Error(`googleDrive: resumable update init failed (${initRes.status})
|
|
767
|
+
throw new Error(`googleDrive: resumable update init failed (HTTP ${initRes.status})`);
|
|
808
768
|
}
|
|
809
|
-
const uploadUrl = initRes.headers.get("location");
|
|
810
|
-
if (!uploadUrl)
|
|
811
|
-
throw new Error("googleDrive: missing Location on resumable init");
|
|
769
|
+
const uploadUrl = uploadSession(initRes.headers.get("location"));
|
|
812
770
|
const CHUNK = 2 * 1024 * 1024;
|
|
813
771
|
const stream = createReadStream(c.path, { highWaterMark: CHUNK });
|
|
814
772
|
let offset = 0;
|
|
815
773
|
let pending = Buffer.alloc(0);
|
|
774
|
+
let lastStatus = 0;
|
|
816
775
|
const flush = async (chunk) => {
|
|
817
|
-
const res = await
|
|
776
|
+
const res = await driveResponse(ctx, uploadUrl, {
|
|
818
777
|
method: "PUT",
|
|
819
778
|
headers: {
|
|
820
|
-
"Content-Length": String(chunk.byteLength),
|
|
821
779
|
"Content-Range": `bytes ${offset}-${offset + chunk.byteLength - 1}/${c.size}`,
|
|
822
780
|
},
|
|
823
781
|
body: new Uint8Array(chunk),
|
|
824
782
|
});
|
|
825
783
|
offset += chunk.byteLength;
|
|
784
|
+
lastStatus = res.status;
|
|
826
785
|
if (res.status !== 200 && res.status !== 201 && res.status !== 308) {
|
|
827
|
-
throw new Error(`googleDrive: resumable chunk failed (${res.status})
|
|
786
|
+
throw new Error(`googleDrive: resumable chunk failed (HTTP ${res.status})`);
|
|
828
787
|
}
|
|
829
788
|
await res.text();
|
|
830
789
|
};
|
|
@@ -837,6 +796,8 @@ export default function googleDrive(rl) {
|
|
|
837
796
|
}
|
|
838
797
|
if (pending.byteLength > 0)
|
|
839
798
|
await flush(pending);
|
|
799
|
+
if (offset !== c.size || (lastStatus !== 200 && lastStatus !== 201))
|
|
800
|
+
throw new Error("googleDrive: resumable update did not complete");
|
|
840
801
|
}
|
|
841
802
|
}
|
|
842
803
|
// Step 2: metadata patch
|
|
@@ -1484,14 +1445,11 @@ export default function googleDrive(rl) {
|
|
|
1484
1445
|
const p = (input ?? {});
|
|
1485
1446
|
const fileId = p.fileId;
|
|
1486
1447
|
const revisionId = p.revisionId;
|
|
1487
|
-
const
|
|
1488
|
-
const url = new URL(`${API_BASE}/drive/v3/files/${fileId}/revisions/${revisionId}`);
|
|
1448
|
+
const url = new URL(`${API_BASE}/drive/v3/files/${filePathId(fileId)}/revisions/${filePathId(revisionId)}`);
|
|
1489
1449
|
url.searchParams.set("alt", "media");
|
|
1490
|
-
const res = await
|
|
1491
|
-
headers: { Authorization: `Bearer ${token}` },
|
|
1492
|
-
});
|
|
1450
|
+
const res = await driveResponse(ctx, url.toString());
|
|
1493
1451
|
if (!res.ok) {
|
|
1494
|
-
throw new Error(`googleDrive: revision download failed (${res.status})
|
|
1452
|
+
throw new Error(`googleDrive: revision download failed (HTTP ${res.status})`);
|
|
1495
1453
|
}
|
|
1496
1454
|
const bytes = Buffer.from(await res.arrayBuffer());
|
|
1497
1455
|
const mimeType = res.headers.get("content-type") ?? "application/octet-stream";
|
|
@@ -1563,33 +1521,30 @@ export default function googleDrive(rl) {
|
|
|
1563
1521
|
const p = (input ?? {});
|
|
1564
1522
|
const fileId = p.fileId;
|
|
1565
1523
|
const revisionId = p.revisionId;
|
|
1566
|
-
const token = await accessToken(ctx);
|
|
1567
1524
|
// 1. Pull the chosen revision's bytes.
|
|
1568
|
-
const dl = await
|
|
1525
|
+
const dl = await driveResponse(ctx, `${API_BASE}/drive/v3/files/${encodeURIComponent(fileId)}/revisions/${encodeURIComponent(revisionId)}?alt=media`);
|
|
1569
1526
|
if (!dl.ok) {
|
|
1570
|
-
throw new Error(`googleDrive: revision restore download failed (${dl.status})
|
|
1527
|
+
throw new Error(`googleDrive: revision restore download failed (HTTP ${dl.status})`);
|
|
1571
1528
|
}
|
|
1572
1529
|
const bytes = Buffer.from(await dl.arrayBuffer());
|
|
1573
1530
|
const mime = p.mimeType ??
|
|
1574
1531
|
dl.headers.get("content-type") ??
|
|
1575
1532
|
"application/octet-stream";
|
|
1576
1533
|
// 2. Multipart-PATCH them as the new head of the same file.
|
|
1577
|
-
const url = new URL(`${API_BASE}/upload/drive/v3/files/${fileId}`);
|
|
1534
|
+
const url = new URL(`${API_BASE}/upload/drive/v3/files/${filePathId(fileId)}`);
|
|
1578
1535
|
url.searchParams.set("uploadType", "multipart");
|
|
1579
1536
|
url.searchParams.set("supportsAllDrives", "true");
|
|
1580
1537
|
url.searchParams.set("fields", "id,name,mimeType,modifiedTime,size,headRevisionId,webViewLink");
|
|
1581
1538
|
const body = buildMultipart({ mimeType: mime }, bytes, mime);
|
|
1582
|
-
const res = await
|
|
1539
|
+
const res = await driveResponse(ctx, url.toString(), {
|
|
1583
1540
|
method: "PATCH",
|
|
1584
1541
|
headers: {
|
|
1585
|
-
Authorization: `Bearer ${token}`,
|
|
1586
1542
|
"Content-Type": `multipart/related; boundary=${MULTIPART_BOUNDARY}`,
|
|
1587
|
-
"Content-Length": String(body.byteLength),
|
|
1588
1543
|
},
|
|
1589
1544
|
body: new Uint8Array(body),
|
|
1590
1545
|
});
|
|
1591
1546
|
if (!res.ok) {
|
|
1592
|
-
throw new Error(`googleDrive: revision restore upload failed (${res.status})
|
|
1547
|
+
throw new Error(`googleDrive: revision restore upload failed (HTTP ${res.status})`);
|
|
1593
1548
|
}
|
|
1594
1549
|
const head = (await res.json());
|
|
1595
1550
|
return { fileId, restoredFromRevisionId: revisionId, head };
|
|
@@ -1816,12 +1771,11 @@ export default function googleDrive(rl) {
|
|
|
1816
1771
|
}, { additionalProperties: false }),
|
|
1817
1772
|
async execute(input, ctx) {
|
|
1818
1773
|
const p = (input ?? {});
|
|
1819
|
-
const
|
|
1820
|
-
const u = new URL(`${API_BASE}/drive/v3/files/${p.fileId}/export`);
|
|
1774
|
+
const u = new URL(`${API_BASE}/drive/v3/files/${filePathId(String(p.fileId))}/export`);
|
|
1821
1775
|
u.searchParams.set("mimeType", p.mimeType);
|
|
1822
|
-
const res = await
|
|
1776
|
+
const res = await driveResponse(ctx, u.toString());
|
|
1823
1777
|
if (!res.ok) {
|
|
1824
|
-
throw new Error(`googleDrive: export failed (${res.status})
|
|
1778
|
+
throw new Error(`googleDrive: export failed (HTTP ${res.status})`);
|
|
1825
1779
|
}
|
|
1826
1780
|
const bytes = Buffer.from(await res.arrayBuffer());
|
|
1827
1781
|
const contentType = res.headers.get("content-type") ?? p.mimeType;
|
|
@@ -35,7 +35,7 @@
|
|
|
35
35
|
* raw rows / objects and callers post-filter.
|
|
36
36
|
*/
|
|
37
37
|
import * as t from "typebox";
|
|
38
|
-
import {
|
|
38
|
+
import { googleJsonRequest } from "../../_shared/googleAuth.js";
|
|
39
39
|
import { Id, JsonValue, NonEmptyString, NonNegativeInteger, PositiveInteger, RawGoogleObject, StringArray, stringEnum, } from "../../_shared/googleSchemas.js";
|
|
40
40
|
const ROW_NUMBER = "row_number";
|
|
41
41
|
const sheetReferenceSchema = t.Union([NonEmptyString, NonNegativeInteger]);
|
|
@@ -73,48 +73,10 @@ const initialSheetSchema = t.Object({
|
|
|
73
73
|
columnGroupControlAfter: t.Optional(t.Boolean()),
|
|
74
74
|
}, { additionalProperties: false })),
|
|
75
75
|
}, { additionalProperties: false });
|
|
76
|
-
// ─── Auth ────────────────────────────────────────────────────────
|
|
77
|
-
async function accessToken(ctx) {
|
|
78
|
-
return googleAccessToken(ctx, "googleSheets", SCOPES);
|
|
79
|
-
}
|
|
80
76
|
// ─── Request ─────────────────────────────────────────────────────
|
|
81
77
|
const SHEETS_BASE = "https://sheets.googleapis.com";
|
|
82
78
|
async function sheetsRequest(ctx, method, path, body, qs, baseOverride) {
|
|
83
|
-
|
|
84
|
-
const url = new URL(`${baseOverride ?? SHEETS_BASE}${path}`);
|
|
85
|
-
if (qs) {
|
|
86
|
-
for (const [k, v] of Object.entries(qs)) {
|
|
87
|
-
if (v === undefined || v === null)
|
|
88
|
-
continue;
|
|
89
|
-
if (Array.isArray(v)) {
|
|
90
|
-
for (const entry of v)
|
|
91
|
-
url.searchParams.append(k, String(entry));
|
|
92
|
-
}
|
|
93
|
-
else {
|
|
94
|
-
url.searchParams.set(k, String(v));
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
|
-
}
|
|
98
|
-
const init = {
|
|
99
|
-
method,
|
|
100
|
-
headers: {
|
|
101
|
-
Authorization: `Bearer ${token}`,
|
|
102
|
-
Accept: "application/json",
|
|
103
|
-
},
|
|
104
|
-
};
|
|
105
|
-
if (body && Object.keys(body).length > 0) {
|
|
106
|
-
init.headers["Content-Type"] =
|
|
107
|
-
"application/json";
|
|
108
|
-
init.body = JSON.stringify(body);
|
|
109
|
-
}
|
|
110
|
-
const res = await fetch(url.toString(), init);
|
|
111
|
-
if (res.status === 204)
|
|
112
|
-
return { success: true };
|
|
113
|
-
const text = await res.text();
|
|
114
|
-
if (!res.ok) {
|
|
115
|
-
throw new Error(`googleSheets: ${method} ${path} → ${res.status} ${text}`);
|
|
116
|
-
}
|
|
117
|
-
return text ? JSON.parse(text) : { success: true };
|
|
79
|
+
return googleJsonRequest(ctx, "googleSheets", SCOPES, method, `${baseOverride ?? SHEETS_BASE}${path}`, body, qs);
|
|
118
80
|
}
|
|
119
81
|
// ─── A1 helpers ─────────────────────────────────────────────────
|
|
120
82
|
/**
|
|
@@ -411,6 +373,7 @@ export default function googleSheets(rl) {
|
|
|
411
373
|
],
|
|
412
374
|
});
|
|
413
375
|
rl.setConnectionSchema({
|
|
376
|
+
authMethod: { type: "string", required: false, description: "delegated or serviceAccount (legacy configs infer the method)" },
|
|
414
377
|
clientId: {
|
|
415
378
|
type: "string",
|
|
416
379
|
required: false,
|
|
@@ -21,47 +21,12 @@
|
|
|
21
21
|
*/
|
|
22
22
|
import { writeFileSync } from "node:fs";
|
|
23
23
|
import * as t from "typebox";
|
|
24
|
-
import {
|
|
24
|
+
import { googleJsonRequest, googleDownload } from "../../_shared/googleAuth.js";
|
|
25
25
|
import { Id, NonEmptyString, PositiveInteger, RawGoogleObject, StringArray, stringEnum, } from "../../_shared/googleSchemas.js";
|
|
26
|
-
// ─── Auth ────────────────────────────────────────────────────────
|
|
27
|
-
async function accessToken(ctx) {
|
|
28
|
-
return googleAccessToken(ctx, "googleSlides", SCOPES);
|
|
29
|
-
}
|
|
30
26
|
// ─── Request ─────────────────────────────────────────────────────
|
|
31
27
|
const API_BASE = "https://slides.googleapis.com/v1";
|
|
32
28
|
async function slidesRequest(ctx, method, path, body, qs) {
|
|
33
|
-
|
|
34
|
-
const url = new URL(`${API_BASE}${path}`);
|
|
35
|
-
if (qs) {
|
|
36
|
-
for (const [k, v] of Object.entries(qs)) {
|
|
37
|
-
if (v === undefined || v === null)
|
|
38
|
-
continue;
|
|
39
|
-
if (Array.isArray(v)) {
|
|
40
|
-
for (const entry of v)
|
|
41
|
-
url.searchParams.append(k, String(entry));
|
|
42
|
-
}
|
|
43
|
-
else {
|
|
44
|
-
url.searchParams.set(k, String(v));
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
const init = {
|
|
49
|
-
method,
|
|
50
|
-
headers: { Authorization: `Bearer ${token}`, Accept: "application/json" },
|
|
51
|
-
};
|
|
52
|
-
if (body && Object.keys(body).length > 0) {
|
|
53
|
-
init.headers["Content-Type"] =
|
|
54
|
-
"application/json";
|
|
55
|
-
init.body = JSON.stringify(body);
|
|
56
|
-
}
|
|
57
|
-
const res = await fetch(url.toString(), init);
|
|
58
|
-
if (res.status === 204)
|
|
59
|
-
return { success: true };
|
|
60
|
-
const text = await res.text();
|
|
61
|
-
if (!res.ok) {
|
|
62
|
-
throw new Error(`googleSlides: ${method} ${path} → ${res.status} ${text}`);
|
|
63
|
-
}
|
|
64
|
-
return text ? JSON.parse(text) : { success: true };
|
|
29
|
+
return googleJsonRequest(ctx, "googleSlides", SCOPES, method, `${API_BASE}${path}`, body, qs);
|
|
65
30
|
}
|
|
66
31
|
// ─── Helpers ────────────────────────────────────────────────────
|
|
67
32
|
const PRES_URL_REGEX = /https:\/\/docs\.google\.com\/presentation\/d\/([a-zA-Z0-9-_]+)/;
|
|
@@ -112,6 +77,7 @@ export default function googleSlides(rl) {
|
|
|
112
77
|
],
|
|
113
78
|
});
|
|
114
79
|
rl.setConnectionSchema({
|
|
80
|
+
authMethod: { type: "string", required: false, description: "delegated or serviceAccount (legacy configs infer the method)" },
|
|
115
81
|
clientId: {
|
|
116
82
|
type: "string",
|
|
117
83
|
required: false,
|
|
@@ -289,9 +255,9 @@ export default function googleSlides(rl) {
|
|
|
289
255
|
if (!p.savePath && !p.download)
|
|
290
256
|
return res;
|
|
291
257
|
// contentUrl is a signed Google URL — fetch without auth headers.
|
|
292
|
-
const imgRes = await
|
|
258
|
+
const imgRes = await googleDownload(res.contentUrl);
|
|
293
259
|
if (!imgRes.ok) {
|
|
294
|
-
throw new Error(`googleSlides: thumbnail fetch failed (${imgRes.status})
|
|
260
|
+
throw new Error(`googleSlides: thumbnail fetch failed (HTTP ${imgRes.status})`);
|
|
295
261
|
}
|
|
296
262
|
const bytes = Buffer.from(await imgRes.arrayBuffer());
|
|
297
263
|
if (typeof p.savePath === "string") {
|
|
@@ -20,40 +20,12 @@
|
|
|
20
20
|
* normalize ISO input via `Date.toISOString()` when detected.
|
|
21
21
|
*/
|
|
22
22
|
import * as t from "typebox";
|
|
23
|
-
import {
|
|
23
|
+
import { googleJsonRequest } from "../../_shared/googleAuth.js";
|
|
24
24
|
import { GoogleTimestamp, Id, stringEnum, } from "../../_shared/googleSchemas.js";
|
|
25
|
-
// ─── Auth ────────────────────────────────────────────────────────
|
|
26
|
-
async function accessToken(ctx) {
|
|
27
|
-
return googleAccessToken(ctx, "googleTasks", SCOPES);
|
|
28
|
-
}
|
|
29
25
|
// ─── Request ─────────────────────────────────────────────────────
|
|
30
26
|
const API_BASE = "https://tasks.googleapis.com/tasks/v1";
|
|
31
27
|
async function tasksRequest(ctx, method, path, body, qs) {
|
|
32
|
-
|
|
33
|
-
const url = new URL(`${API_BASE}${path}`);
|
|
34
|
-
if (qs) {
|
|
35
|
-
for (const [k, v] of Object.entries(qs)) {
|
|
36
|
-
if (v === undefined || v === null)
|
|
37
|
-
continue;
|
|
38
|
-
url.searchParams.set(k, String(v));
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
const init = {
|
|
42
|
-
method,
|
|
43
|
-
headers: { Authorization: `Bearer ${token}`, Accept: "application/json" },
|
|
44
|
-
};
|
|
45
|
-
if (body && Object.keys(body).length > 0) {
|
|
46
|
-
init.headers["Content-Type"] = "application/json";
|
|
47
|
-
init.body = JSON.stringify(body);
|
|
48
|
-
}
|
|
49
|
-
const res = await fetch(url.toString(), init);
|
|
50
|
-
if (res.status === 204)
|
|
51
|
-
return { success: true };
|
|
52
|
-
const text = await res.text();
|
|
53
|
-
if (!res.ok) {
|
|
54
|
-
throw new Error(`googleTasks: ${method} ${path} → ${res.status} ${text}`);
|
|
55
|
-
}
|
|
56
|
-
return text ? JSON.parse(text) : { success: true };
|
|
28
|
+
return googleJsonRequest(ctx, "googleTasks", SCOPES, method, `${API_BASE}${path}`, body, qs);
|
|
57
29
|
}
|
|
58
30
|
async function paginateAll(ctx, path, qs) {
|
|
59
31
|
const out = [];
|
|
@@ -129,6 +101,7 @@ export default function googleTasks(rl) {
|
|
|
129
101
|
],
|
|
130
102
|
});
|
|
131
103
|
rl.setConnectionSchema({
|
|
104
|
+
authMethod: { type: "string", required: false, description: "delegated or serviceAccount (legacy configs infer the method)" },
|
|
132
105
|
clientId: { type: "string", required: false, env: "GOOGLE_TASKS_CLIENT_ID" },
|
|
133
106
|
clientSecret: { type: "string", required: false, env: "GOOGLE_TASKS_CLIENT_SECRET" },
|
|
134
107
|
refreshToken: { type: "string", required: false, env: "GOOGLE_TASKS_REFRESH_TOKEN" },
|
|
@@ -1,28 +1,4 @@
|
|
|
1
|
-
|
|
2
|
-
async function getToken(authUrl, clientId, clientSecret, scope, tenant) {
|
|
3
|
-
if (cachedToken && Date.now() < cachedToken.expiresAt)
|
|
4
|
-
return cachedToken.token;
|
|
5
|
-
const url = tenant ? `${authUrl}?tenant=${tenant}` : authUrl;
|
|
6
|
-
const body = new URLSearchParams({
|
|
7
|
-
grant_type: "client_credentials",
|
|
8
|
-
client_id: clientId,
|
|
9
|
-
client_secret: clientSecret,
|
|
10
|
-
scope,
|
|
11
|
-
});
|
|
12
|
-
const res = await fetch(url, {
|
|
13
|
-
method: "POST",
|
|
14
|
-
headers: { "Content-Type": "application/x-www-form-urlencoded" },
|
|
15
|
-
body: body.toString(),
|
|
16
|
-
});
|
|
17
|
-
if (!res.ok)
|
|
18
|
-
throw new Error(`HaloPSA auth error ${res.status}: ${await res.text()}`);
|
|
19
|
-
const data = (await res.json());
|
|
20
|
-
cachedToken = {
|
|
21
|
-
token: data.access_token,
|
|
22
|
-
expiresAt: Date.now() + (data.expires_in - 60) * 1000,
|
|
23
|
-
};
|
|
24
|
-
return cachedToken.token;
|
|
25
|
-
}
|
|
1
|
+
import { coordinatedAccessToken, requestToken, } from "../../_shared/tokenRefresh.js";
|
|
26
2
|
async function apiRequest(apiUrl, token, method, endpoint, body, qs) {
|
|
27
3
|
const url = new URL(`${apiUrl}${endpoint}`);
|
|
28
4
|
if (qs) {
|
|
@@ -51,12 +27,21 @@ async function apiRequest(apiUrl, token, method, endpoint, body, qs) {
|
|
|
51
27
|
return res.json();
|
|
52
28
|
}
|
|
53
29
|
async function req(ctx, method, endpoint, body, qs) {
|
|
54
|
-
const
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
30
|
+
const token = await coordinatedAccessToken(ctx, (cfg) => {
|
|
31
|
+
const url = new URL(cfg.hostingType === "on-premise"
|
|
32
|
+
? `${cfg.appUrl}/auth/token`
|
|
33
|
+
: `${cfg.authUrl}/token`);
|
|
34
|
+
if (cfg.tenant)
|
|
35
|
+
url.searchParams.set("tenant", cfg.tenant);
|
|
36
|
+
return requestToken({ url: url.toString(), clientAuthentication: "client_secret_post" }, {
|
|
37
|
+
grant_type: "client_credentials",
|
|
38
|
+
scope: cfg.scope ?? "all",
|
|
39
|
+
}, {
|
|
40
|
+
clientId: cfg.clientId,
|
|
41
|
+
clientSecret: cfg.clientSecret,
|
|
42
|
+
});
|
|
43
|
+
});
|
|
44
|
+
return apiRequest(ctx.connection.config.resourceApiUrl.replace(/\/$/, ""), token, method, endpoint, body, qs);
|
|
60
45
|
}
|
|
61
46
|
function registerCrud(rl, resource, apiPath) {
|
|
62
47
|
rl.registerAction(`${resource}.create`, {
|
|
@@ -5,6 +5,7 @@ export default function microsoftCalendar(rl) {
|
|
|
5
5
|
rl.setName(NAME);
|
|
6
6
|
rl.setVersion("1.0.0");
|
|
7
7
|
rl.setConnectionSchema({
|
|
8
|
+
authMethod: { type: "string", required: false, description: "delegated or appOnly; legacy configs infer the method from existing credentials" },
|
|
8
9
|
tenantId: { type: "string", required: false, env: "MS_GRAPH_TENANT_ID", description: "Entra tenant id (app-only) or omit for OAuth /common" },
|
|
9
10
|
clientId: { type: "string", required: false, env: "MS_GRAPH_CLIENT_ID", description: "App (client) id" },
|
|
10
11
|
clientSecret: { type: "string", required: false, env: "MS_GRAPH_CLIENT_SECRET", description: "Client secret VALUE" },
|
|
@@ -42,7 +43,7 @@ export default function microsoftCalendar(rl) {
|
|
|
42
43
|
description: "Get one calendar event by id (full details incl. body).",
|
|
43
44
|
inputSchema: { id: { type: "string", required: true } },
|
|
44
45
|
async execute(input, ctx) {
|
|
45
|
-
return graphRequest(ctx, NAME, SCOPES, "GET", `${userBase(ctx)}/events/${input.id}`);
|
|
46
|
+
return graphRequest(ctx, NAME, SCOPES, "GET", `${userBase(ctx)}/events/${encodeURIComponent(input.id)}`);
|
|
46
47
|
},
|
|
47
48
|
});
|
|
48
49
|
}
|