@prmichaelsen/firebase-admin-sdk-v8 2.7.0 → 2.9.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/CHANGELOG.md CHANGED
@@ -5,6 +5,25 @@ All notable changes to this project will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [2.9.0] - 2026-03-19
9
+
10
+ ### Added
11
+ - **Password Reset Link Generation (`generatePasswordResetLink`)**: Generate password reset links via Identity Toolkit API
12
+ - `generatePasswordResetLink(email, actionCodeSettings?)` - Generate out-of-band password reset link
13
+ - Supports `ActionCodeSettings` for custom continue URLs, mobile deep linking (iOS/Android), and custom hosting domains
14
+ - Returns the OOB link for sending via custom email delivery
15
+ - 6 unit tests + 3 e2e tests
16
+
17
+ ## [2.8.0] - 2026-03-12
18
+
19
+ ### Added
20
+ - **Firestore Batch Get by Paths (`getAllByPaths`)**: Fetch multiple documents from different collections in a single REST API call
21
+ - `getAllByPaths([{ collection, id }])` - Batch get up to 100 documents from arbitrary paths via `documents:batchGet` endpoint
22
+ - Returns results in the same order as input refs
23
+ - Missing documents return `null` (no throw)
24
+ - Supports subcollection paths (e.g., `users/uid1/profile`)
25
+ - 7 unit tests + 3 e2e tests
26
+
8
27
  ## [2.7.0] - 2026-03-12
9
28
 
10
29
  ### Added
@@ -16,6 +35,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
16
35
  - Supports subcollection paths
17
36
  - 8 unit tests + 5 e2e tests
18
37
 
38
+ ### Fixed
39
+ - **FCM**: `sendMessage` now uses `getProjectId()` instead of `getConfig().projectId`, fixing silent failure when `initializeApp()` hasn't been called (falls back to `FIREBASE_PROJECT_ID` env var)
40
+
19
41
  ### Changed
20
42
  - Total unit tests increased from 498 to 506 (+8 tests)
21
43
 
package/dist/index.d.mts CHANGED
@@ -258,6 +258,36 @@ interface ListUsersResult {
258
258
  /** Token for fetching the next page (if available) */
259
259
  pageToken?: string;
260
260
  }
261
+ /**
262
+ * Settings for email action links (password reset, email verification, etc.)
263
+ */
264
+ interface ActionCodeSettings {
265
+ /** The continue/state URL to redirect to after the action is completed */
266
+ url: string;
267
+ /** Whether the action link should be opened in a mobile app or web */
268
+ handleCodeInApp?: boolean;
269
+ /** iOS-specific settings */
270
+ iOS?: {
271
+ /** The iOS bundle ID of the app to open the link in */
272
+ bundleId: string;
273
+ };
274
+ /** Android-specific settings */
275
+ android?: {
276
+ /** The Android package name of the app to open the link in */
277
+ packageName: string;
278
+ /** Whether to install the app if not already installed */
279
+ installApp?: boolean;
280
+ /** Minimum Android app version required */
281
+ minimumVersion?: string;
282
+ };
283
+ /**
284
+ * The domain to use for Dynamic Links
285
+ * @deprecated Use linkDomain instead
286
+ */
287
+ dynamicLinkDomain?: string;
288
+ /** Custom Firebase Hosting domain to use for the link */
289
+ linkDomain?: string;
290
+ }
261
291
 
262
292
  /**
263
293
  * Firebase Admin SDK v8 - Configuration
@@ -518,6 +548,26 @@ declare function listUsers(maxResults?: number, pageToken?: string): Promise<Lis
518
548
  * @param customClaims - Custom claims object (max 1000 bytes when serialized)
519
549
  */
520
550
  declare function setCustomUserClaims(uid: string, customClaims: Record<string, any> | null): Promise<void>;
