@prmichaelsen/firebase-admin-sdk-v8 2.0.21 → 2.1.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/index.js CHANGED
@@ -25,18 +25,27 @@ __export(index_exports, {
25
25
  batchWrite: () => batchWrite,
26
26
  clearConfig: () => clearConfig,
27
27
  clearTokenCache: () => clearTokenCache,
28
+ createCustomToken: () => createCustomToken,
28
29
  deleteDocument: () => deleteDocument,
30
+ deleteFile: () => deleteFile,
31
+ downloadFile: () => downloadFile,
32
+ fileExists: () => fileExists,
33
+ generateSignedUrl: () => generateSignedUrl,
29
34
  getAdminAccessToken: () => getAdminAccessToken,
30
35
  getAuth: () => getAuth,
31
36
  getConfig: () => getConfig,
32
37
  getDocument: () => getDocument,
38
+ getFileMetadata: () => getFileMetadata,
33
39
  getProjectId: () => getProjectId,
34
40
  getServiceAccount: () => getServiceAccount,
35
41
  getUserFromToken: () => getUserFromToken,
36
42
  initializeApp: () => initializeApp,
43
+ listFiles: () => listFiles,
37
44
  queryDocuments: () => queryDocuments,
38
45
  setDocument: () => setDocument,
46
+ signInWithCustomToken: () => signInWithCustomToken,
39
47
  updateDocument: () => updateDocument,
48
+ uploadFile: () => uploadFile,
40
49
  verifyIdToken: () => verifyIdToken
41
50
  });
42
51
  module.exports = __toCommonJS(index_exports);
@@ -105,6 +114,20 @@ function getProjectId() {
105
114
  "Firebase project ID not configured. Either call initializeApp({ projectId: ... }) or set FIREBASE_PROJECT_ID environment variable."
106
115
  );
107
116
  }
117
+ function getFirebaseApiKey() {
118
+ if (globalConfig.apiKey) {
119
+ return globalConfig.apiKey;
120
+ }
121
+ if (typeof process !== "undefined" && process.env) {
122
+ const apiKey = process.env.FIREBASE_API_KEY || process.env.PUBLIC_FIREBASE_API_KEY;
123
+ if (apiKey) {
124
+ return apiKey;
125
+ }
126
+ }
127
+ throw new Error(
128
+ "Firebase API key not configured. Either call initializeApp({ apiKey: ... }) or set FIREBASE_API_KEY environment variable. Find your API key in Firebase Console > Project Settings > Web API Key."
129
+ );
130
+ }
108
131
 
109
132
  // src/x509.ts
110
133
  function decodeBase64(str) {
@@ -325,6 +348,105 @@ async function getUserFromToken(idToken) {
325
348
  photoURL: decodedToken.picture || null
326
349
  };
327
350
  }
