@prmichaelsen/firebase-admin-sdk-v8 2.0.17 → 2.0.20

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.
@@ -0,0 +1,41 @@
1
+ name: E2E Tests
2
+
3
+ on:
4
+ push:
5
+ branches: [ mainline ]
6
+ pull_request:
7
+ branches: [ mainline ]
8
+ workflow_dispatch: # Allow manual trigger
9
+
10
+ jobs:
11
+ e2e-tests:
12
+ runs-on: ubuntu-latest
13
+
14
+ steps:
15
+ - name: Checkout code
16
+ uses: actions/checkout@v4
17
+
18
+ - name: Setup Node.js
19
+ uses: actions/setup-node@v4
20
+ with:
21
+ node-version: '20'
22
+ cache: 'npm'
23
+
24
+ - name: Install dependencies
25
+ run: npm ci
26
+
27
+ - name: Run unit tests
28
+ run: npm test
29
+
30
+ - name: Create service account file
31
+ run: |
32
+ echo '${{ secrets.FIREBASE_SERVICE_ACCOUNT }}' > service-account.json
33
+
34
+ - name: Run e2e tests
35
+ run: npm run test:e2e
36
+ env:
37
+ NODE_ENV: test
38
+
39
+ - name: Clean up service account file
40
+ if: always()
41
+ run: rm -f service-account.json
@@ -0,0 +1,44 @@
1
+ name: Unit Tests
2
+
3
+ on:
4
+ push:
5
+ branches: [ mainline, develop ]
6
+ pull_request:
7
+ branches: [ mainline, develop ]
8
+
9
+ jobs:
10
+ test:
11
+ runs-on: ubuntu-latest
12
+
13
+ strategy:
14
+ matrix:
15
+ node-version: [18, 20, 22]
16
+
17
+ steps:
18
+ - name: Checkout code
19
+ uses: actions/checkout@v4
20
+
21
+ - name: Setup Node.js ${{ matrix.node-version }}
22
+ uses: actions/setup-node@v4
23
+ with:
24
+ node-version: ${{ matrix.node-version }}
25
+ cache: 'npm'
26
+
27
+ - name: Install dependencies
28
+ run: npm ci
29
+
30
+ - name: Run unit tests
31
+ run: npm test
32
+
33
+ - name: Run build
34
+ run: npm run build
35
+
36
+ - name: Upload coverage to Codecov (Node 20 only)
37
+ if: matrix.node-version == 20
38
+ uses: codecov/codecov-action@v4
39
+ with:
40
+ token: ${{ secrets.CODECOV_TOKEN }}
41
+ files: ./coverage/lcov.info
42
+ flags: unittests
43
+ name: codecov-umbrella
44
+ fail_ci_if_error: false
package/README.md CHANGED
@@ -2,8 +2,11 @@
2
2
 
3
3
  > Firebase Admin SDK for Cloudflare Workers and edge runtimes using REST APIs
4
4
 