551
+ /**
552
+ * Generate a password reset link for the given email address
553
+ *
554
+ * Returns an out-of-band (OOB) link that can be sent to the user
555
+ * via a custom email delivery mechanism.
556
+ *
557
+ * @param email - User's email address
558
+ * @param actionCodeSettings - Optional settings for the action code
559
+ * @returns Password reset link URL
560
+ *
561
+ * @example
562
+ * ```typescript
563
+ * const link = await generatePasswordResetLink('user@example.com', {
564
+ * url: 'https://example.com/login',
565
+ * });
566
+ * // Send the link via your own email service
567
+ * await sendEmail(email, `Reset your password: ${link}`);
568
+ * ```
569
+ */
570
+ declare function generatePasswordResetLink(email: string, actionCodeSettings?: ActionCodeSettings): Promise<string>;
521
571
 
522
572
  /**
523
573
  * Firebase Admin SDK v8 - Firestore CRUD Operations
@@ -656,6 +706,26 @@ declare function queryDocuments(collectionPath: string, options?: QueryOptions):
656
706
  * ```
657
707
  */
658
708
  declare function getAll(collectionPath: string, documentIds: string[]): Promise<(DataObject | null)[]>;
709
+ /**
710
+ * Batch get multiple documents from different collections by their full paths
711
+ * Uses the documents:batchGet REST API endpoint
712
+ *
713
+ * @param documentRefs - Array of { collection, id } tuples pointing to documents (max 100)
714
+ * @returns Array of results in the same order as documentRefs. Missing documents return null.
715
+ * @throws {Error} If the operation fails or more than 100 documents requested
716
+ *
717
+ * @example
718
+ * ```typescript
719
+ * const docs = await getAllByPaths([
720
+ * { collection: 'users/uid1/profile', id: 'default' },
721
+ * { collection: 'users/uid2/profile', id: 'default' },
722
+ * ]);
723
+ * ```
724
+ */
725
+ declare function getAllByPaths(documentRefs: Array<{
726
+ collection: string;
727
+ id: string;
728
+ }>): Promise<(DataObject | null)[]>;
659
729
  /**
660
730
  * Perform batch write operations (set, update, delete)
661
731
  *
@@ -1266,4 +1336,4 @@ declare function getAdminAccessToken(): Promise<string>;
1266
1336
  */
1267
1337
  declare function clearTokenCache(): void;
1268
1338
 
1269
- export { type AndroidConfig, type AndroidNotification, type ApnsConfig, type BatchWrite, type BatchWriteResult, type CreateUserRequest, type CustomClaims, type CustomTokenSignInResponse, type DataObject, type DecodedIdToken, type DocumentReference, type DownloadOptions, type Notification as FcmNotification, type FcmOptions, FieldValue, type FieldValue$1 as FieldValueSentinel, FieldValueType, type FileMetadata, type FirestoreDocument, type FirestoreValue, type ListFilesResult, type ListOptions, type ListUsersResult, type Message, type QueryFilter, type QueryOptions, type QueryOrder, type ResumableUploadOptions, type SendResponse, type ServiceAccount, type SessionCookieOptions, type SetOptions, type SignedUrlOptions, type TokenResponse, type TopicManagementError, type TopicManagementResponse, type UpdateOptions, type UpdateUserRequest, type UploadOptions, type UserInfo, type UserRecord, type WebpushConfig, type WhereFilterOp, addDocument, batchWrite, clearConfig, clearTokenCache, countDocuments, createCustomToken, createSessionCookie, createUser, deleteDocument, deleteFile, deleteUser, downloadFile, fileExists, generateSignedUrl, getAdminAccessToken, getAll, getAuth, getConfig, getDocument, getFileMetadata, getProjectId, getServiceAccount, getUserByEmail, getUserByUid, getUserFromToken, initializeApp, iterateCollection, listDocuments, listFiles, listUsers, queryDocuments, sendMessage, setCustomUserClaims, setDocument, signInWithCustomToken, subscribeToTopic, unsubscribeFromTopic, updateDocument, updateUser, uploadFile, uploadFileResumable, verifyIdToken, verifySessionCookie };
1339
+ export { type ActionCodeSettings, type AndroidConfig, type AndroidNotification, type ApnsConfig, type BatchWrite, type BatchWriteResult, type CreateUserRequest, type CustomClaims, type CustomTokenSignInResponse, type DataObject, type DecodedIdToken, type DocumentReference, type DownloadOptions, type Notification as FcmNotification, type FcmOptions, FieldValue, type FieldValue$1 as FieldValueSentinel, FieldValueType, type FileMetadata, type FirestoreDocument, type FirestoreValue, type ListFilesResult, type ListOptions, type ListUsersResult, type Message, type QueryFilter, type QueryOptions, type QueryOrder, type ResumableUploadOptions, type SendResponse, type ServiceAccount, type SessionCookieOptions, type SetOptions, type SignedUrlOptions, type TokenResponse, type TopicManagementError, type TopicManagementResponse, type UpdateOptions, type UpdateUserRequest, type UploadOptions, type UserInfo, type UserRecord, type WebpushConfig, type WhereFilterOp, addDocument, batchWrite, clearConfig, clearTokenCache, countDocuments, createCustomToken, createSessionCookie, createUser, deleteDocument, deleteFile, deleteUser, downloadFile, fileExists, generatePasswordResetLink, generateSignedUrl, getAdminAccessToken, getAll, getAllByPaths, getAuth, getConfig, getDocument, getFileMetadata, getProjectId, getServiceAccount, getUserByEmail, getUserByUid, getUserFromToken, initializeApp, iterateCollection, listDocuments, listFiles, listUsers, queryDocuments, sendMessage, setCustomUserClaims, setDocument, signInWithCustomToken, subscribeToTopic, unsubscribeFromTopic, updateDocument, updateUser, uploadFile, uploadFileResumable, verifyIdToken, verifySessionCookie };
package/dist/index.d.ts CHANGED
@@ -258,6 +258,36 @@ interface ListUsersResult {
258
258
  /** Token for fetching the next page (if available) */
259
259
  pageToken?: string;
260
260
  }
