78 lines
2.6 KiB
JavaScript
78 lines
2.6 KiB
JavaScript
// Google Drive AppData helpers. Files written to the appDataFolder space are
|
|
// invisible to the user in the regular Drive UI and only readable by the
|
|
// OAuth client (i.e. *this* app's client_id) that created them.
|
|
//
|
|
// Docs: https://developers.google.com/drive/api/guides/appdata
|
|
|
|
const DRIVE = "https://www.googleapis.com/drive/v3";
|
|
const UPLOAD = "https://www.googleapis.com/upload/drive/v3";
|
|
|
|
async function driveFetch(token, url, init = {}) {
|
|
const res = await fetch(url, {
|
|
...init,
|
|
headers: {
|
|
Authorization: `Bearer ${token}`,
|
|
...(init.headers || {}),
|
|
},
|
|
});
|
|
if (!res.ok) {
|
|
const text = await res.text();
|
|
throw new Error(`Drive API ${res.status}: ${text.slice(0, 300)}`);
|
|
}
|
|
return res;
|
|
}
|
|
|
|
// Find a file by exact name inside the appDataFolder. Returns the file id, or
|
|
// null if not found.
|
|
export async function findAppDataFile(token, name) {
|
|
const url =
|
|
`${DRIVE}/files?spaces=appDataFolder` +
|
|
`&q=${encodeURIComponent(`name = '${name.replace(/'/g, "\\'")}'`)}` +
|
|
`&fields=files(id,name,modifiedTime)`;
|
|
const res = await driveFetch(token, url);
|
|
const { files } = await res.json();
|
|
if (!files || files.length === 0) return null;
|
|
// If somehow multiple, prefer the most recent.
|
|
files.sort((a, b) => (b.modifiedTime || "").localeCompare(a.modifiedTime || ""));
|
|
return files[0].id;
|
|
}
|
|
|
|
export async function downloadFile(token, fileId) {
|
|
const res = await driveFetch(token, `${DRIVE}/files/${fileId}?alt=media`);
|
|
return res.text();
|
|
}
|
|
|
|
// Multipart upload of a small JSON blob to appDataFolder. If fileId is given,
|
|
// updates that file (preserving parents); otherwise creates a new file.
|
|
export async function uploadAppDataJson(token, name, content, fileId = null) {
|
|
const boundary = "goostr-" + Math.random().toString(36).slice(2);
|
|
const metadata = fileId
|
|
? { name }
|
|
: { name, parents: ["appDataFolder"] };
|
|
|
|
const body =
|
|
`--${boundary}\r\n` +
|
|
`Content-Type: application/json; charset=UTF-8\r\n\r\n` +
|
|
`${JSON.stringify(metadata)}\r\n` +
|
|
`--${boundary}\r\n` +
|
|
`Content-Type: application/json\r\n\r\n` +
|
|
`${content}\r\n` +
|
|
`--${boundary}--`;
|
|
|
|
const url = fileId
|
|
? `${UPLOAD}/files/${fileId}?uploadType=multipart`
|
|
: `${UPLOAD}/files?uploadType=multipart`;
|
|
|
|
const res = await driveFetch(token, url, {
|
|
method: fileId ? "PATCH" : "POST",
|
|
headers: { "Content-Type": `multipart/related; boundary=${boundary}` },
|
|
body,
|
|
});
|
|
return res.json();
|
|
}
|
|
|
|
export async function fetchUserInfo(token) {
|
|
const res = await driveFetch(token, "https://www.googleapis.com/oauth2/v3/userinfo");
|
|
return res.json(); // { sub, name, picture, email, ... }
|
|
}
|