@prmichaelsen/firebase-admin-sdk-v8 2.5.2 → 2.6.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.
@@ -4,7 +4,16 @@
4
4
  "Bash(./agent/scripts/acp.version-check-for-updates.sh:*)",
5
5
  "Bash(test:*)",
6
6
  "Bash(ls:*)",
7
- "Bash(git add:*)"
7
+ "Bash(git add:*)",
8
+ "Bash(grep:*)",
9
+ "Bash(npm test:*)",
10
+ "Bash(npm run:*)",
11
+ "Bash(bash:*)",
12
+ "Bash(npx jest:*)",
13
+ "WebFetch(domain:agentbase.me)",
14
+ "WebSearch",
15
+ "WebFetch(domain:firebase.google.com)",
16
+ "WebFetch(domain:developers.google.com)"
8
17
  ]
9
18
  }
10
19
  }
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.6.0] - 2026-03-07
9
+
10
+ ### Added
11
+ - **Firebase Cloud Messaging (FCM)**: Server-side push notification support via FCM HTTP v1 API
12
+ - `sendMessage()` - Send notifications to devices, topics, or conditions
13
+ - `subscribeToTopic()` - Subscribe up to 1000 device tokens to a topic
14
+ - `unsubscribeFromTopic()` - Unsubscribe device tokens from a topic
15
+ - Full TypeScript types for Message, Notification, AndroidConfig, WebpushConfig, ApnsConfig
16
+ - Platform-specific configuration support (Android, Web, iOS)
17
+ - Data-only messages and notification+data payloads
18
+ - Topic name normalization (auto-prefixes `/topics/` when missing)
19
+ - 29 comprehensive unit tests
20
+ - New module: `src/messaging/` (client, types, index)
21
+ - New exported types: `Message`, `FcmNotification`, `AndroidConfig`, `AndroidNotification`, `WebpushConfig`, `ApnsConfig`, `FcmOptions`, `SendResponse`, `TopicManagementResponse`, `TopicManagementError`
22
+
23
+ ### Changed
24
+ - Total unit tests increased from 469 to 498 (+29 tests)
25
+ - Total test suites increased from 16 to 17
26
+
8
27
  ## [2.5.2] - 2026-03-03
9
28
 
10
29
  ### Fixed