261
+ /**
262
+ * Settings for email action links (password reset, email verification, etc.)
263
+ */
264
+ interface ActionCodeSettings {
265
+ /** The continue/state URL to redirect to after the action is completed */
266
+ url: string;
267
+ /** Whether the action link should be opened in a mobile app or web */
268
+ handleCodeInApp?: boolean;
269
+ /** iOS-specific settings */
270
+ iOS?: {
271
+ /** The iOS bundle ID of the app to open the link in */
272
+ bundleId: string;
273
+ };
274
+ /** Android-specific settings */
275
+ android?: {
276
+ /** The Android package name of the app to open the link in */
277
+ packageName: string;
278
+ /** Whether to install the app if not already installed */
279
+ installApp?: boolean;
280
+ /** Minimum Android app version required */
281
+ minimumVersion?: string;
282
+ };
283
+ /**
284
+ * The domain to use for Dynamic Links
285
+ * @deprecated Use linkDomain instead
286
+ */
287
+ dynamicLinkDomain?: string;
288
+ /** Custom Firebase Hosting domain to use for the link */
289
+ linkDomain?: string;
290
+ }
261
291
 
262
292
  /**
263
293
  * Firebase Admin SDK v8 - Configuration
@@ -518,6 +548,26 @@ declare function listUsers(maxResults?: number, pageToken?: string): Promise<Lis
518
548
  * @param customClaims - Custom claims object (max 1000 bytes when serialized)
519
549
  */
520
550
  declare function setCustomUserClaims(uid: string, customClaims: Record<string, any> | null): Promise<void>;
551
+ /**
552
+ * Generate a password reset link for the given email address
553
+ *
554
+ * Returns an out-of-band (OOB) link that can be sent to the user
555
+ * via a custom email delivery mechanism.
556
+ *
557
+ * @param email - User's email address
558
+ * @param actionCodeSettings - Optional settings for the action code
559
+ * @returns Password reset link URL
560
+ *
561
+ * @example
562
+ * ```typescript
563
+ * const link = await generatePasswordResetLink('user@example.com', {
564
+ * url: 'https://example.com/login',
565
+ * });
566
+ * // Send the link via your own email service
567
+ * await sendEmail(email, `Reset your password: ${link}`);
568
+ * ```
569
+ */
570
+ declare function generatePasswordResetLink(email: string, actionCodeSettings?: ActionCodeSettings): Promise<string>;
521
571
 
522
572
  /**
523
573
  * Firebase Admin SDK v8 - Firestore CRUD Operations
@@ -656,6 +706,26 @@ declare function queryDocuments(collectionPath: string, options?: QueryOptions):
656
706
  * ```
657
707
  */