351
+ function base64UrlEncode(str) {
352
+ return btoa(str).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
353
+ }
354
+ async function signWithPrivateKey(data, privateKey) {
355
+ const pemHeader = "-----BEGIN PRIVATE KEY-----";
356
+ const pemFooter = "-----END PRIVATE KEY-----";
357
+ const pemContents = privateKey.replace(pemHeader, "").replace(pemFooter, "").replace(/\s/g, "");
358
+ const binaryString = atob(pemContents);
359
+ const bytes = new Uint8Array(binaryString.length);
360
+ for (let i = 0; i < binaryString.length; i++) {
361
+ bytes[i] = binaryString.charCodeAt(i);
362
+ }
363
+ const key = await crypto.subtle.importKey(
364
+ "pkcs8",
365
+ bytes,
366
+ {
367
+ name: "RSASSA-PKCS1-v1_5",
368
+ hash: "SHA-256"
369
+ },
370
+ false,
371
+ ["sign"]
372
+ );
373
+ const encoder = new TextEncoder();
374
+ const dataBytes = encoder.encode(data);
375
+ const signature = await crypto.subtle.sign(
376
+ "RSASSA-PKCS1-v1_5",
377
+ key,
378
+ dataBytes
379
+ );
380
+ return base64UrlEncode(String.fromCharCode(...new Uint8Array(signature)));
381
+ }
382
+ async function createCustomToken(uid, customClaims) {
383
+ const serviceAccount = getServiceAccount();
384
+ if (!uid || typeof uid !== "string") {
385
+ throw new Error("uid must be a non-empty string");
386
+ }
387
+ if (uid.length > 128) {
388
+ throw new Error("uid must be at most 128 characters");
389
+ }
390
+ const now = Math.floor(Date.now() / 1e3);
391
+ const payload = {
392
+ iss: serviceAccount.client_email,
393
+ sub: serviceAccount.client_email,
394
+ aud: "https://identitytoolkit.googleapis.com/google.identity.identitytoolkit.v1.IdentityToolkit",
395
+ iat: now,
396
+ exp: now + 3600,
397
+ // 1 hour
398
+ uid
399
+ };
400
+ if (customClaims) {
401
+ payload.claims = customClaims;
402
+ }
403
+ const header = {
404
+ alg: "RS256",
405
+ typ: "JWT"
406
+ };
407
+ const encodedHeader = base64UrlEncode(JSON.stringify(header));
408
+ const encodedPayload = base64UrlEncode(JSON.stringify(payload));
409
+ const unsignedToken = `${encodedHeader}.${encodedPayload}`;
410
+ const signature = await signWithPrivateKey(unsignedToken, serviceAccount.private_key);
411
+ return `${unsignedToken}.${signature}`;
412
+ }
413
+ async function signInWithCustomToken(customToken) {
414
+ if (!customToken || typeof customToken !== "string") {
415
+ throw new Error("customToken must be a non-empty string");
416
+ }
417
+ const apiKey = getFirebaseApiKey();
418
+ const url = `https://identitytoolkit.googleapis.com/v1/accounts:signInWithCustomToken?key=${apiKey}`;
419
+ const response = await fetch(url, {
420
+ method: "POST",
421
+ headers: {
422
+ "Content-Type": "application/json"
423
+ },
424
+ body: JSON.stringify({
425
+ token: customToken,
426
+ returnSecureToken: true
427
+ })
428
+ });
429
+ if (!response.ok) {
430
+ const errorText = await response.text();
431
+ let errorMessage = `Failed to sign in with custom token: ${response.status}`;
432
+ try {
433
+ const errorJson = JSON.parse(errorText);
434
+ if (errorJson.error && errorJson.error.message) {
435
+ errorMessage += ` - ${errorJson.error.message}`;
436
+ }
437
+ } catch {
438
+ errorMessage += ` - ${errorText}`;
439
+ }
440
+ throw new Error(errorMessage);
441
+ }
442
+ const result = await response.json();
443
+ return {
444
+ idToken: result.idToken,
445
+ refreshToken: result.refreshToken,
446
+ expiresIn: result.expiresIn,
447
+ localId: result.localId
448
+ };
449
+ }
328
450
  function getAuth() {
329
451
  return {
330
452
  verifyIdToken
@@ -608,7 +730,7 @@ function removeFieldTransforms(data) {
608
730
  }
609
731
 
610
732
  // src/token-generation.ts
611
- function base64UrlEncode(str) {
733
+ function base64UrlEncode2(str) {
612
734
  return btoa(str).replace(/=/g, "").replace(/\+/g, "-").replace(/\//g, "_");
613
735
  }
614
736
  function base64UrlEncodeBuffer(buffer) {
@@ -629,8 +751,8 @@ async function createJWT(serviceAccount) {
629
751
  exp: expiry,
630
752
  scope: "https://www.googleapis.com/auth/cloud-platform https://www.googleapis.com/auth/datastore https://www.googleapis.com/auth/firebase"
631
753
  };
632
- const encodedHeader = base64UrlEncode(JSON.stringify(header));
633
- const encodedPayload = base64UrlEncode(JSON.stringify(payload));
754
+ const encodedHeader = base64UrlEncode2(JSON.stringify(header));
755
+ const encodedPayload = base64UrlEncode2(JSON.stringify(payload));
634
756
  const unsignedToken = `${encodedHeader}.${encodedPayload}`;
635
757
  const pemContents = serviceAccount.private_key.replace("-----BEGIN PRIVATE KEY-----", "").replace("-----END PRIVATE KEY-----", "").replace(/\s/g, "");
636
758
  const binaryDer = Uint8Array.from(atob(pemContents), (c) => c.charCodeAt(0));
@@ -679,6 +801,26 @@ function clearTokenCache() {
679
801
  tokenExpiry = 0;
680
802
  }
681
803
 
804
+ // src/firestore/path-validation.ts
805
+ function validateCollectionPath(argumentName, collectionPath) {
806
+ const segments = collectionPath.split("/").filter((s) => s.length > 0);
807
+ if (segments.length % 2 === 0) {
808
+ throw new Error(
809
+ `Value for argument "${argumentName}" must point to a collection, but was "${collectionPath}". Your path does not contain an odd number of components.`
810
+ );
811
+ }
812
+ }
813
+ function validateDocumentPath(argumentName, collectionPath, documentId) {
814
+ const collectionSegments = collectionPath.split("/").filter((s) => s.length > 0);
815
+ const totalSegments = collectionSegments.length + 1;
816
+ if (totalSegments % 2 !== 0) {
817
+ const fullPath = `${collectionPath}/${documentId}`;
818
+ throw new Error(
819
+ `Value for argument "${argumentName}" must point to a document, but was "${fullPath}". Your path does not contain an even number of components.`
820
+ );
821
+ }
822
+ }
823
+
682
824
  // src/firestore/operations.ts
683
825
  var FIRESTORE_API = "https://firestore.googleapis.com/v1";
684
826
  async function commitWrites(writes) {
@@ -699,6 +841,7 @@ async function commitWrites(writes) {
699
841
  }
700
842
  }
701
843
  async function setDocument(collectionPath, documentId, data, options) {
844
+ validateDocumentPath("collectionPath", collectionPath, documentId);
702
845
  const projectId = getProjectId();
703
846
  const documentPath = `projects/${projectId}/databases/(default)/documents/${collectionPath}/${documentId}`;
704
847
  const cleanData = removeFieldTransforms(data);
@@ -748,6 +891,7 @@ async function setDocument(collectionPath, documentId, data, options) {
748
891
  }
749
892
  }
750
893
  async function addDocument(collectionPath, data, documentId) {
894
+ validateCollectionPath("collectionPath", collectionPath);
751
895
  const accessToken = await getAdminAccessToken();
752
896
  const projectId = getProjectId();
753
897
  const baseUrl = `${FIRESTORE_API}/projects/${projectId}/databases/(default)/documents/${collectionPath}`;
@@ -779,6 +923,7 @@ async function addDocument(collectionPath, data, documentId) {
779
923
  };
780
924
  }
781
925
  async function getDocument(collectionPath, documentId) {
926
+ validateDocumentPath("collectionPath", collectionPath, documentId);
782
927
  const accessToken = await getAdminAccessToken();
783
928
  const projectId = getProjectId();
784
929
  const url = `${FIRESTORE_API}/projects/${projectId}/databases/(default)/documents/${collectionPath}/${documentId}`;
@@ -798,6 +943,7 @@ async function getDocument(collectionPath, documentId) {
798
943
  return convertFromFirestoreFormat(result.fields);
799
944
  }
800
945
  async function updateDocument(collectionPath, documentId, data) {
946
+ validateDocumentPath("collectionPath", collectionPath, documentId);
801
947
  const projectId = getProjectId();
802
948
  const documentPath = `projects/${projectId}/databases/(default)/documents/${collectionPath}/${documentId}`;
803
949
  const cleanData = removeFieldTransforms(data);
@@ -850,6 +996,7 @@ async function updateDocument(collectionPath, documentId, data) {
850
996
  }
851
997
  }
852
998
  async function deleteDocument(collectionPath, documentId) {
999
+ validateDocumentPath("collectionPath", collectionPath, documentId);
853
1000
  const accessToken = await getAdminAccessToken();
854
1001
  const projectId = getProjectId();
855
1002
  const url = `${FIRESTORE_API}/projects/${projectId}/databases/(default)/documents/${collectionPath}/${documentId}`;
@@ -865,6 +1012,7 @@ async function deleteDocument(collectionPath, documentId) {
865
1012
  }
866
1013
  }
867
1014
  async function queryDocuments(collectionPath, options) {
1015
+ validateCollectionPath("collectionPath", collectionPath);
868
1016
  const accessToken = await getAdminAccessToken();
869
1017
  const projectId = getProjectId();
870
1018
  if (!options || Object.keys(options).length === 0) {
@@ -981,6 +1129,315 @@ async function batchWrite(operations) {
981
1129
  }
982
1130
  return await response.json();
983
1131
  }
1132
+
1133
+ // src/storage/client.ts
1134
+ var STORAGE_API_BASE = "https://storage.googleapis.com/storage/v1";
1135
+ var UPLOAD_API_BASE = "https://storage.googleapis.com/upload/storage/v1";
1136
+ function getDefaultBucket() {
1137
+ const customBucket = process.env.FIREBASE_STORAGE_BUCKET;
1138
+ if (customBucket) {
1139
+ return customBucket;
1140
+ }
1141
+ const projectId = getProjectId();
1142
+ return `${projectId}.appspot.com`;
1143
+ }
1144
+ function detectContentType(filename) {
1145
+ const ext = filename.split(".").pop()?.toLowerCase();
1146
+ const mimeTypes = {
1147
+ "txt": "text/plain",
1148
+ "html": "text/html",
1149
+ "htm": "text/html",
1150
+ "css": "text/css",
1151
+ "js": "application/javascript",
1152
+ "json": "application/json",
1153
+ "xml": "application/xml",
1154
+ "jpg": "image/jpeg",
1155
+ "jpeg": "image/jpeg",
1156
+ "png": "image/png",
1157
+ "gif": "image/gif",
1158
+ "svg": "image/svg+xml",
1159
+ "webp": "image/webp",
1160
+ "pdf": "application/pdf",
1161
+ "zip": "application/zip",
1162
+ "mp4": "video/mp4",
1163
+ "mp3": "audio/mpeg",
1164
+ "wav": "audio/wav"
1165
+ };
1166
+ return mimeTypes[ext || ""] || "application/octet-stream";
1167
+ }
1168
+ async function uploadFile(path, data, options = {}) {
1169
+ const token = await getAdminAccessToken();
1170
+ const bucket = getDefaultBucket();
1171
+ const contentType = options.contentType || detectContentType(path);
1172
+ const url = `${UPLOAD_API_BASE}/b/${encodeURIComponent(bucket)}/o?uploadType=media&name=${encodeURIComponent(path)}`;
1173
+ let body;
1174
+ if (data instanceof Blob) {
1175
+ body = await data.arrayBuffer();
1176
+ } else if (data instanceof Uint8Array) {
1177
+ const buffer = new ArrayBuffer(data.byteLength);
1178
+ new Uint8Array(buffer).set(data);
1179
+ body = buffer;
1180
+ } else {
1181
+ body = data;
1182
+ }
1183
+ const response = await fetch(url, {
1184
+ method: "POST",
1185
+ headers: {
1186
+ "Authorization": `Bearer ${token}`,
1187
+ "Content-Type": contentType,
1188
+ "Content-Length": body.byteLength.toString()
1189
+ },
1190
+ body
1191
+ });
1192
+ if (!response.ok) {
1193
+ const errorText = await response.text();
1194
+ throw new Error(`Failed to upload file: ${response.status} ${errorText}`);
1195
+ }
1196
+ const result = await response.json();
1197
+ if (options.public) {
1198
+ await makeFilePublic(path);
1199
+ }
1200
+ if (options.metadata) {
1201
+ return await updateFileMetadata(path, options.metadata);
1202
+ }
1203
+ return result;
1204
+ }
1205
+ async function downloadFile(path, _options = {}) {
1206
+ const token = await getAdminAccessToken();
1207
+ const bucket = getDefaultBucket();
1208
+ const url = `${STORAGE_API_BASE}/b/${encodeURIComponent(bucket)}/o/${encodeURIComponent(path)}?alt=media`;
1209
+ const response = await fetch(url, {
1210
+ method: "GET",
1211
+ headers: {
1212
+ "Authorization": `Bearer ${token}`
1213
+ }
1214
+ });
1215
+ if (!response.ok) {
1216
+ const errorText = await response.text();
1217
+ throw new Error(`Failed to download file: ${response.status} ${errorText}`);
1218
+ }
1219
+ return await response.arrayBuffer();
1220
+ }
1221
+ async function deleteFile(path) {
1222
+ const token = await getAdminAccessToken();
1223
+ const bucket = getDefaultBucket();
1224
+ const url = `${STORAGE_API_BASE}/b/${encodeURIComponent(bucket)}/o/${encodeURIComponent(path)}`;
1225
+ const response = await fetch(url, {
1226
+ method: "DELETE",
1227
+ headers: {
1228
+ "Authorization": `Bearer ${token}`
1229
+ }
1230
+ });
1231
+ if (!response.ok) {
1232
+ const errorText = await response.text();
1233
+ throw new Error(`Failed to delete file: ${response.status} ${errorText}`);
1234
+ }
1235
+ }
1236
+ async function getFileMetadata(path) {
1237
+ const token = await getAdminAccessToken();
1238
+ const bucket = getDefaultBucket();
1239
+ const url = `${STORAGE_API_BASE}/b/${encodeURIComponent(bucket)}/o/${encodeURIComponent(path)}`;
1240
+ const response = await fetch(url, {
1241
+ method: "GET",
1242
+ headers: {
1243
+ "Authorization": `Bearer ${token}`
1244
+ }
1245
+ });
1246
+ if (!response.ok) {
1247
+ const errorText = await response.text();
1248
+ throw new Error(`Failed to get file metadata: ${response.status} ${errorText}`);
1249
+ }
1250
+ return await response.json();
1251
+ }
1252
+ async function updateFileMetadata(path, metadata) {
1253
+ const token = await getAdminAccessToken();
1254
+ const bucket = getDefaultBucket();
1255
+ const url = `${STORAGE_API_BASE}/b/${encodeURIComponent(bucket)}/o/${encodeURIComponent(path)}`;
1256
+ const response = await fetch(url, {
1257
+ method: "PATCH",
1258
+ headers: {
1259
+ "Authorization": `Bearer ${token}`,
1260
+ "Content-Type": "application/json"
1261
+ },
1262
+ body: JSON.stringify({ metadata })
1263
+ });
1264
+ if (!response.ok) {
1265
+ const errorText = await response.text();
1266
+ throw new Error(`Failed to update file metadata: ${response.status} ${errorText}`);
1267
+ }
1268
+ return await response.json();
1269
+ }
1270
+ async function makeFilePublic(path) {
1271
+ const token = await getAdminAccessToken();
1272
+ const bucket = getDefaultBucket();
1273
+ const url = `${STORAGE_API_BASE}/b/${encodeURIComponent(bucket)}/o/${encodeURIComponent(path)}/acl`;
1274
+ const response = await fetch(url, {
1275
+ method: "POST",
1276
+ headers: {
1277
+ "Authorization": `Bearer ${token}`,
1278
+ "Content-Type": "application/json"
1279
+ },
1280
+ body: JSON.stringify({
1281
+ entity: "allUsers",
1282
+ role: "READER"
1283
+ })
1284
+ });
1285
+ if (!response.ok) {
1286
+ const errorText = await response.text();
1287
+ throw new Error(`Failed to make file public: ${response.status} ${errorText}`);
1288
+ }
1289
+ }
1290
+ async function listFiles(options = {}) {
1291
+ const token = await getAdminAccessToken();
1292
+ const bucket = getDefaultBucket();
1293
+ const params = new URLSearchParams();
1294
+ if (options.prefix) params.append("prefix", options.prefix);
1295
+ if (options.delimiter) params.append("delimiter", options.delimiter);
1296
+ if (options.maxResults) params.append("maxResults", options.maxResults.toString());
1297
+ if (options.pageToken) params.append("pageToken", options.pageToken);
1298
+ const url = `${STORAGE_API_BASE}/b/${encodeURIComponent(bucket)}/o?${params.toString()}`;
1299
+ const response = await fetch(url, {
1300
+ method: "GET",
1301
+ headers: {
1302
+ "Authorization": `Bearer ${token}`
1303
+ }
1304
+ });
1305
+ if (!response.ok) {
1306
+ const errorText = await response.text();
1307
+ throw new Error(`Failed to list files: ${response.status} ${errorText}`);
1308
+ }
1309
+ const result = await response.json();
1310
+ return {
1311
+ files: result.items || [],
1312
+ nextPageToken: result.nextPageToken
1313
+ };
1314
+ }
1315
+ async function fileExists(path) {
1316
+ try {
1317
+ await getFileMetadata(path);
1318
+ return true;
1319
+ } catch (error) {
1320
+ if (error instanceof Error && error.message.includes("404")) {
1321
+ return false;
1322
+ }
1323
+ throw error;
1324
+ }
1325
+ }
1326
+
1327
+ // src/storage/signed-urls.ts
1328
+ function getStorageBucket() {
1329
+ const customBucket = process.env.FIREBASE_STORAGE_BUCKET;
1330
+ if (customBucket) {
1331
+ return customBucket;
1332
+ }
1333
+ const projectId = getProjectId();
1334
+ return `${projectId}.appspot.com`;
1335
+ }
1336
+ function getExpirationTimestamp(expires) {
1337
+ if (expires instanceof Date) {
1338
+ return Math.floor(expires.getTime() / 1e3);
1339
+ }
1340
+ return Math.floor(Date.now() / 1e3) + expires;
1341
+ }
1342
+ function actionToMethod(action) {
1343
+ switch (action) {
1344
+ case "read":
1345
+ return "GET";
1346
+ case "write":
1347
+ return "PUT";
1348
+ case "delete":
1349
+ return "DELETE";
1350
+ }
1351
+ }
1352
+ function stringToHex(str) {
1353
+ const encoder = new TextEncoder();
1354
+ const bytes = encoder.encode(str);
1355
+ return Array.from(bytes).map((b) => b.toString(16).padStart(2, "0")).join("");
1356
+ }
1357
+ async function signData(data, privateKey) {
1358
+ const pemHeader = "-----BEGIN PRIVATE KEY-----";
1359
+ const pemFooter = "-----END PRIVATE KEY-----";
1360
+ const pemContents = privateKey.replace(pemHeader, "").replace(pemFooter, "").replace(/\s/g, "");
1361
+ const binaryString = atob(pemContents);
1362
+ const bytes = new Uint8Array(binaryString.length);
1363
+ for (let i = 0; i < binaryString.length; i++) {
1364
+ bytes[i] = binaryString.charCodeAt(i);
1365
+ }
1366
+ const key = await crypto.subtle.importKey(
1367
+ "pkcs8",
1368
+ bytes,
1369
+ {
1370
+ name: "RSASSA-PKCS1-v1_5",
1371
+ hash: "SHA-256"
1372
+ },
1373
+ false,
1374
+ ["sign"]
1375
+ );
1376
+ const encoder = new TextEncoder();
1377
+ const dataBytes = encoder.encode(data);
1378
+ const signature = await crypto.subtle.sign(
1379
+ "RSASSA-PKCS1-v1_5",
1380
+ key,
1381
+ dataBytes
1382
+ );
1383
+ const signatureArray = new Uint8Array(signature);
1384
+ return Array.from(signatureArray).map((b) => b.toString(16).padStart(2, "0")).join("");
1385
+ }
1386
+ async function generateSignedUrl(path, options) {
1387
+ const serviceAccount = getServiceAccount();
1388
+ const bucket = getStorageBucket();
1389
+ const method = actionToMethod(options.action);
1390
+ const expiration = getExpirationTimestamp(options.expires);
1391
+ const now = /* @__PURE__ */ new Date();
1392
+ const timestamp = Math.floor(now.getTime() / 1e3);
1393
+ const isoString = now.toISOString();
1394
+ const datestamp = isoString.split("T")[0].replace(/-/g, "");
1395
+ const timeString = isoString.split("T")[1].replace(/[:.]/g, "").substring(0, 6);
1396
+ const dateTimeStamp = `${datestamp}T${timeString}Z`;
1397
+ const credentialScope = `${datestamp}/auto/storage/goog4_request`;
1398
+ const credential = `${serviceAccount.client_email}/${credentialScope}`;
1399
+ const canonicalHeaders = `host:storage.googleapis.com
1400
+ `;
1401
+ const signedHeaders = "host";
1402
+ const queryParams = {
1403
+ "X-Goog-Algorithm": "GOOG4-RSA-SHA256",
1404
+ "X-Goog-Credential": credential,
1405
+ "X-Goog-Date": dateTimeStamp,
1406
+ "X-Goog-Expires": (expiration - timestamp).toString(),
1407
+ "X-Goog-SignedHeaders": signedHeaders
1408
+ };
1409
+ if (options.contentType) {
1410
+ queryParams["response-content-type"] = options.contentType;
1411
+ }
1412
+ if (options.responseDisposition) {
1413
+ queryParams["response-content-disposition"] = options.responseDisposition;
1414
+ }
1415
+ if (options.responseType) {
1416
+ queryParams["response-content-type"] = options.responseType;
1417
+ }
1418
+ const sortedParams = Object.keys(queryParams).sort();
1419
+ const canonicalQueryString = sortedParams.map((key) => `${encodeURIComponent(key)}=${encodeURIComponent(queryParams[key])}`).join("&");
1420
+ const encodedPath = path.split("/").map((segment) => encodeURIComponent(segment)).join("/");
1421
+ const canonicalUri = `/${bucket}/${encodedPath}`;
1422
+ const canonicalRequest = [
1423
+ method,
1424
+ canonicalUri,
1425
+ canonicalQueryString,
1426
+ canonicalHeaders,
1427
+ signedHeaders,
1428
+ "UNSIGNED-PAYLOAD"
1429
+ ].join("\n");
1430
+ const canonicalRequestHash = stringToHex(canonicalRequest);
1431
+ const stringToSign = [
1432
+ "GOOG4-RSA-SHA256",
1433
+ dateTimeStamp,
1434
+ credentialScope,
1435
+ canonicalRequestHash
1436
+ ].join("\n");
1437
+ const signature = await signData(stringToSign, serviceAccount.private_key);
1438
+ const signedUrl = `https://storage.googleapis.com${canonicalUri}?${canonicalQueryString}&X-Goog-Signature=${signature}`;
1439
+ return signedUrl;
1440
+ }
984
1441
  // Annotate the CommonJS export names for ESM import in node:
985
1442
  0 && (module.exports = {
986
1443
  FieldValue,
@@ -988,17 +1445,26 @@ async function batchWrite(operations) {
988
1445
  batchWrite,
989
1446
  clearConfig,
990
1447
  clearTokenCache,
1448
+ createCustomToken,
991
1449
  deleteDocument,
1450
+ deleteFile,
1451
+ downloadFile,
1452
+ fileExists,
1453
+ generateSignedUrl,
992
1454
  getAdminAccessToken,
993
1455
  getAuth,
994
1456
  getConfig,
995
1457
  getDocument,
1458
+ getFileMetadata,
996
1459
  getProjectId,
997
1460
  getServiceAccount,
998
1461
  getUserFromToken,
999
1462
  initializeApp,
1463
+ listFiles,
1000
1464
  queryDocuments,
1001
1465
  setDocument,
1466
+ signInWithCustomToken,
1002
1467
  updateDocument,
1468
+ uploadFile,
1003
1469
  verifyIdToken
1004
1470
  });