package/dist/index.d.mts CHANGED
@@ -1006,6 +1006,153 @@ interface ResumableUploadOptions extends UploadOptions {
1006
1006
  */
1007
1007
  declare function uploadFileResumable(path: string, data: ArrayBuffer | Uint8Array | Blob | ReadableStream<Uint8Array>, contentType: string, options?: ResumableUploadOptions): Promise<FileMetadata>;
1008
1008
 
1009
+ /**
1010
+ * Firebase Cloud Messaging (FCM) Types
1011
+ * Based on FCM HTTP v1 API specification
1012
+ */
1013
+ interface Message {
1014
+ /** Device registration token (mutually exclusive with topic/condition) */
1015
+ token?: string;
1016
+ /** Topic name (mutually exclusive with token/condition) */
1017
+ topic?: string;
1018
+ /** Condition expression for topic combinations (mutually exclusive with token/topic) */
1019
+ condition?: string;
1020
+ /** Basic notification payload displayed on all platforms */
1021
+ notification?: Notification;
1022
+ /** Custom key-value data payload */
1023
+ data?: Record<string, string>;
1024
+ /** Android-specific configuration */
1025
+ android?: AndroidConfig;
1026
+ /** Web push protocol configuration */
1027
+ webpush?: WebpushConfig;
1028
+ /** Apple Push Notification Service configuration */
1029
+ apns?: ApnsConfig;
1030
+ /** FCM-specific options */
1031
+ fcm_options?: FcmOptions;
1032
+ }
1033
+ interface Notification {
1034
+ title?: string;
1035
+ body?: string;
1036
+ image?: string;
1037
+ }
1038
+ interface AndroidConfig {
1039
+ collapse_key?: string;
1040
+ priority?: 'high' | 'normal';
1041
+ ttl?: string;
1042
+ restricted_package_name?: string;
1043
+ data?: Record<string, string>;
1044
+ notification?: AndroidNotification;
1045
+ fcm_options?: AndroidFcmOptions;
1046
+ direct_boot_ok?: boolean;
1047
+ }
1048
+ interface AndroidNotification {
1049
+ title?: string;
1050
+ body?: string;
1051
+ icon?: string;
1052
+ color?: string;
1053
+ sound?: string;
1054
+ tag?: string;
1055
+ click_action?: string;
1056
+ body_loc_key?: string;
1057
+ body_loc_args?: string[];
1058
+ title_loc_key?: string;
1059
+ title_loc_args?: string[];
1060
+ channel_id?: string;
1061
+ ticker?: string;
1062
+ sticky?: boolean;
1063
+ event_time?: string;
1064
+ local_only?: boolean;
1065
+ notification_priority?: AndroidNotificationPriority;
1066
+ default_sound?: boolean;
1067
+ default_vibrate_timings?: boolean;
1068
+ default_light_settings?: boolean;
1069
+ vibrate_timings?: string[];
1070
+ visibility?: 'VISIBILITY_UNSPECIFIED' | 'PRIVATE' | 'PUBLIC' | 'SECRET';
1071
+ notification_count?: number;
1072
+ light_settings?: LightSettings;
1073
+ image?: string;
1074
+ }
1075
+ type AndroidNotificationPriority = 'PRIORITY_UNSPECIFIED' | 'PRIORITY_MIN' | 'PRIORITY_LOW' | 'PRIORITY_DEFAULT' | 'PRIORITY_HIGH' | 'PRIORITY_MAX';
1076
+ interface LightSettings {
1077
+ color: {
1078
+ red: number;
1079
+ green: number;
1080
+ blue: number;
1081
+ alpha: number;
1082
+ };
1083
+ light_on_duration: string;
1084
+ light_off_duration: string;
1085
+ }
1086
+ interface AndroidFcmOptions {
1087
+ analytics_label?: string;
1088
+ }
1089
+ interface WebpushConfig {
1090
+ headers?: Record<string, string>;
1091
+ data?: Record<string, string>;
1092
+ notification?: Record<string, unknown>;
1093
+ fcm_options?: WebpushFcmOptions;
1094
+ }
1095
+ interface WebpushFcmOptions {
1096
+ link?: string;
1097
+ analytics_label?: string;
1098
+ }
1099
+ interface ApnsConfig {
1100
+ headers?: Record<string, string>;
1101
+ payload?: Record<string, unknown>;
1102
+ fcm_options?: ApnsFcmOptions;
1103
+ }
1104
+ interface ApnsFcmOptions {
1105
+ analytics_label?: string;
1106
+ image?: string;
1107
+ }
1108
+ interface FcmOptions {
1109
+ analytics_label?: string;
1110
+ }
1111
+ interface SendResponse {
1112
+ /** Full resource name: "projects/{id}/messages/{id}" */
1113
+ name: string;
1114
+ }
1115
+ interface TopicManagementResponse {
1116
+ successCount: number;
1117
+ failureCount: number;
1118
+ errors: TopicManagementError[];
1119
+ }
1120
+ interface TopicManagementError {
1121
+ index: number;
1122
+ error: string;
1123
+ }
1124
+
1125
+ /**
1126
+ * Firebase Cloud Messaging (FCM) Client
1127
+ * Implements FCM HTTP v1 API for sending messages and managing topic subscriptions.
1128
+ */
1129
+
1130
+ /**
1131
+ * Send a message via FCM HTTP v1 API.
1132
+ *
1133
+ * Exactly one of `message.token`, `message.topic`, or `message.condition` must be set.
1134
+ *
1135
+ * @param message - The message to send
1136
+ * @returns The message resource name (e.g. "projects/my-project/messages/123")
1137
+ */
1138
+ declare function sendMessage(message: Message): Promise<string>;
1139
+ /**
1140
+ * Subscribe device tokens to a topic.
1141
+ *
1142
+ * @param tokens - Array of device registration tokens (max 1000)
1143
+ * @param topic - Topic name (with or without "/topics/" prefix)
1144
+ * @returns Results with success/failure counts and per-token errors
1145
+ */
1146
+ declare function subscribeToTopic(tokens: string[], topic: string): Promise<TopicManagementResponse>;
1147
+ /**
1148
+ * Unsubscribe device tokens from a topic.
1149
+ *
1150
+ * @param tokens - Array of device registration tokens (max 1000)
1151
+ * @param topic - Topic name (with or without "/topics/" prefix)
1152
+ * @returns Results with success/failure counts and per-token errors
1153
+ */
1154
+ declare function unsubscribeFromTopic(tokens: string[], topic: string): Promise<TopicManagementResponse>;
1155
+
1009
1156
  /**
1010
1157
  * Firebase Admin SDK v8 - Field Value Helpers
1011
1158
  * Special field values for Firestore operations
@@ -1103,4 +1250,4 @@ declare function getAdminAccessToken(): Promise<string>;
1103
1250
  */
1104
1251
  declare function clearTokenCache(): void;
1105
1252
 
1106
- export { type BatchWrite, type BatchWriteResult, type CreateUserRequest, type CustomClaims, type CustomTokenSignInResponse, type DataObject, type DecodedIdToken, type DocumentReference, type DownloadOptions, FieldValue, type FieldValue$1 as FieldValueSentinel, FieldValueType, type FileMetadata, type FirestoreDocument, type FirestoreValue, type ListFilesResult, type ListOptions, type ListUsersResult, type QueryFilter, type QueryOptions, type QueryOrder, type ResumableUploadOptions, type ServiceAccount, type SessionCookieOptions, type SetOptions, type SignedUrlOptions, type TokenResponse, type UpdateOptions, type UpdateUserRequest, type UploadOptions, type UserInfo, type UserRecord, type WhereFilterOp, addDocument, batchWrite, clearConfig, clearTokenCache, countDocuments, createCustomToken, createSessionCookie, createUser, deleteDocument, deleteFile, deleteUser, downloadFile, fileExists, generateSignedUrl, getAdminAccessToken, getAuth, getConfig, getDocument, getFileMetadata, getProjectId, getServiceAccount, getUserByEmail, getUserByUid, getUserFromToken, initializeApp, iterateCollection, listDocuments, listFiles, listUsers, queryDocuments, setCustomUserClaims, setDocument, signInWithCustomToken, updateDocument, updateUser, uploadFile, uploadFileResumable, verifyIdToken, verifySessionCookie };
1253
+ 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, 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
@@ -1006,6 +1006,153 @@ interface ResumableUploadOptions extends UploadOptions {
1006
1006
  */
1007
1007
  declare function uploadFileResumable(path: string, data: ArrayBuffer | Uint8Array | Blob | ReadableStream<Uint8Array>, contentType: string, options?: ResumableUploadOptions): Promise<FileMetadata>;
1008
1008
 
1009
+ /**
1010
+ * Firebase Cloud Messaging (FCM) Types
1011
+ * Based on FCM HTTP v1 API specification
1012
+ */
1013
+ interface Message {
1014
+ /** Device registration token (mutually exclusive with topic/condition) */
1015
+ token?: string;
1016
+ /** Topic name (mutually exclusive with token/condition) */
1017
+ topic?: string;
1018
+ /** Condition expression for topic combinations (mutually exclusive with token/topic) */
1019
+ condition?: string;
1020
+ /** Basic notification payload displayed on all platforms */
1021
+ notification?: Notification;
1022
+ /** Custom key-value data payload */
1023
+ data?: Record<string, string>;
1024
+ /** Android-specific configuration */
1025
+ android?: AndroidConfig;
1026
+ /** Web push protocol configuration */
1027
+ webpush?: WebpushConfig;
1028
+ /** Apple Push Notification Service configuration */
1029
+ apns?: ApnsConfig;
1030
+ /** FCM-specific options */
1031
+ fcm_options?: FcmOptions;
1032
+ }
1033
+ interface Notification {
1034
+ title?: string;
1035
+ body?: string;
1036
+ image?: string;
1037
+ }
1038
+ interface AndroidConfig {
1039
+ collapse_key?: string;
1040
+ priority?: 'high' | 'normal';
1041
+ ttl?: string;
1042
+ restricted_package_name?: string;
1043
+ data?: Record<string, string>;
1044
+ notification?: AndroidNotification;
1045
+ fcm_options?: AndroidFcmOptions;
1046
+ direct_boot_ok?: boolean;
1047
+ }
1048
+ interface AndroidNotification {
1049
+ title?: string;
1050
+ body?: string;
1051
+ icon?: string;
1052
+ color?: string;
1053
+ sound?: string;
1054
+ tag?: string;
1055
+ click_action?: string;
1056
+ body_loc_key?: string;
1057
+ body_loc_args?: string[];
1058
+ title_loc_key?: string;
1059
+ title_loc_args?: string[];
1060
+ channel_id?: string;
1061
+ ticker?: string;
1062
+ sticky?: boolean;
1063
+ event_time?: string;
1064
+ local_only?: boolean;
1065
+ notification_priority?: AndroidNotificationPriority;
1066
+ default_sound?: boolean;
1067
+ default_vibrate_timings?: boolean;
1068
+ default_light_settings?: boolean;
1069
+ vibrate_timings?: string[];
1070
+ visibility?: 'VISIBILITY_UNSPECIFIED' | 'PRIVATE' | 'PUBLIC' | 'SECRET';
1071
+ notification_count?: number;
1072
+ light_settings?: LightSettings;
1073
+ image?: string;
1074
+ }
1075
+ type AndroidNotificationPriority = 'PRIORITY_UNSPECIFIED' | 'PRIORITY_MIN' | 'PRIORITY_LOW' | 'PRIORITY_DEFAULT' | 'PRIORITY_HIGH' | 'PRIORITY_MAX';
1076
+ interface LightSettings {
1077
+ color: {
1078
+ red: number;
1079
+ green: number;
1080
+ blue: number;
1081
+ alpha: number;
1082
+ };
1083
+ light_on_duration: string;
1084
+ light_off_duration: string;
1085
+ }
1086
+ interface AndroidFcmOptions {
1087
+ analytics_label?: string;
1088
+ }
1089
+ interface WebpushConfig {
1090
+ headers?: Record<string, string>;
1091
+ data?: Record<string, string>;
1092
+ notification?: Record<string, unknown>;
1093
+ fcm_options?: WebpushFcmOptions;
1094
+ }
1095
+ interface WebpushFcmOptions {
1096
+ link?: string;
1097
+ analytics_label?: string;
1098
+ }
1099
+ interface ApnsConfig {
1100
+ headers?: Record<string, string>;
1101
+ payload?: Record<string, unknown>;
1102
+ fcm_options?: ApnsFcmOptions;
1103
+ }
1104
+ interface ApnsFcmOptions {
1105
+ analytics_label?: string;
1106
+ image?: string;
1107
+ }
1108
+ interface FcmOptions {
1109
+ analytics_label?: string;
1110
+ }
1111
+ interface SendResponse {
1112
+ /** Full resource name: "projects/{id}/messages/{id}" */
1113
+ name: string;
1114
+ }
1115
+ interface TopicManagementResponse {
1116
+ successCount: number;
1117
+ failureCount: number;
1118
+ errors: TopicManagementError[];
1119
+ }
1120
+ interface TopicManagementError {
1121
+ index: number;
1122
+ error: string;
1123
+ }
1124
+
1125
+ /**
1126
+ * Firebase Cloud Messaging (FCM) Client
1127
+ * Implements FCM HTTP v1 API for sending messages and managing topic subscriptions.
1128
+ */
1129
+
1130
+ /**
1131
+ * Send a message via FCM HTTP v1 API.
1132
+ *
1133
+ * Exactly one of `message.token`, `message.topic`, or `message.condition` must be set.
1134
+ *
1135
+ * @param message - The message to send
1136
+ * @returns The message resource name (e.g. "projects/my-project/messages/123")
1137
+ */
1138
+ declare function sendMessage(message: Message): Promise<string>;
1139
+ /**
1140
+ * Subscribe device tokens to a topic.
1141
+ *
1142
+ * @param tokens - Array of device registration tokens (max 1000)
1143
+ * @param topic - Topic name (with or without "/topics/" prefix)
1144
+ * @returns Results with success/failure counts and per-token errors
1145
+ */
1146
+ declare function subscribeToTopic(tokens: string[], topic: string): Promise<TopicManagementResponse>;
1147
+ /**
1148
+ * Unsubscribe device tokens from a topic.
1149
+ *
1150
+ * @param tokens - Array of device registration tokens (max 1000)
1151
+ * @param topic - Topic name (with or without "/topics/" prefix)
1152
+ * @returns Results with success/failure counts and per-token errors
1153
+ */
1154
+ declare function unsubscribeFromTopic(tokens: string[], topic: string): Promise<TopicManagementResponse>;
1155
+
1009
1156
  /**
1010
1157
  * Firebase Admin SDK v8 - Field Value Helpers
1011
1158
  * Special field values for Firestore operations
@@ -1103,4 +1250,4 @@ declare function getAdminAccessToken(): Promise<string>;
1103
1250
  */
1104
1251
  declare function clearTokenCache(): void;
1105
1252
 
1106
- export { type BatchWrite, type BatchWriteResult, type CreateUserRequest, type CustomClaims, type CustomTokenSignInResponse, type DataObject, type DecodedIdToken, type DocumentReference, type DownloadOptions, FieldValue, type FieldValue$1 as FieldValueSentinel, FieldValueType, type FileMetadata, type FirestoreDocument, type FirestoreValue, type ListFilesResult, type ListOptions, type ListUsersResult, type QueryFilter, type QueryOptions, type QueryOrder, type ResumableUploadOptions, type ServiceAccount, type SessionCookieOptions, type SetOptions, type SignedUrlOptions, type TokenResponse, type UpdateOptions, type UpdateUserRequest, type UploadOptions, type UserInfo, type UserRecord, type WhereFilterOp, addDocument, batchWrite, clearConfig, clearTokenCache, countDocuments, createCustomToken, createSessionCookie, createUser, deleteDocument, deleteFile, deleteUser, downloadFile, fileExists, generateSignedUrl, getAdminAccessToken, getAuth, getConfig, getDocument, getFileMetadata, getProjectId, getServiceAccount, getUserByEmail, getUserByUid, getUserFromToken, initializeApp, iterateCollection, listDocuments, listFiles, listUsers, queryDocuments, setCustomUserClaims, setDocument, signInWithCustomToken, updateDocument, updateUser, uploadFile, uploadFileResumable, verifyIdToken, verifySessionCookie };
1253
+ 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, 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
@@ -231,9 +231,12 @@ __export(index_exports, {
231
231
  listFiles: () => listFiles,
232
232
  listUsers: () => listUsers,
233
233
  queryDocuments: () => queryDocuments,
234
+ sendMessage: () => sendMessage,
234
235
  setCustomUserClaims: () => setCustomUserClaims,
235
236
  setDocument: () => setDocument,
236
237
  signInWithCustomToken: () => signInWithCustomToken,
238
+ subscribeToTopic: () => subscribeToTopic,
239
+ unsubscribeFromTopic: () => unsubscribeFromTopic,
237
240
  updateDocument: () => updateDocument,
238
241
  updateUser: () => updateUser,
239
242
  uploadFile: () => uploadFile,
@@ -2142,6 +2145,96 @@ async function uploadFromStream(bucket, path, stream, contentType, chunkSize, op
2142
2145
  }
2143
2146
  }
2144
2147
 
2148
+ // src/messaging/client.ts
2149
+ init_token_generation();
2150
+ init_config();
2151
+ var FCM_BASE_URL = "https://fcm.googleapis.com/v1";
2152
+ var IID_BASE_URL = "https://iid.googleapis.com/iid/v1";
2153
+ async function sendMessage(message) {
2154
+ const targets = [message.token, message.topic, message.condition].filter(Boolean);
2155
+ if (targets.length === 0) {
2156
+ throw new Error("One of token, topic, or condition must be specified");
2157
+ }
2158
+ if (targets.length > 1) {
2159
+ throw new Error("Only one of token, topic, or condition can be specified");
2160
+ }
2161
+ const config = getConfig();
2162
+ const accessToken = await getAdminAccessToken();
2163
+ const url = `${FCM_BASE_URL}/projects/${config.projectId}/messages:send`;
2164
+ const response = await fetch(url, {
2165
+ method: "POST",
2166
+ headers: {
2167
+ "Authorization": `Bearer ${accessToken}`,
2168
+ "Content-Type": "application/json"
2169
+ },
2170
+ body: JSON.stringify({ message })
2171
+ });
2172
+ if (!response.ok) {
2173
+ let errorDetail;
2174
+ try {
2175
+ const errorBody = await response.json();
2176
+ errorDetail = JSON.stringify(errorBody);
2177
+ } catch {
2178
+ errorDetail = await response.text();
2179
+ }
2180
+ throw new Error(`FCM send failed (${response.status}): ${errorDetail}`);
2181
+ }
2182
+ const result = await response.json();
2183
+ return result.name;
2184
+ }
2185
+ async function subscribeToTopic(tokens, topic) {
2186
+ return manageTopicSubscription(tokens, topic, "batchAdd");
2187
+ }
2188
+ async function unsubscribeFromTopic(tokens, topic) {
2189
+ return manageTopicSubscription(tokens, topic, "batchRemove");
2190
+ }
2191
+ async function manageTopicSubscription(tokens, topic, action) {
2192
+ if (tokens.length === 0) {
2193
+ throw new Error("At least one token is required");
2194
+ }
2195
+ if (tokens.length > 1e3) {
2196
+ throw new Error("Maximum 1000 tokens per request");
2197
+ }
2198
+ const accessToken = await getAdminAccessToken();
2199
+ const url = `${IID_BASE_URL}:${action}`;
2200
+ const topicPath = topic.startsWith("/topics/") ? topic : `/topics/${topic}`;
2201
+ const response = await fetch(url, {
2202
+ method: "POST",
2203
+ headers: {
2204
+ "Authorization": `Bearer ${accessToken}`,
2205
+ "Content-Type": "application/json",
2206
+ "access_token_auth": "true"
2207
+ },
2208
+ body: JSON.stringify({
2209
+ to: topicPath,
2210
+ registration_tokens: tokens
2211
+ })
2212
+ });
2213
+ if (!response.ok) {
2214
+ let errorDetail;
2215
+ try {
2216
+ errorDetail = await response.text();
2217
+ } catch {
2218
+ errorDetail = `HTTP ${response.status}`;
2219
+ }
2220
+ throw new Error(`FCM topic ${action} failed (${response.status}): ${errorDetail}`);
2221
+ }
2222
+ const result = await response.json();
2223
+ let successCount = 0;
2224
+ let failureCount = 0;
2225
+ const errors = [];
2226
+ for (let i = 0; i < result.results.length; i++) {
2227
+ const entry = result.results[i];
2228
+ if (entry.error) {
2229
+ failureCount++;
2230
+ errors.push({ index: i, error: entry.error });
2231
+ } else {
2232
+ successCount++;
2233
+ }
2234
+ }
2235
+ return { successCount, failureCount, errors };
2236
+ }
2237
+
2145
2238
  // src/index.ts
2146
2239
  init_token_generation();
2147
2240
  init_service_account();
@@ -2178,9 +2271,12 @@ init_service_account();
2178
2271
  listFiles,
2179
2272
  listUsers,
2180
2273
  queryDocuments,
2274
+ sendMessage,
2181
2275
  setCustomUserClaims,
2182
2276
  setDocument,
2183
2277
  signInWithCustomToken,
2278
+ subscribeToTopic,
2279
+ unsubscribeFromTopic,
2184
2280
  updateDocument,
2185
2281
  updateUser,
2186
2282
  uploadFile,
package/dist/index.mjs CHANGED
@@ -1891,6 +1891,94 @@ async function uploadFromStream(bucket, path, stream, contentType, chunkSize, op
1891
1891
  reader.releaseLock();
1892
1892
  }
1893
1893
  }
1894
+
1895
+ // src/messaging/client.ts
1896
+ var FCM_BASE_URL = "https://fcm.googleapis.com/v1";
1897
+ var IID_BASE_URL = "https://iid.googleapis.com/iid/v1";
1898
+ async function sendMessage(message) {
1899
+ const targets = [message.token, message.topic, message.condition].filter(Boolean);
1900
+ if (targets.length === 0) {
1901
+ throw new Error("One of token, topic, or condition must be specified");
1902
+ }
1903
+ if (targets.length > 1) {
1904
+ throw new Error("Only one of token, topic, or condition can be specified");
1905
+ }
1906
+ const config = getConfig();
1907
+ const accessToken = await getAdminAccessToken();
1908
+ const url = `${FCM_BASE_URL}/projects/${config.projectId}/messages:send`;
1909
+ const response = await fetch(url, {
1910
+ method: "POST",
1911
+ headers: {
1912
+ "Authorization": `Bearer ${accessToken}`,
1913
+ "Content-Type": "application/json"
1914
+ },
1915
+ body: JSON.stringify({ message })
1916
+ });
1917
+ if (!response.ok) {
1918
+ let errorDetail;
1919
+ try {
1920
+ const errorBody = await response.json();
1921
+ errorDetail = JSON.stringify(errorBody);
1922
+ } catch {
1923
+ errorDetail = await response.text();
1924
+ }
1925
+ throw new Error(`FCM send failed (${response.status}): ${errorDetail}`);
1926
+ }
1927
+ const result = await response.json();
1928
+ return result.name;
1929
+ }
1930
+ async function subscribeToTopic(tokens, topic) {
1931
+ return manageTopicSubscription(tokens, topic, "batchAdd");
1932
+ }
1933
+ async function unsubscribeFromTopic(tokens, topic) {
1934
+ return manageTopicSubscription(tokens, topic, "batchRemove");
1935
+ }
1936
+ async function manageTopicSubscription(tokens, topic, action) {
1937
+ if (tokens.length === 0) {
1938
+ throw new Error("At least one token is required");
1939
+ }
1940
+ if (tokens.length > 1e3) {
1941
+ throw new Error("Maximum 1000 tokens per request");
1942
+ }
1943
+ const accessToken = await getAdminAccessToken();
1944
+ const url = `${IID_BASE_URL}:${action}`;
1945
+ const topicPath = topic.startsWith("/topics/") ? topic : `/topics/${topic}`;
1946
+ const response = await fetch(url, {
1947
+ method: "POST",
1948
+ headers: {
1949
+ "Authorization": `Bearer ${accessToken}`,
1950
+ "Content-Type": "application/json",
1951
+ "access_token_auth": "true"
1952
+ },
1953
+ body: JSON.stringify({
1954
+ to: topicPath,
1955
+ registration_tokens: tokens
1956
+ })
1957
+ });
1958
+ if (!response.ok) {
1959
+ let errorDetail;
1960
+ try {
1961
+ errorDetail = await response.text();
1962
+ } catch {
1963
+ errorDetail = `HTTP ${response.status}`;
1964
+ }
1965
+ throw new Error(`FCM topic ${action} failed (${response.status}): ${errorDetail}`);
1966
+ }
1967
+ const result = await response.json();
1968
+ let successCount = 0;
1969
+ let failureCount = 0;
1970
+ const errors = [];
1971
+ for (let i = 0; i < result.results.length; i++) {
1972
+ const entry = result.results[i];
1973
+ if (entry.error) {
1974
+ failureCount++;
1975
+ errors.push({ index: i, error: entry.error });
1976
+ } else {
1977
+ successCount++;
1978
+ }
1979
+ }
1980
+ return { successCount, failureCount, errors };
1981
+ }
1894
1982
  export {
1895
1983
  FieldValue,
1896
1984
  addDocument,
@@ -1923,9 +2011,12 @@ export {
1923
2011
  listFiles,
1924
2012
  listUsers,
1925
2013
  queryDocuments,
2014
+ sendMessage,
1926
2015
  setCustomUserClaims,
1927
2016
  setDocument,
1928
2017
  signInWithCustomToken,
2018
+ subscribeToTopic,
2019
+ unsubscribeFromTopic,
1929
2020
  updateDocument,
1930
2021
  updateUser,
1931
2022
  uploadFile,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@prmichaelsen/firebase-admin-sdk-v8",
3
- "version": "2.5.2",
3
+ "version": "2.6.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",