658
708
  declare function getAll(collectionPath: string, documentIds: string[]): Promise<(DataObject | null)[]>;
709
+ /**
710
+ * Batch get multiple documents from different collections by their full paths
711
+ * Uses the documents:batchGet REST API endpoint
712
+ *
713
+ * @param documentRefs - Array of { collection, id } tuples pointing to documents (max 100)
714
+ * @returns Array of results in the same order as documentRefs. Missing documents return null.
715
+ * @throws {Error} If the operation fails or more than 100 documents requested
716
+ *
717
+ * @example
718
+ * ```typescript
719
+ * const docs = await getAllByPaths([
720
+ * { collection: 'users/uid1/profile', id: 'default' },
721
+ * { collection: 'users/uid2/profile', id: 'default' },
722
+ * ]);
723
+ * ```
724
+ */
725
+ declare function getAllByPaths(documentRefs: Array<{
726
+ collection: string;
727
+ id: string;
728
+ }>): Promise<(DataObject | null)[]>;
659
729
  /**
660
730
  * Perform batch write operations (set, update, delete)
661
731
  *
@@ -1266,4 +1336,4 @@ declare function getAdminAccessToken(): Promise<string>;
1266
1336
  */
1267
1337
  declare function clearTokenCache(): void;
1268
1338
 