5
- [![npm version](https://img.shields.io/npm/v/firebase-admin-sdk-v8.svg)](https://www.npmjs.com/package/firebase-admin-sdk-v8)
5
+ [![npm version](https://img.shields.io/npm/v/@prmichaelsen/firebase-admin-sdk-v8.svg)](https://www.npmjs.com/package/@prmichaelsen/firebase-admin-sdk-v8)
6
6
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
7
+ [![Unit Tests](https://github.com/prmichaelsen/firebase-admin-sdk-v8/actions/workflows/test.yml/badge.svg)](https://github.com/prmichaelsen/firebase-admin-sdk-v8/actions/workflows/test.yml)
8
+ [![E2E Tests](https://github.com/prmichaelsen/firebase-admin-sdk-v8/actions/workflows/e2e-tests.yml/badge.svg)](https://github.com/prmichaelsen/firebase-admin-sdk-v8/actions/workflows/e2e-tests.yml)
9
+ [![codecov](https://codecov.io/gh/prmichaelsen/firebase-admin-sdk-v8/branch/mainline/graph/badge.svg)](https://codecov.io/gh/prmichaelsen/firebase-admin-sdk-v8)
7
10
 
8
11
  This library provides Firebase Admin SDK functionality for Cloudflare Workers and other edge runtimes. It uses REST APIs and JWT token generation instead of the Node.js Admin SDK, making it compatible with environments that don't support Node.js.
9
12
 
@@ -25,7 +28,7 @@ This library provides Firebase Admin SDK functionality for Cloudflare Workers an
25
28
  ## 📦 Installation
26
29
 
27
30
  ```bash
28
- npm install firebase-admin-sdk-v8
31
+ npm install @prmichaelsen/firebase-admin-sdk-v8
29
32
  ```
30
33
 
31
34
  ## 🚀 Quick Start
@@ -78,7 +81,7 @@ FIREBASE_PROJECT_ID=your-project-id
78
81
  ### 2. Verify ID Tokens
79
82
 
80
83
  ```typescript
81
- import { verifyIdToken, getUserFromToken } from 'firebase-admin-sdk-v8';
84
+ import { verifyIdToken, getUserFromToken } from '@prmichaelsen/firebase-admin-sdk-v8';
82
85
 
83
86
  const authHeader = request.headers.get('authorization');
84
87
  const idToken = authHeader?.split('Bearer ')[1];
@@ -94,7 +97,7 @@ try {
94
97
  ### 3. Basic Firestore Operations
95
98
 
96
99
  ```typescript
97
- import { setDocument, getDocument, updateDocument, FieldValue } from 'firebase-admin-sdk-v8';
100
+ import { setDocument, getDocument, updateDocument, FieldValue } from '@prmichaelsen/firebase-admin-sdk-v8';
98
101
 
99
102
  // Set a document (create or overwrite)
100
103
  await setDocument('users', 'user123', {
@@ -116,7 +119,7 @@ await updateDocument('users', 'user123', {
116
119
  ### 4. Advanced Queries
117
120
 
118
121
  ```typescript
119
- import { queryDocuments } from 'firebase-admin-sdk-v8';
122
+ import { queryDocuments } from '@prmichaelsen/firebase-admin-sdk-v8';
120
123
 
121
124
  const activeUsers = await queryDocuments('users', {
122
125
  where: [
@@ -397,7 +400,7 @@ export default {
397
400
  ### Leaderboard Example
398
401
 
399
402
  ```typescript
400
- import { queryDocuments, updateDocument, FieldValue } from 'firebase-admin-sdk-v8';
403
+ import { queryDocuments, updateDocument, FieldValue } from '@prmichaelsen/firebase-admin-sdk-v8';
401
404
 
402
405
  async function getTopPlayers(limit = 10) {
403
406
  return await queryDocuments('players', {
@@ -418,7 +421,7 @@ async function updatePlayerScore(playerId: string, points: number) {
418
421
  ### Bulk Operations Example
419
422
 
420
423
  ```typescript
421
- import { batchWrite, FieldValue } from 'firebase-admin-sdk-v8';
424
+ import { batchWrite, FieldValue } from '@prmichaelsen/firebase-admin-sdk-v8';
422
425
 
423
426
  async function bulkUpdateUsers(userIds: string[], updates: any) {
424
427
  const operations = userIds.map(userId => ({
package/dist/index.d.mts CHANGED
@@ -287,18 +287,18 @@ declare function getUserFromToken(idToken: string): Promise<UserInfo>;
287
287
  declare function getAuth(): any;
288
288
 
289
289
  /**
290
- * Firebase Admin SDK v8 - Firestore REST API
291
- * Provides CRUD operations for Firestore using REST API
290
+ * Firebase Admin SDK v8 - Firestore CRUD Operations
291
+ * All Firestore document operations using REST API
292
292
  */
293
293
 
294
294
  /**
295
295
  * Set a document in Firestore (create or overwrite)
296
296
  *
297
- * @param {string} collectionPath - Collection path
298
- * @param {string} documentId - Document ID
299
- * @param {DataObject} data - Document data
300
- * @param {SetOptions} [options] - Set options (merge, mergeFields)
301
- * @returns {Promise<void>}
297
+ * @param collectionPath - Collection path
298
+ * @param documentId - Document ID
299
+ * @param data - Document data
300
+ * @param options - Set options (merge, mergeFields)
301
+ * @returns Promise that resolves when document is set
302
302
  * @throws {Error} If the operation fails
303
303
  *
304
304
  * @example
@@ -317,10 +317,10 @@ declare function setDocument(collectionPath: string, documentId: string, data: D
317
317
  /**
318
318
  * Add a document to Firestore collection
319
319
  *
320
- * @param {string} collectionPath - Collection path (e.g., 'users' or 'users/uid/posts')
321
- * @param {DataObject} data - Document data
322
- * @param {string} [documentId] - Optional document ID (auto-generated if not provided)
323
- * @returns {Promise<DocumentReference>} Document reference with id and path
320
+ * @param collectionPath - Collection path (e.g., 'users' or 'users/uid/posts')
321
+ * @param data - Document data
322
+ * @param documentId - Optional document ID (auto-generated if not provided)
323
+ * @returns Document reference with id and path
324
324
  * @throws {Error} If the operation fails
325
325
  *
326
326
  * @example
@@ -337,9 +337,9 @@ declare function addDocument(collectionPath: string, data: DataObject, documentI
337
337
  /**
338
338
  * Get a document from Firestore
339
339
  *
340
- * @param {string} collectionPath - Collection path
341
- * @param {string} documentId - Document ID
342
- * @returns {Promise<DataObject | null>} Document data or null if not found
340
+ * @param collectionPath - Collection path
341
+ * @param documentId - Document ID
342
+ * @returns Document data or null if not found
343
343
  * @throws {Error} If the operation fails
344
344
  *
345
345
  * @example
@@ -354,10 +354,10 @@ declare function getDocument(collectionPath: string, documentId: string): Promis
354
354
  /**
355
355
  * Update a document in Firestore
356
356
  *
357
- * @param {string} collectionPath - Collection path
358
- * @param {string} documentId - Document ID
359
- * @param {DataObject} data - Data to update
360
- * @returns {Promise<void>}
357
+ * @param collectionPath - Collection path
358
+ * @param documentId - Document ID
359
+ * @param data - Data to update
360
+ * @returns Promise that resolves when document is updated
361
361
  * @throws {Error} If the operation fails
362
362
  *
363
363
  * @example
@@ -372,9 +372,9 @@ declare function updateDocument(collectionPath: string, documentId: string, data
372
372
  /**
373
373
  * Delete a document from Firestore
374
374
  *
375
- * @param {string} collectionPath - Collection path
376
- * @param {string} documentId - Document ID
377
- * @returns {Promise<void>}
375
+ * @param collectionPath - Collection path
376
+ * @param documentId - Document ID
377
+ * @returns Promise that resolves when document is deleted
378
378
  * @throws {Error} If the operation fails
379
379
  *
380
380
  * @example
@@ -386,9 +386,9 @@ declare function deleteDocument(collectionPath: string, documentId: string): Pro
386
386
  /**
387
387
  * Query documents in a collection with advanced filtering
388
388
  *
389
- * @param {string} collectionPath - Collection path
390
- * @param {QueryOptions} [options] - Query options (where, orderBy, limit, etc.)
391
- * @returns {Promise<Array<{ id: string; data: DataObject }>>} Array of documents
389
+ * @param collectionPath - Collection path
390
+ * @param options - Query options (where, orderBy, limit, etc.)
391
+ * @returns Array of documents with id and data
392
392
  * @throws {Error} If the operation fails
393
393
  *
394
394
  * @example
@@ -410,8 +410,8 @@ declare function queryDocuments(collectionPath: string, options?: QueryOptions):
410
410
  /**
411
411
  * Perform batch write operations (set, update, delete)
412
412
  *
413
- * @param {BatchWrite[]} operations - Array of batch operations
414
- * @returns {Promise<BatchWriteResult>} Batch write result
413
+ * @param operations - Array of batch operations
414
+ * @returns Batch write result
415
415
  * @throws {Error} If the operation fails
416
416
  *
417
417
  * @example
package/dist/index.d.ts CHANGED
@@ -287,18 +287,18 @@ declare function getUserFromToken(idToken: string): Promise<UserInfo>;
287
287
  declare function getAuth(): any;
288
288
 
289
289
  /**
290
- * Firebase Admin SDK v8 - Firestore REST API
291
- * Provides CRUD operations for Firestore using REST API
290
+ * Firebase Admin SDK v8 - Firestore CRUD Operations
291
+ * All Firestore document operations using REST API
292
292
  */
293
293
 
294
294
  /**
295
295
  * Set a document in Firestore (create or overwrite)
296
296
  *
297
- * @param {string} collectionPath - Collection path
298
- * @param {string} documentId - Document ID
299
- * @param {DataObject} data - Document data
300
- * @param {SetOptions} [options] - Set options (merge, mergeFields)
301
- * @returns {Promise<void>}
297
+ * @param collectionPath - Collection path
298
+ * @param documentId - Document ID
299
+ * @param data - Document data
300
+ * @param options - Set options (merge, mergeFields)
301
+ * @returns Promise that resolves when document is set
302
302
  * @throws {Error} If the operation fails
303
303
  *
304
304
  * @example
@@ -317,10 +317,10 @@ declare function setDocument(collectionPath: string, documentId: string, data: D
317
317
  /**
318
318
  * Add a document to Firestore collection
319
319
  *
320
- * @param {string} collectionPath - Collection path (e.g., 'users' or 'users/uid/posts')
321
- * @param {DataObject} data - Document data
322
- * @param {string} [documentId] - Optional document ID (auto-generated if not provided)
323
- * @returns {Promise<DocumentReference>} Document reference with id and path
320
+ * @param collectionPath - Collection path (e.g., 'users' or 'users/uid/posts')
321
+ * @param data - Document data
322
+ * @param documentId - Optional document ID (auto-generated if not provided)
323
+ * @returns Document reference with id and path
324
324
  * @throws {Error} If the operation fails
325
325
  *
326
326
  * @example
@@ -337,9 +337,9 @@ declare function addDocument(collectionPath: string, data: DataObject, documentI
337
337
  /**
338
338
  * Get a document from Firestore
339
339
  *
340
- * @param {string} collectionPath - Collection path
341
- * @param {string} documentId - Document ID
342
- * @returns {Promise<DataObject | null>} Document data or null if not found
340
+ * @param collectionPath - Collection path
341
+ * @param documentId - Document ID
342
+ * @returns Document data or null if not found
343
343
  * @throws {Error} If the operation fails
344
344
  *
345
345
  * @example
@@ -354,10 +354,10 @@ declare function getDocument(collectionPath: string, documentId: string): Promis
354
354
  /**
355
355
  * Update a document in Firestore
356
356
  *
357
- * @param {string} collectionPath - Collection path
358
- * @param {string} documentId - Document ID
359
- * @param {DataObject} data - Data to update
360
- * @returns {Promise<void>}
357
+ * @param collectionPath - Collection path
358
+ * @param documentId - Document ID
359
+ * @param data - Data to update
360
+ * @returns Promise that resolves when document is updated
361
361
  * @throws {Error} If the operation fails
362
362
  *
363
363
  * @example
@@ -372,9 +372,9 @@ declare function updateDocument(collectionPath: string, documentId: string, data
372
372
  /**
373
373
  * Delete a document from Firestore
374
374
  *
375
- * @param {string} collectionPath - Collection path
376
- * @param {string} documentId - Document ID
377
- * @returns {Promise<void>}
375
+ * @param collectionPath - Collection path
376
+ * @param documentId - Document ID
377
+ * @returns Promise that resolves when document is deleted
378
378
  * @throws {Error} If the operation fails
379
379
  *
380
380
  * @example
@@ -386,9 +386,9 @@ declare function deleteDocument(collectionPath: string, documentId: string): Pro
386
386
  /**
387
387
  * Query documents in a collection with advanced filtering
388
388
  *
389
- * @param {string} collectionPath - Collection path
390
- * @param {QueryOptions} [options] - Query options (where, orderBy, limit, etc.)
391
- * @returns {Promise<Array<{ id: string; data: DataObject }>>} Array of documents
389
+ * @param collectionPath - Collection path
390
+ * @param options - Query options (where, orderBy, limit, etc.)
391
+ * @returns Array of documents with id and data
392
392
  * @throws {Error} If the operation fails
393
393
  *
394
394
  * @example
@@ -410,8 +410,8 @@ declare function queryDocuments(collectionPath: string, options?: QueryOptions):
410
410
  /**
411
411
  * Perform batch write operations (set, update, delete)
412
412
  *
413
- * @param {BatchWrite[]} operations - Array of batch operations
414
- * @returns {Promise<BatchWriteResult>} Batch write result
413
+ * @param operations - Array of batch operations
414
+ * @returns Batch write result
415
415
  * @throws {Error} If the operation fails
416
416
  *
417
417
  * @example
package/dist/index.js CHANGED
@@ -331,78 +331,6 @@ function getAuth() {
331
331
  };
332
332
  }
333
333
 
334
- // src/token-generation.ts
335
- function base64UrlEncode(str) {
336
- return btoa(str).replace(/=/g, "").replace(/\+/g, "-").replace(/\//g, "_");
337
- }
338
- function base64UrlEncodeBuffer(buffer) {
339
- return btoa(String.fromCharCode(...buffer)).replace(/=/g, "").replace(/\+/g, "-").replace(/\//g, "_");
340
- }
341
- async function createJWT(serviceAccount) {
342
- const now = Math.floor(Date.now() / 1e3);
343
- const expiry = now + 3600;
344
- const header = {
345
- alg: "RS256",
346
- typ: "JWT"
347
- };
348
- const payload = {
349
- iss: serviceAccount.client_email,
350
- sub: serviceAccount.client_email,
351
- aud: serviceAccount.token_uri,
352
- iat: now,
353
- exp: expiry,
354
- scope: "https://www.googleapis.com/auth/cloud-platform https://www.googleapis.com/auth/datastore https://www.googleapis.com/auth/firebase"
355
- };
356
- const encodedHeader = base64UrlEncode(JSON.stringify(header));
357
- const encodedPayload = base64UrlEncode(JSON.stringify(payload));
358
- const unsignedToken = `${encodedHeader}.${encodedPayload}`;
359
- const pemContents = serviceAccount.private_key.replace("-----BEGIN PRIVATE KEY-----", "").replace("-----END PRIVATE KEY-----", "").replace(/\s/g, "");
360
- const binaryDer = Uint8Array.from(atob(pemContents), (c) => c.charCodeAt(0));
361
- const cryptoKey = await crypto.subtle.importKey(
362
- "pkcs8",
363
- binaryDer,
364
- { name: "RSASSA-PKCS1-v1_5", hash: "SHA-256" },
365
- false,
366
- ["sign"]
367
- );
368
- const signature = await crypto.subtle.sign(
369
- "RSASSA-PKCS1-v1_5",
370
- cryptoKey,
371
- new TextEncoder().encode(unsignedToken)
372
- );
373
- const encodedSignature = base64UrlEncodeBuffer(new Uint8Array(signature));
374
- return `${unsignedToken}.${encodedSignature}`;
375
- }
376
- var cachedAccessToken = null;
377
- var tokenExpiry = 0;
378
- async function getAdminAccessToken() {
379
- if (cachedAccessToken && Date.now() < tokenExpiry) {
380
- return cachedAccessToken;
381
- }
382
- const serviceAccount = getServiceAccount();
383
- const jwt = await createJWT(serviceAccount);
384
- const response = await fetch(serviceAccount.token_uri, {
385
- method: "POST",
386
- headers: { "Content-Type": "application/x-www-form-urlencoded" },
387
- body: new URLSearchParams({
388
- grant_type: "urn:ietf:params:oauth:grant-type:jwt-bearer",
389
- assertion: jwt
390
- })
391
- });
392
- if (!response.ok) {
393
- const errorText = await response.text();
394
- throw new Error(`Failed to get access token: ${errorText}`);
395
- }
396
- const data = await response.json();
397
- cachedAccessToken = data.access_token;
398
- tokenExpiry = Date.now() + data.expires_in * 1e3 - 6e4;
399
- return cachedAccessToken;
400
- }
401
- function clearTokenCache() {
402
- cachedAccessToken = null;
403
- tokenExpiry = 0;
404
- }
405
-
406
334
  // src/field-value.ts
407
335
  function serverTimestamp() {
408
336
  return {
@@ -679,7 +607,79 @@ function removeFieldTransforms(data) {
679
607
  return result;
680
608
  }
681
609
 
682
- // src/firestore-rest.ts
610
+ // src/token-generation.ts
611
+ function base64UrlEncode(str) {
612
+ return btoa(str).replace(/=/g, "").replace(/\+/g, "-").replace(/\//g, "_");
613
+ }
614
+ function base64UrlEncodeBuffer(buffer) {
615
+ return btoa(String.fromCharCode(...buffer)).replace(/=/g, "").replace(/\+/g, "-").replace(/\//g, "_");
616
+ }
617
+ async function createJWT(serviceAccount) {
618
+ const now = Math.floor(Date.now() / 1e3);
619
+ const expiry = now + 3600;
620
+ const header = {
621
+ alg: "RS256",
622
+ typ: "JWT"
623
+ };
624
+ const payload = {
625
+ iss: serviceAccount.client_email,
626
+ sub: serviceAccount.client_email,
627
+ aud: serviceAccount.token_uri,
628
+ iat: now,
629
+ exp: expiry,
630
+ scope: "https://www.googleapis.com/auth/cloud-platform https://www.googleapis.com/auth/datastore https://www.googleapis.com/auth/firebase"
631
+ };
632
+ const encodedHeader = base64UrlEncode(JSON.stringify(header));
633
+ const encodedPayload = base64UrlEncode(JSON.stringify(payload));
634
+ const unsignedToken = `${encodedHeader}.${encodedPayload}`;
635
+ const pemContents = serviceAccount.private_key.replace("-----BEGIN PRIVATE KEY-----", "").replace("-----END PRIVATE KEY-----", "").replace(/\s/g, "");
636
+ const binaryDer = Uint8Array.from(atob(pemContents), (c) => c.charCodeAt(0));
637
+ const cryptoKey = await crypto.subtle.importKey(
638
+ "pkcs8",
639
+ binaryDer,
640
+ { name: "RSASSA-PKCS1-v1_5", hash: "SHA-256" },
641
+ false,
642
+ ["sign"]
643
+ );
644
+ const signature = await crypto.subtle.sign(
645
+ "RSASSA-PKCS1-v1_5",
646
+ cryptoKey,
647
+ new TextEncoder().encode(unsignedToken)
648
+ );
649
+ const encodedSignature = base64UrlEncodeBuffer(new Uint8Array(signature));
650
+ return `${unsignedToken}.${encodedSignature}`;
651
+ }
652
+ var cachedAccessToken = null;
653
+ var tokenExpiry = 0;
654
+ async function getAdminAccessToken() {
655
+ if (cachedAccessToken && Date.now() < tokenExpiry) {
656
+ return cachedAccessToken;
657
+ }
658
+ const serviceAccount = getServiceAccount();
659
+ const jwt = await createJWT(serviceAccount);
660
+ const response = await fetch(serviceAccount.token_uri, {
661
+ method: "POST",
662
+ headers: { "Content-Type": "application/x-www-form-urlencoded" },
663
+ body: new URLSearchParams({
664
+ grant_type: "urn:ietf:params:oauth:grant-type:jwt-bearer",
665
+ assertion: jwt
666
+ })
667
+ });
668
+ if (!response.ok) {
669
+ const errorText = await response.text();
670
+ throw new Error(`Failed to get access token: ${errorText}`);
671
+ }
672
+ const data = await response.json();
673
+ cachedAccessToken = data.access_token;
674
+ tokenExpiry = Date.now() + data.expires_in * 1e3 - 6e4;
675
+ return cachedAccessToken;
676
+ }
677
+ function clearTokenCache() {
678
+ cachedAccessToken = null;
679
+ tokenExpiry = 0;
680
+ }
681
+
682
+ // src/firestore/operations.ts
683
683
  var FIRESTORE_API = "https://firestore.googleapis.com/v1";
684
684
  async function commitWrites(writes) {
685
685
  const accessToken = await getAdminAccessToken();
package/dist/index.mjs CHANGED
@@ -288,78 +288,6 @@ function getAuth() {
288
288
  };
289
289
  }
290
290
 
291
- // src/token-generation.ts
292
- function base64UrlEncode(str) {
293
- return btoa(str).replace(/=/g, "").replace(/\+/g, "-").replace(/\//g, "_");
294
- }
295
- function base64UrlEncodeBuffer(buffer) {
296
- return btoa(String.fromCharCode(...buffer)).replace(/=/g, "").replace(/\+/g, "-").replace(/\//g, "_");
297
- }
298
- async function createJWT(serviceAccount) {
299
- const now = Math.floor(Date.now() / 1e3);
300
- const expiry = now + 3600;
301
- const header = {
302
- alg: "RS256",
303
- typ: "JWT"
304
- };
305
- const payload = {
306
- iss: serviceAccount.client_email,
307
- sub: serviceAccount.client_email,
308
- aud: serviceAccount.token_uri,
309
- iat: now,
310
- exp: expiry,
311
- scope: "https://www.googleapis.com/auth/cloud-platform https://www.googleapis.com/auth/datastore https://www.googleapis.com/auth/firebase"
312
- };
313
- const encodedHeader = base64UrlEncode(JSON.stringify(header));
314
- const encodedPayload = base64UrlEncode(JSON.stringify(payload));
315
- const unsignedToken = `${encodedHeader}.${encodedPayload}`;
316
- const pemContents = serviceAccount.private_key.replace("-----BEGIN PRIVATE KEY-----", "").replace("-----END PRIVATE KEY-----", "").replace(/\s/g, "");
317
- const binaryDer = Uint8Array.from(atob(pemContents), (c) => c.charCodeAt(0));
318
- const cryptoKey = await crypto.subtle.importKey(
319
- "pkcs8",
320
- binaryDer,
321
- { name: "RSASSA-PKCS1-v1_5", hash: "SHA-256" },
322
- false,
323
- ["sign"]
324
- );
325
- const signature = await crypto.subtle.sign(
326
- "RSASSA-PKCS1-v1_5",
327
- cryptoKey,
328
- new TextEncoder().encode(unsignedToken)
329
- );
330
- const encodedSignature = base64UrlEncodeBuffer(new Uint8Array(signature));
331
- return `${unsignedToken}.${encodedSignature}`;
332
- }
333
- var cachedAccessToken = null;
334
- var tokenExpiry = 0;
335
- async function getAdminAccessToken() {
336
- if (cachedAccessToken && Date.now() < tokenExpiry) {
337
- return cachedAccessToken;
338
- }
339
- const serviceAccount = getServiceAccount();
340
- const jwt = await createJWT(serviceAccount);
341
- const response = await fetch(serviceAccount.token_uri, {
342
- method: "POST",
343
- headers: { "Content-Type": "application/x-www-form-urlencoded" },
344
- body: new URLSearchParams({
345
- grant_type: "urn:ietf:params:oauth:grant-type:jwt-bearer",
346
- assertion: jwt
347
- })
348
- });
349
- if (!response.ok) {
350
- const errorText = await response.text();
351
- throw new Error(`Failed to get access token: ${errorText}`);
352
- }
353
- const data = await response.json();
354
- cachedAccessToken = data.access_token;
355
- tokenExpiry = Date.now() + data.expires_in * 1e3 - 6e4;
356
- return cachedAccessToken;
357
- }
358
- function clearTokenCache() {
359
- cachedAccessToken = null;
360
- tokenExpiry = 0;
361
- }
362
-
363
291
  // src/field-value.ts
364
292
  function serverTimestamp() {
365
293
  return {
@@ -636,7 +564,79 @@ function removeFieldTransforms(data) {
636
564
  return result;
637
565
  }
638
566
 
639
- // src/firestore-rest.ts
567
+ // src/token-generation.ts
568
+ function base64UrlEncode(str) {
569
+ return btoa(str).replace(/=/g, "").replace(/\+/g, "-").replace(/\//g, "_");
570
+ }
571
+ function base64UrlEncodeBuffer(buffer) {
572
+ return btoa(String.fromCharCode(...buffer)).replace(/=/g, "").replace(/\+/g, "-").replace(/\//g, "_");
573
+ }
574
+ async function createJWT(serviceAccount) {
575
+ const now = Math.floor(Date.now() / 1e3);
576
+ const expiry = now + 3600;
577
+ const header = {
578
+ alg: "RS256",
579
+ typ: "JWT"
580
+ };
581
+ const payload = {
582
+ iss: serviceAccount.client_email,
583
+ sub: serviceAccount.client_email,
584
+ aud: serviceAccount.token_uri,
585
+ iat: now,
586
+ exp: expiry,
587
+ scope: "https://www.googleapis.com/auth/cloud-platform https://www.googleapis.com/auth/datastore https://www.googleapis.com/auth/firebase"
588
+ };
589
+ const encodedHeader = base64UrlEncode(JSON.stringify(header));
590
+ const encodedPayload = base64UrlEncode(JSON.stringify(payload));
591
+ const unsignedToken = `${encodedHeader}.${encodedPayload}`;
592
+ const pemContents = serviceAccount.private_key.replace("-----BEGIN PRIVATE KEY-----", "").replace("-----END PRIVATE KEY-----", "").replace(/\s/g, "");
593
+ const binaryDer = Uint8Array.from(atob(pemContents), (c) => c.charCodeAt(0));
594
+ const cryptoKey = await crypto.subtle.importKey(
595
+ "pkcs8",
596
+ binaryDer,
597
+ { name: "RSASSA-PKCS1-v1_5", hash: "SHA-256" },
598
+ false,
599
+ ["sign"]
600
+ );
601
+ const signature = await crypto.subtle.sign(
602
+ "RSASSA-PKCS1-v1_5",
603
+ cryptoKey,
604
+ new TextEncoder().encode(unsignedToken)
605
+ );
606
+ const encodedSignature = base64UrlEncodeBuffer(new Uint8Array(signature));
607
+ return `${unsignedToken}.${encodedSignature}`;
608
+ }
609
+ var cachedAccessToken = null;
610
+ var tokenExpiry = 0;
611
+ async function getAdminAccessToken() {
612
+ if (cachedAccessToken && Date.now() < tokenExpiry) {
613
+ return cachedAccessToken;
614
+ }
615
+ const serviceAccount = getServiceAccount();
616
+ const jwt = await createJWT(serviceAccount);
617
+ const response = await fetch(serviceAccount.token_uri, {
618
+ method: "POST",
619
+ headers: { "Content-Type": "application/x-www-form-urlencoded" },
620
+ body: new URLSearchParams({
621
+ grant_type: "urn:ietf:params:oauth:grant-type:jwt-bearer",
622
+ assertion: jwt
623
+ })
624
+ });
625
+ if (!response.ok) {
626
+ const errorText = await response.text();
627
+ throw new Error(`Failed to get access token: ${errorText}`);
628
+ }
629
+ const data = await response.json();
630
+ cachedAccessToken = data.access_token;
631
+ tokenExpiry = Date.now() + data.expires_in * 1e3 - 6e4;
632
+ return cachedAccessToken;
633
+ }
634
+ function clearTokenCache() {
635
+ cachedAccessToken = null;
636
+ tokenExpiry = 0;
637
+ }
638
+
639
+ // src/firestore/operations.ts
640
640
  var FIRESTORE_API = "https://firestore.googleapis.com/v1";
641
641
  async function commitWrites(writes) {
642
642
  const accessToken = await getAdminAccessToken();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@prmichaelsen/firebase-admin-sdk-v8",
3
- "version": "2.0.17",
3
+ "version": "2.0.20",
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",
package/firebase.json DELETED
@@ -1,9 +0,0 @@
1
- {
2
- "projects": {
3
- "default": "prmichaelsen-firebase-e2e"
4
- },
5
- "firestore": {
6
- "rules": "firestore.rules",
7
- "indexes": "firestore.indexes.json"
8
- }
9
- }
@@ -1,33 +0,0 @@
1
- {
2
- "indexes": [
3
- {
4
- "collectionGroup": "e2e-tests",
5
- "queryScope": "COLLECTION",
6
- "fields": [
7
- {
8
- "fieldPath": "_test",
9
- "order": "ASCENDING"
10
- },
11
- {
12
- "fieldPath": "age",
13
- "order": "ASCENDING"
14
- }
15
- ]
16
- },
17
- {
18
- "collectionGroup": "messages",
19
- "queryScope": "COLLECTION",
20
- "fields": [
21
- {
22
- "fieldPath": "_test",
23
- "order": "ASCENDING"
24
- },
25
- {
26
- "fieldPath": "timestamp",
27
- "order": "ASCENDING"
28
- }
29
- ]
30
- }
31
- ],
32
- "fieldOverrides": []
33
- }
package/firestore.rules DELETED
@@ -1,11 +0,0 @@
1
- rules_version = '2';
2
-
3
- service cloud.firestore {
4
- match /databases/{database}/documents {
5
- // Allow all reads and writes for testing
6
- // WARNING: These rules are for testing only!
7
- match /{document=**} {
8
- allow read, write: if true;
9
- }
10
- }
11
- }
package/jest.config.js DELETED
@@ -1,12 +0,0 @@
1
- module.exports = {
2
- preset: 'ts-jest',
3
- testEnvironment: 'node',
4
- roots: ['<rootDir>/src'],
5
- testMatch: ['**/*.spec.ts'],
6
- moduleFileExtensions: ['ts', 'js'],
7
- collectCoverageFrom: [
8
- 'src/**/*.ts',
9
- '!src/**/*.d.ts',
10
- '!src/**/*.spec.ts',
11
- ],
12
- };
@@ -1,14 +0,0 @@
1
- module.exports = {
2
- preset: 'ts-jest',
3
- testEnvironment: 'node',
4
- testMatch: ['**/*.e2e.ts'],
5
- testTimeout: 30000, // 30 seconds for real API calls
6
- roots: ['<rootDir>/src'],
7
- collectCoverageFrom: [
8
- 'src/**/*.ts',
9
- '!src/**/*.spec.ts',
10
- '!src/**/*.e2e.ts',
11
- '!src/types.ts',
12
- '!src/index.ts',
13
- ],
14
- };
@@ -1,13 +0,0 @@
1
- {
2
- "type": "service_account",
3
- "project_id": "prmichaelsen-firebase-e2e",
4
- "private_key_id": "84caabb8515ec5fab4caa53a0f85405c270d5cd4",
5
- "private_key": "-----BEGIN PRIVATE KEY-----\nMIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQC/0n8V7m7vCQ9/\n5FGYL5V20pQxliUMF/ycNBSV/xHrXAvTMxyMEjFK8Tg9sBLdYhuxC488lnwm3/o+\nn5JkckQ2AyzXX6mi+camwxdLXmT97kXqdFLisO4PY7Fv6HvvMgRQqysj7aJkyiz7\nrAs0DpQq1L70sBFdVLiScPH9FFnuV0EeyOTuVgYwGYk1L76/U7bb3WLNCmcmtG+Y\nt4BNzyMqFgtIdXCQFVBQf9HXy3x5xk8a1rrkf7pEuiwK0PXCDIVH7GYX4xCiQ1qs\nYF4IunNFhcwu1hk5XTIDSVrIn/qLPMZvEcBqHxIJd5JvePjx5hPP81b1mrGUVske\nsaRimjxxAgMBAAECggEAAWNKVG7KslkMRH5y5q56yYbMLVsAPp5SehDYZfNtU5jG\nucr2CxS7SDxcOKS0UOdmUDlyBQaztED3mbS5hZeGuHtSZjvaHtnpdDMXe+NIHhw3\nY520LHwKOjtG69/bPFz4x1qjBRoG4Zgi4NlFpXqbhinO4zdTkNYi6xBSd+R0oshP\nSo59lQvs8e3bMD4f4uTl7JxUifjVM64R3gCkH6AbGqU01wg8UzsllCf0fQM04w8i\nq1oVjhBVWYYVE7w2H+EU+LdiRSRET+Vrk5dtBL3vI6yckvGmxTBMnkK2gTFAAR6F\nnN4NKlUsiFol9WVRY3tdRNOnStjlORDyerSEFQaqhwKBgQDfAa5ddxBVbxUS3E2a\nnGJRA44fYlRyZf9KnJdz8uWsCvuG7UYbe2bp7TxlpEXdTG6Vqu/hhYWd/msmhupZ\nc+YFg1HWE1Ee+nQv7iwAD+okfncnKGIC65XjwaSEMRGrmtxBL6Qq4yxn/Yug/8YR\nniGRQKuMtJ2ZA0p41vI6Mkt7kwKBgQDcM7eQQ5482FcXuOfUlPoiWnd+pF5mNrU1\ngKoxdsXMYRKyTGMPLrOKqsf6kisQUlA48vWRCQOa9QBSjHktfOt2EZ3mNQz5k3AR\n/dIjCGf+ATr2P9Sc0uo7sA20XB62wmG+t2A/vxf9WzBAgdDk6nENB4nz6lL9xcak\ngvVt2vXSawKBgQCV6JRk4f/J3oVFC3DjaRKyMPid4kSwLh6B8mfhGrwHfc59cgz5\ntmeFAuPh057fV1zTIXhlmpMqlPdEi9cHUOCkfhVKGewjLetiuPE9DXWxGI5SdVQF\ncIZu9yH3duDRAaXj7/mklteoBAmTrbxg5XLdKKLpUBTM4ihyuNNWCa8yHwKBgCYK\noTnBFMM6NMGaZiKpohTxQBeW2eAar2+QzNZCyKUoWAyJecuTq9zW6Dl3qwzky4sr\nHhVyUzcgAHBCaGTdYehB3t94ZsdvGztgeD8pIp4VJFSKbnaxUVoCbjusdnnoVu6V\ny4D3yHMyn8FlK+uAPQudM835u2CwHEMrhK731uQFAoGBAKO0crVWuY1EP1RLSyLK\nIS3uUgbauBbQlE4ZPBTi84vXCZjN/53obFgkPHJ5tdjxj6C8x58jgHg/Hufyvbib\ngOQQxadM90UBumlzA8lV7A9jvL2ryfop9ketnVO5hPvB4O8iw9Z1R+25FeM9ZFJI\nqmEjiy9Nhmvpvur+FZ85qcRm\n-----END PRIVATE KEY-----\n",
6
- "client_email": "firebase-adminsdk-fbsvc@prmichaelsen-firebase-e2e.iam.gserviceaccount.com",
7
- "client_id": "103889460014910902622",
8
- "auth_uri": "https://accounts.google.com/o/oauth2/auth",
9
- "token_uri": "https://oauth2.googleapis.com/token",
10
- "auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs",
11
- "client_x509_cert_url": "https://www.googleapis.com/robot/v1/metadata/x509/firebase-adminsdk-fbsvc%40prmichaelsen-firebase-e2e.iam.gserviceaccount.com",
12
- "universe_domain": "googleapis.com"
13
- }