1269
- export { type AndroidConfig, type AndroidNotification, type ApnsConfig, type BatchWrite, type BatchWriteResult, type CreateUserRequest, type CustomClaims, type CustomTokenSignInResponse, type DataObject, type DecodedIdToken, type DocumentReference, type DownloadOptions, type Notification as FcmNotification, type FcmOptions, FieldValue, type FieldValue$1 as FieldValueSentinel, FieldValueType, type FileMetadata, type FirestoreDocument, type FirestoreValue, type ListFilesResult, type ListOptions, type ListUsersResult, type Message, type QueryFilter, type QueryOptions, type QueryOrder, type ResumableUploadOptions, type SendResponse, type ServiceAccount, type SessionCookieOptions, type SetOptions, type SignedUrlOptions, type TokenResponse, type TopicManagementError, type TopicManagementResponse, type UpdateOptions, type UpdateUserRequest, type UploadOptions, type UserInfo, type UserRecord, type WebpushConfig, type WhereFilterOp, addDocument, batchWrite, clearConfig, clearTokenCache, countDocuments, createCustomToken, createSessionCookie, createUser, deleteDocument, deleteFile, deleteUser, downloadFile, fileExists, generateSignedUrl, getAdminAccessToken, getAll, getAuth, getConfig, getDocument, getFileMetadata, getProjectId, getServiceAccount, getUserByEmail, getUserByUid, getUserFromToken, initializeApp, iterateCollection, listDocuments, listFiles, listUsers, queryDocuments, sendMessage, setCustomUserClaims, setDocument, signInWithCustomToken, subscribeToTopic, unsubscribeFromTopic, updateDocument, updateUser, uploadFile, uploadFileResumable, verifyIdToken, verifySessionCookie };
1339
+ export { type ActionCodeSettings, type AndroidConfig, type AndroidNotification, type ApnsConfig, type BatchWrite, type BatchWriteResult, type CreateUserRequest, type CustomClaims, type CustomTokenSignInResponse, type DataObject, type DecodedIdToken, type DocumentReference, type DownloadOptions, type Notification as FcmNotification, type FcmOptions, FieldValue, type FieldValue$1 as FieldValueSentinel, FieldValueType, type FileMetadata, type FirestoreDocument, type FirestoreValue, type ListFilesResult, type ListOptions, type ListUsersResult, type Message, type QueryFilter, type QueryOptions, type QueryOrder, type ResumableUploadOptions, type SendResponse, type ServiceAccount, type SessionCookieOptions, type SetOptions, type SignedUrlOptions, type TokenResponse, type TopicManagementError, type TopicManagementResponse, type UpdateOptions, type UpdateUserRequest, type UploadOptions, type UserInfo, type UserRecord, type WebpushConfig, type WhereFilterOp, addDocument, batchWrite, clearConfig, clearTokenCache, countDocuments, createCustomToken, createSessionCookie, createUser, deleteDocument, deleteFile, deleteUser, downloadFile, fileExists, generatePasswordResetLink, generateSignedUrl, getAdminAccessToken, getAll, getAllByPaths, getAuth, getConfig, getDocument, getFileMetadata, getProjectId, getServiceAccount, getUserByEmail, getUserByUid, getUserFromToken, initializeApp, iterateCollection, listDocuments, listFiles, listUsers, queryDocuments, sendMessage, setCustomUserClaims, setDocument, signInWithCustomToken, subscribeToTopic, unsubscribeFromTopic, updateDocument, updateUser, uploadFile, uploadFileResumable, verifyIdToken, verifySessionCookie };
package/dist/index.js CHANGED
@@ -214,9 +214,11 @@ __export(index_exports, {
214
214
  deleteUser: () => deleteUser,
215
215
  downloadFile: () => downloadFile,
216
216
  fileExists: () => fileExists,
217
+ generatePasswordResetLink: () => generatePasswordResetLink,
217
218
  generateSignedUrl: () => generateSignedUrl,
218
219
  getAdminAccessToken: () => getAdminAccessToken,
219
220
  getAll: () => getAll,
221
+ getAllByPaths: () => getAllByPaths,
220
222
  getAuth: () => getAuth,
221
223
  getConfig: () => getConfig,
222
224
  getDocument: () => getDocument,
@@ -953,6 +955,67 @@ async function setCustomUserClaims(uid, customClaims) {
953
955
  throw new Error(`Failed to set custom claims: ${response.status} ${errorText}`);
954
956
  }
955
957
  }
958
+ function buildActionCodeSettingsRequest(settings) {
959
+ const request = {};
960
+ request.continueUrl = settings.url;
961
+ if (typeof settings.handleCodeInApp === "boolean") {
962
+ request.canHandleCodeInApp = settings.handleCodeInApp;
963
+ }
964
+ if (settings.iOS?.bundleId) {
965
+ request.iOSBundleId = settings.iOS.bundleId;
966
+ }
967
+ if (settings.android) {
968
+ request.androidPackageName = settings.android.packageName;
969
+ if (typeof settings.android.installApp === "boolean") {
970
+ request.androidInstallApp = settings.android.installApp;
971
+ }
972
+ if (settings.android.minimumVersion) {
973
+ request.androidMinimumVersion = settings.android.minimumVersion;
974
+ }
975
+ }
976
+ if (settings.dynamicLinkDomain) {
977
+ request.dynamicLinkDomain = settings.dynamicLinkDomain;
978
+ }
979
+ if (settings.linkDomain) {
980
+ request.linkDomain = settings.linkDomain;
981
+ }
982
+ return request;
983
+ }
984
+ async function generatePasswordResetLink(email, actionCodeSettings) {
985
+ if (!email || typeof email !== "string") {
986
+ throw new Error("email must be a non-empty string");
987
+ }
988
+ const projectId = getProjectId();
989
+ const accessToken = await getAdminAccessToken();
990
+ const requestBody = {
991
+ requestType: "PASSWORD_RESET",
992
+ email,
993
+ returnOobLink: true
994
+ };
995
+ if (actionCodeSettings) {
996
+ Object.assign(requestBody, buildActionCodeSettingsRequest(actionCodeSettings));
997
+ }
998
+ const response = await fetch(
999
+ `${IDENTITY_TOOLKIT_API}/projects/${projectId}/accounts:sendOobCode`,
1000
+ {
1001
+ method: "POST",
1002
+ headers: {
1003
+ "Authorization": `Bearer ${accessToken}`,
1004
+ "Content-Type": "application/json"
1005
+ },
1006
+ body: JSON.stringify(requestBody)
1007
+ }
1008
+ );
1009
+ if (!response.ok) {
1010
+ const errorText = await response.text();
1011
+ throw new Error(`Failed to generate password reset link: ${response.status} ${errorText}`);
1012
+ }
1013
+ const data = await response.json();
1014
+ if (!data.oobLink) {
1015
+ throw new Error("Password reset link not returned by server");
1016
+ }
1017
+ return data.oobLink;
1018
+ }
956
1019
 
957
1020
  // src/field-value.ts
958
1021
  function serverTimestamp() {
@@ -1553,6 +1616,44 @@ async function getAll(collectionPath, documentIds) {
1553
1616
  }
1554
1617
  return documents.map((docPath) => resultMap.get(docPath) ?? null);
1555
1618
  }
1619
+ async function getAllByPaths(documentRefs) {
1620
+ if (documentRefs.length === 0) return [];
1621
+ if (documentRefs.length > 100) {
1622
+ throw new Error("getAllByPaths supports a maximum of 100 documents per request");
1623
+ }
1624
+ for (const ref of documentRefs) {
1625
+ validateDocumentPath("collection", ref.collection, ref.id);
1626
+ }
1627
+ const accessToken = await getAdminAccessToken();
1628
+ const projectId = getProjectId();
1629
+ const basePath = `projects/${projectId}/databases/(default)/documents`;
1630
+ const documents = documentRefs.map((ref) => `${basePath}/${ref.collection}/${ref.id}`);
1631
+ const url = `${FIRESTORE_API}/${basePath}:batchGet`;
1632
+ const response = await fetch(url, {
1633
+ method: "POST",
1634
+ headers: {
1635
+ "Authorization": `Bearer ${accessToken}`,
1636
+ "Content-Type": "application/json"
1637
+ },
1638
+ body: JSON.stringify({ documents })
1639
+ });
1640
+ if (!response.ok) {
1641
+ const errorText = await response.text();
1642
+ throw new Error(`Failed to batch get documents: ${errorText}`);
1643
+ }
1644
+ const results = await response.json();
1645
+ const resultMap = /* @__PURE__ */ new Map();
1646
+ for (const result of results) {
1647
+ if (result.found) {
1648
+ const name = result.found.name;
1649
+ const data = convertFromFirestoreFormat(result.found.fields);
1650
+ resultMap.set(name, data);
1651
+ } else if (result.missing) {
1652
+ resultMap.set(result.missing, null);
1653
+ }
1654
+ }
1655
+ return documents.map((docPath) => resultMap.get(docPath) ?? null);
1656
+ }
1556
1657
  async function batchWrite(operations) {
1557
1658
  const accessToken = await getAdminAccessToken();
1558
1659
  const projectId = getProjectId();
@@ -2293,9 +2394,11 @@ init_service_account();
2293
2394
  deleteUser,
2294
2395
  downloadFile,
2295
2396
  fileExists,
2397
+ generatePasswordResetLink,
2296
2398
  generateSignedUrl,
2297
2399
  getAdminAccessToken,
2298
2400
  getAll,
2401
+ getAllByPaths,
2299
2402
  getAuth,
2300
2403
  getConfig,
2301
2404
  getDocument,
package/dist/index.mjs CHANGED
@@ -708,6 +708,67 @@ async function setCustomUserClaims(uid, customClaims) {
708
708
  throw new Error(`Failed to set custom claims: ${response.status} ${errorText}`);
709
709
  }
710
710
  }
711
+ function buildActionCodeSettingsRequest(settings) {
712
+ const request = {};
713
+ request.continueUrl = settings.url;
714
+ if (typeof settings.handleCodeInApp === "boolean") {
715
+ request.canHandleCodeInApp = settings.handleCodeInApp;
716
+ }
717
+ if (settings.iOS?.bundleId) {
718
+ request.iOSBundleId = settings.iOS.bundleId;
719
+ }
720
+ if (settings.android) {
721
+ request.androidPackageName = settings.android.packageName;
722
+ if (typeof settings.android.installApp === "boolean") {
723
+ request.androidInstallApp = settings.android.installApp;
724
+ }
725
+ if (settings.android.minimumVersion) {
726
+ request.androidMinimumVersion = settings.android.minimumVersion;
727
+ }
728
+ }
729
+ if (settings.dynamicLinkDomain) {
730
+ request.dynamicLinkDomain = settings.dynamicLinkDomain;
731
+ }
732
+ if (settings.linkDomain) {
733
+ request.linkDomain = settings.linkDomain;
734
+ }
735
+ return request;
736
+ }
737
+ async function generatePasswordResetLink(email, actionCodeSettings) {
738
+ if (!email || typeof email !== "string") {
739
+ throw new Error("email must be a non-empty string");
740
+ }
741
+ const projectId = getProjectId();
742
+ const accessToken = await getAdminAccessToken();
743
+ const requestBody = {
744
+ requestType: "PASSWORD_RESET",
745
+ email,
746
+ returnOobLink: true
747
+ };
748
+ if (actionCodeSettings) {
749
+ Object.assign(requestBody, buildActionCodeSettingsRequest(actionCodeSettings));
750
+ }
751
+ const response = await fetch(
752
+ `${IDENTITY_TOOLKIT_API}/projects/${projectId}/accounts:sendOobCode`,
753
+ {
754
+ method: "POST",
755
+ headers: {
756
+ "Authorization": `Bearer ${accessToken}`,
757
+ "Content-Type": "application/json"
758
+ },
759
+ body: JSON.stringify(requestBody)
760
+ }
761
+ );
762
+ if (!response.ok) {
763
+ const errorText = await response.text();
764
+ throw new Error(`Failed to generate password reset link: ${response.status} ${errorText}`);
765
+ }
766
+ const data = await response.json();
767
+ if (!data.oobLink) {
768
+ throw new Error("Password reset link not returned by server");
769
+ }
770
+ return data.oobLink;
771
+ }
711
772
 
712
773
  // src/field-value.ts
713
774
  function serverTimestamp() {
@@ -1304,6 +1365,44 @@ async function getAll(collectionPath, documentIds) {
1304
1365
  }
1305
1366
  return documents.map((docPath) => resultMap.get(docPath) ?? null);
1306
1367
  }
1368
+ async function getAllByPaths(documentRefs) {
1369
+ if (documentRefs.length === 0) return [];
1370
+ if (documentRefs.length > 100) {
1371
+ throw new Error("getAllByPaths supports a maximum of 100 documents per request");
1372
+ }
1373
+ for (const ref of documentRefs) {
1374
+ validateDocumentPath("collection", ref.collection, ref.id);
1375
+ }
1376
+ const accessToken = await getAdminAccessToken();
1377
+ const projectId = getProjectId();
1378
+ const basePath = `projects/${projectId}/databases/(default)/documents`;
1379
+ const documents = documentRefs.map((ref) => `${basePath}/${ref.collection}/${ref.id}`);
1380
+ const url = `${FIRESTORE_API}/${basePath}:batchGet`;
1381
+ const response = await fetch(url, {
1382
+ method: "POST",
1383
+ headers: {
1384
+ "Authorization": `Bearer ${accessToken}`,
1385
+ "Content-Type": "application/json"
1386
+ },
1387
+ body: JSON.stringify({ documents })
1388
+ });
1389
+ if (!response.ok) {
1390
+ const errorText = await response.text();
1391
+ throw new Error(`Failed to batch get documents: ${errorText}`);
1392
+ }
1393
+ const results = await response.json();
1394
+ const resultMap = /* @__PURE__ */ new Map();
1395
+ for (const result of results) {
1396
+ if (result.found) {
1397
+ const name = result.found.name;
1398
+ const data = convertFromFirestoreFormat(result.found.fields);
1399
+ resultMap.set(name, data);
1400
+ } else if (result.missing) {
1401
+ resultMap.set(result.missing, null);
1402
+ }
1403
+ }
1404
+ return documents.map((docPath) => resultMap.get(docPath) ?? null);
1405
+ }
1307
1406
  async function batchWrite(operations) {
1308
1407
  const accessToken = await getAdminAccessToken();
1309
1408
  const projectId = getProjectId();
@@ -2032,9 +2131,11 @@ export {
2032
2131
  deleteUser,
2033
2132
  downloadFile,
2034
2133
  fileExists,
2134
+ generatePasswordResetLink,
2035
2135
  generateSignedUrl,
2036
2136
  getAdminAccessToken,
2037
2137
  getAll,
2138
+ getAllByPaths,
2038
2139
  getAuth,
2039
2140
  getConfig,
2040
2141
  getDocument,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@prmichaelsen/firebase-admin-sdk-v8",
3
- "version": "2.7.0",
3
+ "version": "2.9.0",
4
4
  "description": "Firebase Admin SDK for Cloudflare Workers and edge runtimes using REST APIs",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.mjs",