@prmichaelsen/firebase-admin-sdk-v8 2.0.21 → 2.0.22
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.d.mts +184 -1
- package/dist/index.d.ts +184 -1
- package/dist/index.js +333 -0
- package/dist/index.mjs +326 -0
- package/package.json +1 -1
- package/resources/puppy.png +0 -0
package/dist/index.d.mts
CHANGED
|
@@ -425,6 +425,189 @@ declare function queryDocuments(collectionPath: string, options?: QueryOptions):
|
|
|
425
425
|
*/
|
|
426
426
|
declare function batchWrite(operations: BatchWrite[]): Promise<BatchWriteResult>;
|
|
427
427
|
|
|
428
|
+
/**
|
|
429
|
+
* Firebase Storage client using Google Cloud Storage REST API
|
|
430
|
+
* Compatible with edge runtimes (Cloudflare Workers, Vercel Edge, etc.)
|
|
431
|
+
*/
|
|
432
|
+
/**
|
|
433
|
+
* Options for uploading files
|
|
434
|
+
*/
|
|
435
|
+
interface UploadOptions {
|
|
436
|
+
contentType?: string;
|
|
437
|
+
metadata?: Record<string, string>;
|
|
438
|
+
public?: boolean;
|
|
439
|
+
}
|
|
440
|
+
/**
|
|
441
|
+
* Options for downloading files
|
|
442
|
+
*/
|
|
443
|
+
interface DownloadOptions {
|
|
444
|
+
responseType?: 'arraybuffer' | 'blob';
|
|
445
|
+
}
|
|
446
|
+
/**
|
|
447
|
+
* Options for listing files
|
|
448
|
+
*/
|
|
449
|
+
interface ListOptions {
|
|
450
|
+
prefix?: string;
|
|
451
|
+
delimiter?: string;
|
|
452
|
+
maxResults?: number;
|
|
453
|
+
pageToken?: string;
|
|
454
|
+
}
|
|
455
|
+
/**
|
|
456
|
+
* File metadata returned by Storage API
|
|
457
|
+
*/
|
|
458
|
+
interface FileMetadata {
|
|
459
|
+
name: string;
|
|
460
|
+
bucket: string;
|
|
461
|
+
size: string;
|
|
462
|
+
contentType: string;
|
|
463
|
+
timeCreated: string;
|
|
464
|
+
updated: string;
|
|
465
|
+
md5Hash: string;
|
|
466
|
+
metadata?: Record<string, string>;
|
|
467
|
+
}
|
|
468
|
+
/**
|
|
469
|
+
* Result of listing files
|
|
470
|
+
*/
|
|
471
|
+
interface ListFilesResult {
|
|
472
|
+
files: FileMetadata[];
|
|
473
|
+
nextPageToken?: string;
|
|
474
|
+
}
|
|
475
|
+
/**
|
|
476
|
+
* Upload a file to Firebase Storage
|
|
477
|
+
*
|
|
478
|
+
* @param path - File path in storage (e.g., 'images/photo.jpg')
|
|
479
|
+
* @param data - File data as ArrayBuffer, Uint8Array, or Blob
|
|
480
|
+
* @param options - Upload options (contentType, metadata, public)
|
|
481
|
+
* @returns File metadata
|
|
482
|
+
*
|
|
483
|
+
* @example
|
|
484
|
+
* ```typescript
|
|
485
|
+
* const data = new TextEncoder().encode('Hello, Storage!');
|
|
486
|
+
* const metadata = await uploadFile('files/hello.txt', data, {
|
|
487
|
+
* contentType: 'text/plain',
|
|
488
|
+
* metadata: { userId: '123' },
|
|
489
|
+
* });
|
|
490
|
+
* ```
|
|
491
|
+
*/
|
|
492
|
+
declare function uploadFile(path: string, data: ArrayBuffer | Uint8Array | Blob, options?: UploadOptions): Promise<FileMetadata>;
|
|
493
|
+
/**
|
|
494
|
+
* Download a file from Firebase Storage
|
|
495
|
+
*
|
|
496
|
+
* @param path - File path in storage
|
|
497
|
+
* @param options - Download options
|
|
498
|
+
* @returns File data as ArrayBuffer
|
|
499
|
+
*
|
|
500
|
+
* @example
|
|
501
|
+
* ```typescript
|
|
502
|
+
* const data = await downloadFile('files/hello.txt');
|
|
503
|
+
* const text = new TextDecoder().decode(data);
|
|
504
|
+
* console.log(text); // "Hello, Storage!"
|
|
505
|
+
* ```
|
|
506
|
+
*/
|
|
507
|
+
declare function downloadFile(path: string, _options?: DownloadOptions): Promise<ArrayBuffer>;
|
|
508
|
+
/**
|
|
509
|
+
* Delete a file from Firebase Storage
|
|
510
|
+
*
|
|
511
|
+
* @param path - File path in storage
|
|
512
|
+
*
|
|
513
|
+
* @example
|
|
514
|
+
* ```typescript
|
|
515
|
+
* await deleteFile('files/hello.txt');
|
|
516
|
+
* ```
|
|
517
|
+
*/
|
|
518
|
+
declare function deleteFile(path: string): Promise<void>;
|
|
519
|
+
/**
|
|
520
|
+
* Get file metadata from Firebase Storage
|
|
521
|
+
*
|
|
522
|
+
* @param path - File path in storage
|
|
523
|
+
* @returns File metadata
|
|
524
|
+
*
|
|
525
|
+
* @example
|
|
526
|
+
* ```typescript
|
|
527
|
+
* const metadata = await getFileMetadata('files/hello.txt');
|
|
528
|
+
* console.log(metadata.size, metadata.contentType);
|
|
529
|
+
* ```
|
|
530
|
+
*/
|
|
531
|
+
declare function getFileMetadata(path: string): Promise<FileMetadata>;
|
|
532
|
+
/**
|
|
533
|
+
* List files in Firebase Storage
|
|
534
|
+
*
|
|
535
|
+
* @param options - List options (prefix, delimiter, maxResults, pageToken)
|
|
536
|
+
* @returns List of files and optional next page token
|
|
537
|
+
*
|
|
538
|
+
* @example
|
|
539
|
+
* ```typescript
|
|
540
|
+
* // List all files with prefix
|
|
541
|
+
* const { files } = await listFiles({ prefix: 'images/' });
|
|
542
|
+
*
|
|
543
|
+
* // List with pagination
|
|
544
|
+
* const { files, nextPageToken } = await listFiles({ maxResults: 10 });
|
|
545
|
+
* if (nextPageToken) {
|
|
546
|
+
* const nextPage = await listFiles({ pageToken: nextPageToken });
|
|
547
|
+
* }
|
|
548
|
+
* ```
|
|
549
|
+
*/
|
|
550
|
+
declare function listFiles(options?: ListOptions): Promise<ListFilesResult>;
|
|
551
|
+
/**
|
|
552
|
+
* Check if a file exists in Firebase Storage
|
|
553
|
+
*
|
|
554
|
+
* @param path - File path in storage
|
|
555
|
+
* @returns True if file exists, false otherwise
|
|
556
|
+
*
|
|
557
|
+
* @example
|
|
558
|
+
* ```typescript
|
|
559
|
+
* if (await fileExists('files/hello.txt')) {
|
|
560
|
+
* console.log('File exists!');
|
|
561
|
+
* }
|
|
562
|
+
* ```
|
|
563
|
+
*/
|
|
564
|
+
declare function fileExists(path: string): Promise<boolean>;
|
|
565
|
+
|
|
566
|
+
/**
|
|
567
|
+
* Generate signed URLs for temporary access to Firebase Storage files
|
|
568
|
+
* Uses Google Cloud Storage V4 signing process
|
|
569
|
+
*/
|
|
570
|
+
/**
|
|
571
|
+
* Options for generating signed URLs
|
|
572
|
+
*/
|
|
573
|
+
interface SignedUrlOptions {
|
|
574
|
+
action: 'read' | 'write' | 'delete';
|
|
575
|
+
expires: Date | number;
|
|
576
|
+
contentType?: string;
|
|
577
|
+
responseDisposition?: string;
|
|
578
|
+
responseType?: string;
|
|
579
|
+
}
|
|
580
|
+
/**
|
|
581
|
+
* Generate a signed URL for temporary access to a Storage file
|
|
582
|
+
*
|
|
583
|
+
* Uses Google Cloud Storage V4 signing process
|
|
584
|
+
*
|
|
585
|
+
* @param path - File path in storage (e.g., 'images/photo.jpg')
|
|
586
|
+
* @param options - Signed URL options
|
|
587
|
+
* @returns Signed URL that can be used without authentication
|
|
588
|
+
*
|
|
589
|
+
* @example
|
|
590
|
+
* ```typescript
|
|
591
|
+
* // Generate read URL valid for 1 hour
|
|
592
|
+
* const url = await generateSignedUrl('files/hello.txt', {
|
|
593
|
+
* action: 'read',
|
|
594
|
+
* expires: 3600,
|
|
595
|
+
* });
|
|
596
|
+
*
|
|
597
|
+
* // Generate write URL with content type
|
|
598
|
+
* const uploadUrl = await generateSignedUrl('files/upload.jpg', {
|
|
599
|
+
* action: 'write',
|
|
600
|
+
* expires: 1800, // 30 minutes
|
|
601
|
+
* contentType: 'image/jpeg',
|
|
602
|
+
* });
|
|
603
|
+
*
|
|
604
|
+
* // Use the URL (no auth needed)
|
|
605
|
+
* const response = await fetch(url);
|
|
606
|
+
* const data = await response.arrayBuffer();
|
|
607
|
+
* ```
|
|
608
|
+
*/
|
|
609
|
+
declare function generateSignedUrl(path: string, options: SignedUrlOptions): Promise<string>;
|
|
610
|
+
|
|
428
611
|
/**
|
|
429
612
|
* Firebase Admin SDK v8 - Field Value Helpers
|
|
430
613
|
* Special field values for Firestore operations
|
|
@@ -522,4 +705,4 @@ declare function getAdminAccessToken(): Promise<string>;
|
|
|
522
705
|
*/
|
|
523
706
|
declare function clearTokenCache(): void;
|
|
524
707
|
|
|
525
|
-
export { type BatchWrite, type BatchWriteResult, type DataObject, type DecodedIdToken, type DocumentReference, FieldValue, type FieldValue$1 as FieldValueSentinel, FieldValueType, type FirestoreDocument, type FirestoreValue, type QueryFilter, type QueryOptions, type QueryOrder, type ServiceAccount, type SetOptions, type TokenResponse, type UpdateOptions, type UserInfo, type WhereFilterOp, addDocument, batchWrite, clearConfig, clearTokenCache, deleteDocument, getAdminAccessToken, getAuth, getConfig, getDocument, getProjectId, getServiceAccount, getUserFromToken, initializeApp, queryDocuments, setDocument, updateDocument, verifyIdToken };
|
|
708
|
+
export { type BatchWrite, type BatchWriteResult, 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 QueryFilter, type QueryOptions, type QueryOrder, type ServiceAccount, type SetOptions, type SignedUrlOptions, type TokenResponse, type UpdateOptions, type UploadOptions, type UserInfo, type WhereFilterOp, addDocument, batchWrite, clearConfig, clearTokenCache, deleteDocument, deleteFile, downloadFile, fileExists, generateSignedUrl, getAdminAccessToken, getAuth, getConfig, getDocument, getFileMetadata, getProjectId, getServiceAccount, getUserFromToken, initializeApp, listFiles, queryDocuments, setDocument, updateDocument, uploadFile, verifyIdToken };
|
package/dist/index.d.ts
CHANGED
|
@@ -425,6 +425,189 @@ declare function queryDocuments(collectionPath: string, options?: QueryOptions):
|
|
|
425
425
|
*/
|
|
426
426
|
declare function batchWrite(operations: BatchWrite[]): Promise<BatchWriteResult>;
|
|
427
427
|
|
|
428
|
+
/**
|
|
429
|
+
* Firebase Storage client using Google Cloud Storage REST API
|
|
430
|
+
* Compatible with edge runtimes (Cloudflare Workers, Vercel Edge, etc.)
|
|
431
|
+
*/
|
|
432
|
+
/**
|
|
433
|
+
* Options for uploading files
|
|
434
|
+
*/
|
|
435
|
+
interface UploadOptions {
|
|
436
|
+
contentType?: string;
|
|
437
|
+
metadata?: Record<string, string>;
|
|
438
|
+
public?: boolean;
|
|
439
|
+
}
|
|
440
|
+
/**
|
|
441
|
+
* Options for downloading files
|
|
442
|
+
*/
|
|
443
|
+
interface DownloadOptions {
|
|
444
|
+
responseType?: 'arraybuffer' | 'blob';
|
|
445
|
+
}
|
|
446
|
+
/**
|
|
447
|
+
* Options for listing files
|
|
448
|
+
*/
|
|
449
|
+
interface ListOptions {
|
|
450
|
+
prefix?: string;
|
|
451
|
+
delimiter?: string;
|
|
452
|
+
maxResults?: number;
|
|
453
|
+
pageToken?: string;
|
|
454
|
+
}
|
|
455
|
+
/**
|
|
456
|
+
* File metadata returned by Storage API
|
|
457
|
+
*/
|
|
458
|
+
interface FileMetadata {
|
|
459
|
+
name: string;
|
|
460
|
+
bucket: string;
|
|
461
|
+
size: string;
|
|
462
|
+
contentType: string;
|
|
463
|
+
timeCreated: string;
|
|
464
|
+
updated: string;
|
|
465
|
+
md5Hash: string;
|
|
466
|
+
metadata?: Record<string, string>;
|
|
467
|
+
}
|
|
468
|
+
/**
|
|
469
|
+
* Result of listing files
|
|
470
|
+
*/
|
|
471
|
+
interface ListFilesResult {
|
|
472
|
+
files: FileMetadata[];
|
|
473
|
+
nextPageToken?: string;
|
|
474
|
+
}
|
|
475
|
+
/**
|
|
476
|
+
* Upload a file to Firebase Storage
|
|
477
|
+
*
|
|
478
|
+
* @param path - File path in storage (e.g., 'images/photo.jpg')
|
|
479
|
+
* @param data - File data as ArrayBuffer, Uint8Array, or Blob
|
|
480
|
+
* @param options - Upload options (contentType, metadata, public)
|
|
481
|
+
* @returns File metadata
|
|
482
|
+
*
|
|
483
|
+
* @example
|
|
484
|
+
* ```typescript
|
|
485
|
+
* const data = new TextEncoder().encode('Hello, Storage!');
|
|
486
|
+
* const metadata = await uploadFile('files/hello.txt', data, {
|
|
487
|
+
* contentType: 'text/plain',
|
|
488
|
+
* metadata: { userId: '123' },
|
|
489
|
+
* });
|
|
490
|
+
* ```
|
|
491
|
+
*/
|
|
492
|
+
declare function uploadFile(path: string, data: ArrayBuffer | Uint8Array | Blob, options?: UploadOptions): Promise<FileMetadata>;
|
|
493
|
+
/**
|
|
494
|
+
* Download a file from Firebase Storage
|
|
495
|
+
*
|
|
496
|
+
* @param path - File path in storage
|
|
497
|
+
* @param options - Download options
|
|
498
|
+
* @returns File data as ArrayBuffer
|
|
499
|
+
*
|
|
500
|
+
* @example
|
|
501
|
+
* ```typescript
|
|
502
|
+
* const data = await downloadFile('files/hello.txt');
|
|
503
|
+
* const text = new TextDecoder().decode(data);
|
|
504
|
+
* console.log(text); // "Hello, Storage!"
|
|
505
|
+
* ```
|
|
506
|
+
*/
|
|
507
|
+
declare function downloadFile(path: string, _options?: DownloadOptions): Promise<ArrayBuffer>;
|
|
508
|
+
/**
|
|
509
|
+
* Delete a file from Firebase Storage
|
|
510
|
+
*
|
|
511
|
+
* @param path - File path in storage
|
|
512
|
+
*
|
|
513
|
+
* @example
|
|
514
|
+
* ```typescript
|
|
515
|
+
* await deleteFile('files/hello.txt');
|
|
516
|
+
* ```
|
|
517
|
+
*/
|
|
518
|
+
declare function deleteFile(path: string): Promise<void>;
|
|
519
|
+
/**
|
|
520
|
+
* Get file metadata from Firebase Storage
|
|
521
|
+
*
|
|
522
|
+
* @param path - File path in storage
|
|
523
|
+
* @returns File metadata
|
|
524
|
+
*
|
|
525
|
+
* @example
|
|
526
|
+
* ```typescript
|
|
527
|
+
* const metadata = await getFileMetadata('files/hello.txt');
|
|
528
|
+
* console.log(metadata.size, metadata.contentType);
|
|
529
|
+
* ```
|
|
530
|
+
*/
|
|
531
|
+
declare function getFileMetadata(path: string): Promise<FileMetadata>;
|
|
532
|
+
/**
|
|
533
|
+
* List files in Firebase Storage
|
|
534
|
+
*
|
|
535
|
+
* @param options - List options (prefix, delimiter, maxResults, pageToken)
|
|
536
|
+
* @returns List of files and optional next page token
|
|
537
|
+
*
|
|
538
|
+
* @example
|
|
539
|
+
* ```typescript
|
|
540
|
+
* // List all files with prefix
|
|
541
|
+
* const { files } = await listFiles({ prefix: 'images/' });
|
|
542
|
+
*
|
|
543
|
+
* // List with pagination
|
|
544
|
+
* const { files, nextPageToken } = await listFiles({ maxResults: 10 });
|
|
545
|
+
* if (nextPageToken) {
|
|
546
|
+
* const nextPage = await listFiles({ pageToken: nextPageToken });
|
|
547
|
+
* }
|
|
548
|
+
* ```
|
|
549
|
+
*/
|
|
550
|
+
declare function listFiles(options?: ListOptions): Promise<ListFilesResult>;
|
|
551
|
+
/**
|
|
552
|
+
* Check if a file exists in Firebase Storage
|
|
553
|
+
*
|
|
554
|
+
* @param path - File path in storage
|
|
555
|
+
* @returns True if file exists, false otherwise
|
|
556
|
+
*
|
|
557
|
+
* @example
|
|
558
|
+
* ```typescript
|
|
559
|
+
* if (await fileExists('files/hello.txt')) {
|
|
560
|
+
* console.log('File exists!');
|
|
561
|
+
* }
|
|
562
|
+
* ```
|
|
563
|
+
*/
|
|
564
|
+
declare function fileExists(path: string): Promise<boolean>;
|
|
565
|
+
|
|
566
|
+
/**
|
|
567
|
+
* Generate signed URLs for temporary access to Firebase Storage files
|
|
568
|
+
* Uses Google Cloud Storage V4 signing process
|
|
569
|
+
*/
|
|
570
|
+
/**
|
|
571
|
+
* Options for generating signed URLs
|
|
572
|
+
*/
|
|
573
|
+
interface SignedUrlOptions {
|
|
574
|
+
action: 'read' | 'write' | 'delete';
|
|
575
|
+
expires: Date | number;
|
|
576
|
+
contentType?: string;
|
|
577
|
+
responseDisposition?: string;
|
|
578
|
+
responseType?: string;
|
|
579
|
+
}
|
|
580
|
+
/**
|
|
581
|
+
* Generate a signed URL for temporary access to a Storage file
|
|
582
|
+
*
|
|
583
|
+
* Uses Google Cloud Storage V4 signing process
|
|
584
|
+
*
|
|
585
|
+
* @param path - File path in storage (e.g., 'images/photo.jpg')
|
|
586
|
+
* @param options - Signed URL options
|
|
587
|
+
* @returns Signed URL that can be used without authentication
|
|
588
|
+
*
|
|
589
|
+
* @example
|
|
590
|
+
* ```typescript
|
|
591
|
+
* // Generate read URL valid for 1 hour
|
|
592
|
+
* const url = await generateSignedUrl('files/hello.txt', {
|
|
593
|
+
* action: 'read',
|
|
594
|
+
* expires: 3600,
|
|
595
|
+
* });
|
|
596
|
+
*
|
|
597
|
+
* // Generate write URL with content type
|
|
598
|
+
* const uploadUrl = await generateSignedUrl('files/upload.jpg', {
|
|
599
|
+
* action: 'write',
|
|
600
|
+
* expires: 1800, // 30 minutes
|
|
601
|
+
* contentType: 'image/jpeg',
|
|
602
|
+
* });
|
|
603
|
+
*
|
|
604
|
+
* // Use the URL (no auth needed)
|
|
605
|
+
* const response = await fetch(url);
|
|
606
|
+
* const data = await response.arrayBuffer();
|
|
607
|
+
* ```
|
|
608
|
+
*/
|
|
609
|
+
declare function generateSignedUrl(path: string, options: SignedUrlOptions): Promise<string>;
|
|
610
|
+
|
|
428
611
|
/**
|
|
429
612
|
* Firebase Admin SDK v8 - Field Value Helpers
|
|
430
613
|
* Special field values for Firestore operations
|
|
@@ -522,4 +705,4 @@ declare function getAdminAccessToken(): Promise<string>;
|
|
|
522
705
|
*/
|
|
523
706
|
declare function clearTokenCache(): void;
|
|
524
707
|
|
|
525
|
-
export { type BatchWrite, type BatchWriteResult, type DataObject, type DecodedIdToken, type DocumentReference, FieldValue, type FieldValue$1 as FieldValueSentinel, FieldValueType, type FirestoreDocument, type FirestoreValue, type QueryFilter, type QueryOptions, type QueryOrder, type ServiceAccount, type SetOptions, type TokenResponse, type UpdateOptions, type UserInfo, type WhereFilterOp, addDocument, batchWrite, clearConfig, clearTokenCache, deleteDocument, getAdminAccessToken, getAuth, getConfig, getDocument, getProjectId, getServiceAccount, getUserFromToken, initializeApp, queryDocuments, setDocument, updateDocument, verifyIdToken };
|
|
708
|
+
export { type BatchWrite, type BatchWriteResult, 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 QueryFilter, type QueryOptions, type QueryOrder, type ServiceAccount, type SetOptions, type SignedUrlOptions, type TokenResponse, type UpdateOptions, type UploadOptions, type UserInfo, type WhereFilterOp, addDocument, batchWrite, clearConfig, clearTokenCache, deleteDocument, deleteFile, downloadFile, fileExists, generateSignedUrl, getAdminAccessToken, getAuth, getConfig, getDocument, getFileMetadata, getProjectId, getServiceAccount, getUserFromToken, initializeApp, listFiles, queryDocuments, setDocument, updateDocument, uploadFile, verifyIdToken };
|
package/dist/index.js
CHANGED
|
@@ -26,17 +26,24 @@ __export(index_exports, {
|
|
|
26
26
|
clearConfig: () => clearConfig,
|
|
27
27
|
clearTokenCache: () => clearTokenCache,
|
|
28
28
|
deleteDocument: () => deleteDocument,
|
|
29
|
+
deleteFile: () => deleteFile,
|
|
30
|
+
downloadFile: () => downloadFile,
|
|
31
|
+
fileExists: () => fileExists,
|
|
32
|
+
generateSignedUrl: () => generateSignedUrl,
|
|
29
33
|
getAdminAccessToken: () => getAdminAccessToken,
|
|
30
34
|
getAuth: () => getAuth,
|
|
31
35
|
getConfig: () => getConfig,
|
|
32
36
|
getDocument: () => getDocument,
|
|
37
|
+
getFileMetadata: () => getFileMetadata,
|
|
33
38
|
getProjectId: () => getProjectId,
|
|
34
39
|
getServiceAccount: () => getServiceAccount,
|
|
35
40
|
getUserFromToken: () => getUserFromToken,
|
|
36
41
|
initializeApp: () => initializeApp,
|
|
42
|
+
listFiles: () => listFiles,
|
|
37
43
|
queryDocuments: () => queryDocuments,
|
|
38
44
|
setDocument: () => setDocument,
|
|
39
45
|
updateDocument: () => updateDocument,
|
|
46
|
+
uploadFile: () => uploadFile,
|
|
40
47
|
verifyIdToken: () => verifyIdToken
|
|
41
48
|
});
|
|
42
49
|
module.exports = __toCommonJS(index_exports);
|
|
@@ -679,6 +686,26 @@ function clearTokenCache() {
|
|
|
679
686
|
tokenExpiry = 0;
|
|
680
687
|
}
|
|
681
688
|
|
|
689
|
+
// src/firestore/path-validation.ts
|
|
690
|
+
function validateCollectionPath(argumentName, collectionPath) {
|
|
691
|
+
const segments = collectionPath.split("/").filter((s) => s.length > 0);
|
|
692
|
+
if (segments.length % 2 === 0) {
|
|
693
|
+
throw new Error(
|
|
694
|
+
`Value for argument "${argumentName}" must point to a collection, but was "${collectionPath}". Your path does not contain an odd number of components.`
|
|
695
|
+
);
|
|
696
|
+
}
|
|
697
|
+
}
|
|
698
|
+
function validateDocumentPath(argumentName, collectionPath, documentId) {
|
|
699
|
+
const collectionSegments = collectionPath.split("/").filter((s) => s.length > 0);
|
|
700
|
+
const totalSegments = collectionSegments.length + 1;
|
|
701
|
+
if (totalSegments % 2 !== 0) {
|
|
702
|
+
const fullPath = `${collectionPath}/${documentId}`;
|
|
703
|
+
throw new Error(
|
|
704
|
+
`Value for argument "${argumentName}" must point to a document, but was "${fullPath}". Your path does not contain an even number of components.`
|
|
705
|
+
);
|
|
706
|
+
}
|
|
707
|
+
}
|
|
708
|
+
|
|
682
709
|
// src/firestore/operations.ts
|
|
683
710
|
var FIRESTORE_API = "https://firestore.googleapis.com/v1";
|
|
684
711
|
async function commitWrites(writes) {
|
|
@@ -699,6 +726,7 @@ async function commitWrites(writes) {
|
|
|
699
726
|
}
|
|
700
727
|
}
|
|
701
728
|
async function setDocument(collectionPath, documentId, data, options) {
|
|
729
|
+
validateDocumentPath("collectionPath", collectionPath, documentId);
|
|
702
730
|
const projectId = getProjectId();
|
|
703
731
|
const documentPath = `projects/${projectId}/databases/(default)/documents/${collectionPath}/${documentId}`;
|
|
704
732
|
const cleanData = removeFieldTransforms(data);
|
|
@@ -748,6 +776,7 @@ async function setDocument(collectionPath, documentId, data, options) {
|
|
|
748
776
|
}
|
|
749
777
|
}
|
|
750
778
|
async function addDocument(collectionPath, data, documentId) {
|
|
779
|
+
validateCollectionPath("collectionPath", collectionPath);
|
|
751
780
|
const accessToken = await getAdminAccessToken();
|
|
752
781
|
const projectId = getProjectId();
|
|
753
782
|
const baseUrl = `${FIRESTORE_API}/projects/${projectId}/databases/(default)/documents/${collectionPath}`;
|
|
@@ -779,6 +808,7 @@ async function addDocument(collectionPath, data, documentId) {
|
|
|
779
808
|
};
|
|
780
809
|
}
|
|
781
810
|
async function getDocument(collectionPath, documentId) {
|
|
811
|
+
validateDocumentPath("collectionPath", collectionPath, documentId);
|
|
782
812
|
const accessToken = await getAdminAccessToken();
|
|
783
813
|
const projectId = getProjectId();
|
|
784
814
|
const url = `${FIRESTORE_API}/projects/${projectId}/databases/(default)/documents/${collectionPath}/${documentId}`;
|
|
@@ -798,6 +828,7 @@ async function getDocument(collectionPath, documentId) {
|
|
|
798
828
|
return convertFromFirestoreFormat(result.fields);
|
|
799
829
|
}
|
|
800
830
|
async function updateDocument(collectionPath, documentId, data) {
|
|
831
|
+
validateDocumentPath("collectionPath", collectionPath, documentId);
|
|
801
832
|
const projectId = getProjectId();
|
|
802
833
|
const documentPath = `projects/${projectId}/databases/(default)/documents/${collectionPath}/${documentId}`;
|
|
803
834
|
const cleanData = removeFieldTransforms(data);
|
|
@@ -850,6 +881,7 @@ async function updateDocument(collectionPath, documentId, data) {
|
|
|
850
881
|
}
|
|
851
882
|
}
|
|
852
883
|
async function deleteDocument(collectionPath, documentId) {
|
|
884
|
+
validateDocumentPath("collectionPath", collectionPath, documentId);
|
|
853
885
|
const accessToken = await getAdminAccessToken();
|
|
854
886
|
const projectId = getProjectId();
|
|
855
887
|
const url = `${FIRESTORE_API}/projects/${projectId}/databases/(default)/documents/${collectionPath}/${documentId}`;
|
|
@@ -865,6 +897,7 @@ async function deleteDocument(collectionPath, documentId) {
|
|
|
865
897
|
}
|
|
866
898
|
}
|
|
867
899
|
async function queryDocuments(collectionPath, options) {
|
|
900
|
+
validateCollectionPath("collectionPath", collectionPath);
|
|
868
901
|
const accessToken = await getAdminAccessToken();
|
|
869
902
|
const projectId = getProjectId();
|
|
870
903
|
if (!options || Object.keys(options).length === 0) {
|
|
@@ -981,6 +1014,299 @@ async function batchWrite(operations) {
|
|
|
981
1014
|
}
|
|
982
1015
|
return await response.json();
|
|
983
1016
|
}
|
|
1017
|
+
|
|
1018
|
+
// src/storage/client.ts
|
|
1019
|
+
var STORAGE_API_BASE = "https://storage.googleapis.com/storage/v1";
|
|
1020
|
+
var UPLOAD_API_BASE = "https://storage.googleapis.com/upload/storage/v1";
|
|
1021
|
+
function getDefaultBucket() {
|
|
1022
|
+
const projectId = getProjectId();
|
|
1023
|
+
return `${projectId}.appspot.com`;
|
|
1024
|
+
}
|
|
1025
|
+
function detectContentType(filename) {
|
|
1026
|
+
const ext = filename.split(".").pop()?.toLowerCase();
|
|
1027
|
+
const mimeTypes = {
|
|
1028
|
+
"txt": "text/plain",
|
|
1029
|
+
"html": "text/html",
|
|
1030
|
+
"htm": "text/html",
|
|
1031
|
+
"css": "text/css",
|
|
1032
|
+
"js": "application/javascript",
|
|
1033
|
+
"json": "application/json",
|
|
1034
|
+
"xml": "application/xml",
|
|
1035
|
+
"jpg": "image/jpeg",
|
|
1036
|
+
"jpeg": "image/jpeg",
|
|
1037
|
+
"png": "image/png",
|
|
1038
|
+
"gif": "image/gif",
|
|
1039
|
+
"svg": "image/svg+xml",
|
|
1040
|
+
"webp": "image/webp",
|
|
1041
|
+
"pdf": "application/pdf",
|
|
1042
|
+
"zip": "application/zip",
|
|
1043
|
+
"mp4": "video/mp4",
|
|
1044
|
+
"mp3": "audio/mpeg",
|
|
1045
|
+
"wav": "audio/wav"
|
|
1046
|
+
};
|
|
1047
|
+
return mimeTypes[ext || ""] || "application/octet-stream";
|
|
1048
|
+
}
|
|
1049
|
+
async function uploadFile(path, data, options = {}) {
|
|
1050
|
+
const token = await getAdminAccessToken();
|
|
1051
|
+
const bucket = getDefaultBucket();
|
|
1052
|
+
const contentType = options.contentType || detectContentType(path);
|
|
1053
|
+
const url = `${UPLOAD_API_BASE}/b/${encodeURIComponent(bucket)}/o?uploadType=media&name=${encodeURIComponent(path)}`;
|
|
1054
|
+
let body;
|
|
1055
|
+
if (data instanceof Blob) {
|
|
1056
|
+
body = await data.arrayBuffer();
|
|
1057
|
+
} else if (data instanceof Uint8Array) {
|
|
1058
|
+
const buffer = new ArrayBuffer(data.byteLength);
|
|
1059
|
+
new Uint8Array(buffer).set(data);
|
|
1060
|
+
body = buffer;
|
|
1061
|
+
} else {
|
|
1062
|
+
body = data;
|
|
1063
|
+
}
|
|
1064
|
+
const response = await fetch(url, {
|
|
1065
|
+
method: "POST",
|
|
1066
|
+
headers: {
|
|
1067
|
+
"Authorization": `Bearer ${token}`,
|
|
1068
|
+
"Content-Type": contentType,
|
|
1069
|
+
"Content-Length": body.byteLength.toString()
|
|
1070
|
+
},
|
|
1071
|
+
body
|
|
1072
|
+
});
|
|
1073
|
+
if (!response.ok) {
|
|
1074
|
+
const errorText = await response.text();
|
|
1075
|
+
throw new Error(`Failed to upload file: ${response.status} ${errorText}`);
|
|
1076
|
+
}
|
|
1077
|
+
const result = await response.json();
|
|
1078
|
+
if (options.public) {
|
|
1079
|
+
await makeFilePublic(path);
|
|
1080
|
+
}
|
|
1081
|
+
if (options.metadata) {
|
|
1082
|
+
return await updateFileMetadata(path, options.metadata);
|
|
1083
|
+
}
|
|
1084
|
+
return result;
|
|
1085
|
+
}
|
|
1086
|
+
async function downloadFile(path, _options = {}) {
|
|
1087
|
+
const token = await getAdminAccessToken();
|
|
1088
|
+
const bucket = getDefaultBucket();
|
|
1089
|
+
const url = `${STORAGE_API_BASE}/b/${encodeURIComponent(bucket)}/o/${encodeURIComponent(path)}?alt=media`;
|
|
1090
|
+
const response = await fetch(url, {
|
|
1091
|
+
method: "GET",
|
|
1092
|
+
headers: {
|
|
1093
|
+
"Authorization": `Bearer ${token}`
|
|
1094
|
+
}
|
|
1095
|
+
});
|
|
1096
|
+
if (!response.ok) {
|
|
1097
|
+
const errorText = await response.text();
|
|
1098
|
+
throw new Error(`Failed to download file: ${response.status} ${errorText}`);
|
|
1099
|
+
}
|
|
1100
|
+
return await response.arrayBuffer();
|
|
1101
|
+
}
|
|
1102
|
+
async function deleteFile(path) {
|
|
1103
|
+
const token = await getAdminAccessToken();
|
|
1104
|
+
const bucket = getDefaultBucket();
|
|
1105
|
+
const url = `${STORAGE_API_BASE}/b/${encodeURIComponent(bucket)}/o/${encodeURIComponent(path)}`;
|
|
1106
|
+
const response = await fetch(url, {
|
|
1107
|
+
method: "DELETE",
|
|
1108
|
+
headers: {
|
|
1109
|
+
"Authorization": `Bearer ${token}`
|
|
1110
|
+
}
|
|
1111
|
+
});
|
|
1112
|
+
if (!response.ok) {
|
|
1113
|
+
const errorText = await response.text();
|
|
1114
|
+
throw new Error(`Failed to delete file: ${response.status} ${errorText}`);
|
|
1115
|
+
}
|
|
1116
|
+
}
|
|
1117
|
+
async function getFileMetadata(path) {
|
|
1118
|
+
const token = await getAdminAccessToken();
|
|
1119
|
+
const bucket = getDefaultBucket();
|
|
1120
|
+
const url = `${STORAGE_API_BASE}/b/${encodeURIComponent(bucket)}/o/${encodeURIComponent(path)}`;
|
|
1121
|
+
const response = await fetch(url, {
|
|
1122
|
+
method: "GET",
|
|
1123
|
+
headers: {
|
|
1124
|
+
"Authorization": `Bearer ${token}`
|
|
1125
|
+
}
|
|
1126
|
+
});
|
|
1127
|
+
if (!response.ok) {
|
|
1128
|
+
const errorText = await response.text();
|
|
1129
|
+
throw new Error(`Failed to get file metadata: ${response.status} ${errorText}`);
|
|
1130
|
+
}
|
|
1131
|
+
return await response.json();
|
|
1132
|
+
}
|
|
1133
|
+
async function updateFileMetadata(path, metadata) {
|
|
1134
|
+
const token = await getAdminAccessToken();
|
|
1135
|
+
const bucket = getDefaultBucket();
|
|
1136
|
+
const url = `${STORAGE_API_BASE}/b/${encodeURIComponent(bucket)}/o/${encodeURIComponent(path)}`;
|
|
1137
|
+
const response = await fetch(url, {
|
|
1138
|
+
method: "PATCH",
|
|
1139
|
+
headers: {
|
|
1140
|
+
"Authorization": `Bearer ${token}`,
|
|
1141
|
+
"Content-Type": "application/json"
|
|
1142
|
+
},
|
|
1143
|
+
body: JSON.stringify({ metadata })
|
|
1144
|
+
});
|
|
1145
|
+
if (!response.ok) {
|
|
1146
|
+
const errorText = await response.text();
|
|
1147
|
+
throw new Error(`Failed to update file metadata: ${response.status} ${errorText}`);
|
|
1148
|
+
}
|
|
1149
|
+
return await response.json();
|
|
1150
|
+
}
|
|
1151
|
+
async function makeFilePublic(path) {
|
|
1152
|
+
const token = await getAdminAccessToken();
|
|
1153
|
+
const bucket = getDefaultBucket();
|
|
1154
|
+
const url = `${STORAGE_API_BASE}/b/${encodeURIComponent(bucket)}/o/${encodeURIComponent(path)}/acl`;
|
|
1155
|
+
const response = await fetch(url, {
|
|
1156
|
+
method: "POST",
|
|
1157
|
+
headers: {
|
|
1158
|
+
"Authorization": `Bearer ${token}`,
|
|
1159
|
+
"Content-Type": "application/json"
|
|
1160
|
+
},
|
|
1161
|
+
body: JSON.stringify({
|
|
1162
|
+
entity: "allUsers",
|
|
1163
|
+
role: "READER"
|
|
1164
|
+
})
|
|
1165
|
+
});
|
|
1166
|
+
if (!response.ok) {
|
|
1167
|
+
const errorText = await response.text();
|
|
1168
|
+
throw new Error(`Failed to make file public: ${response.status} ${errorText}`);
|
|
1169
|
+
}
|
|
1170
|
+
}
|
|
1171
|
+
async function listFiles(options = {}) {
|
|
1172
|
+
const token = await getAdminAccessToken();
|
|
1173
|
+
const bucket = getDefaultBucket();
|
|
1174
|
+
const params = new URLSearchParams();
|
|
1175
|
+
if (options.prefix) params.append("prefix", options.prefix);
|
|
1176
|
+
if (options.delimiter) params.append("delimiter", options.delimiter);
|
|
1177
|
+
if (options.maxResults) params.append("maxResults", options.maxResults.toString());
|
|
1178
|
+
if (options.pageToken) params.append("pageToken", options.pageToken);
|
|
1179
|
+
const url = `${STORAGE_API_BASE}/b/${encodeURIComponent(bucket)}/o?${params.toString()}`;
|
|
1180
|
+
const response = await fetch(url, {
|
|
1181
|
+
method: "GET",
|
|
1182
|
+
headers: {
|
|
1183
|
+
"Authorization": `Bearer ${token}`
|
|
1184
|
+
}
|
|
1185
|
+
});
|
|
1186
|
+
if (!response.ok) {
|
|
1187
|
+
const errorText = await response.text();
|
|
1188
|
+
throw new Error(`Failed to list files: ${response.status} ${errorText}`);
|
|
1189
|
+
}
|
|
1190
|
+
const result = await response.json();
|
|
1191
|
+
return {
|
|
1192
|
+
files: result.items || [],
|
|
1193
|
+
nextPageToken: result.nextPageToken
|
|
1194
|
+
};
|
|
1195
|
+
}
|
|
1196
|
+
async function fileExists(path) {
|
|
1197
|
+
try {
|
|
1198
|
+
await getFileMetadata(path);
|
|
1199
|
+
return true;
|
|
1200
|
+
} catch (error) {
|
|
1201
|
+
if (error instanceof Error && error.message.includes("404")) {
|
|
1202
|
+
return false;
|
|
1203
|
+
}
|
|
1204
|
+
throw error;
|
|
1205
|
+
}
|
|
1206
|
+
}
|
|
1207
|
+
|
|
1208
|
+
// src/storage/signed-urls.ts
|
|
1209
|
+
function getExpirationTimestamp(expires) {
|
|
1210
|
+
if (expires instanceof Date) {
|
|
1211
|
+
return Math.floor(expires.getTime() / 1e3);
|
|
1212
|
+
}
|
|
1213
|
+
return Math.floor(Date.now() / 1e3) + expires;
|
|
1214
|
+
}
|
|
1215
|
+
function actionToMethod(action) {
|
|
1216
|
+
switch (action) {
|
|
1217
|
+
case "read":
|
|
1218
|
+
return "GET";
|
|
1219
|
+
case "write":
|
|
1220
|
+
return "PUT";
|
|
1221
|
+
case "delete":
|
|
1222
|
+
return "DELETE";
|
|
1223
|
+
}
|
|
1224
|
+
}
|
|
1225
|
+
function stringToHex(str) {
|
|
1226
|
+
const encoder = new TextEncoder();
|
|
1227
|
+
const bytes = encoder.encode(str);
|
|
1228
|
+
return Array.from(bytes).map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
1229
|
+
}
|
|
1230
|
+
async function signData(data, privateKey) {
|
|
1231
|
+
const pemHeader = "-----BEGIN PRIVATE KEY-----";
|
|
1232
|
+
const pemFooter = "-----END PRIVATE KEY-----";
|
|
1233
|
+
const pemContents = privateKey.replace(pemHeader, "").replace(pemFooter, "").replace(/\s/g, "");
|
|
1234
|
+
const binaryString = atob(pemContents);
|
|
1235
|
+
const bytes = new Uint8Array(binaryString.length);
|
|
1236
|
+
for (let i = 0; i < binaryString.length; i++) {
|
|
1237
|
+
bytes[i] = binaryString.charCodeAt(i);
|
|
1238
|
+
}
|
|
1239
|
+
const key = await crypto.subtle.importKey(
|
|
1240
|
+
"pkcs8",
|
|
1241
|
+
bytes,
|
|
1242
|
+
{
|
|
1243
|
+
name: "RSASSA-PKCS1-v1_5",
|
|
1244
|
+
hash: "SHA-256"
|
|
1245
|
+
},
|
|
1246
|
+
false,
|
|
1247
|
+
["sign"]
|
|
1248
|
+
);
|
|
1249
|
+
const encoder = new TextEncoder();
|
|
1250
|
+
const dataBytes = encoder.encode(data);
|
|
1251
|
+
const signature = await crypto.subtle.sign(
|
|
1252
|
+
"RSASSA-PKCS1-v1_5",
|
|
1253
|
+
key,
|
|
1254
|
+
dataBytes
|
|
1255
|
+
);
|
|
1256
|
+
const signatureArray = new Uint8Array(signature);
|
|
1257
|
+
return Array.from(signatureArray).map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
1258
|
+
}
|
|
1259
|
+
async function generateSignedUrl(path, options) {
|
|
1260
|
+
const serviceAccount = getServiceAccount();
|
|
1261
|
+
const projectId = getProjectId();
|
|
1262
|
+
const bucket = `${projectId}.appspot.com`;
|
|
1263
|
+
const method = actionToMethod(options.action);
|
|
1264
|
+
const expiration = getExpirationTimestamp(options.expires);
|
|
1265
|
+
const timestamp = Math.floor(Date.now() / 1e3);
|
|
1266
|
+
const datestamp = new Date(timestamp * 1e3).toISOString().split("T")[0].replace(/-/g, "");
|
|
1267
|
+
const credentialScope = `${datestamp}/auto/storage/goog4_request`;
|
|
1268
|
+
const credential = `${serviceAccount.client_email}/${credentialScope}`;
|
|
1269
|
+
const canonicalHeaders = `host:storage.googleapis.com
|
|
1270
|
+
`;
|
|
1271
|
+
const signedHeaders = "host";
|
|
1272
|
+
const queryParams = {
|
|
1273
|
+
"X-Goog-Algorithm": "GOOG4-RSA-SHA256",
|
|
1274
|
+
"X-Goog-Credential": credential,
|
|
1275
|
+
"X-Goog-Date": `${datestamp}T000000Z`,
|
|
1276
|
+
"X-Goog-Expires": (expiration - timestamp).toString(),
|
|
1277
|
+
"X-Goog-SignedHeaders": signedHeaders
|
|
1278
|
+
};
|
|
1279
|
+
if (options.contentType) {
|
|
1280
|
+
queryParams["response-content-type"] = options.contentType;
|
|
1281
|
+
}
|
|
1282
|
+
if (options.responseDisposition) {
|
|
1283
|
+
queryParams["response-content-disposition"] = options.responseDisposition;
|
|
1284
|
+
}
|
|
1285
|
+
if (options.responseType) {
|
|
1286
|
+
queryParams["response-content-type"] = options.responseType;
|
|
1287
|
+
}
|
|
1288
|
+
const sortedParams = Object.keys(queryParams).sort();
|
|
1289
|
+
const canonicalQueryString = sortedParams.map((key) => `${encodeURIComponent(key)}=${encodeURIComponent(queryParams[key])}`).join("&");
|
|
1290
|
+
const canonicalUri = `/${bucket}/${path}`;
|
|
1291
|
+
const canonicalRequest = [
|
|
1292
|
+
method,
|
|
1293
|
+
canonicalUri,
|
|
1294
|
+
canonicalQueryString,
|
|
1295
|
+
canonicalHeaders,
|
|
1296
|
+
signedHeaders,
|
|
1297
|
+
"UNSIGNED-PAYLOAD"
|
|
1298
|
+
].join("\n");
|
|
1299
|
+
const canonicalRequestHash = stringToHex(canonicalRequest);
|
|
1300
|
+
const stringToSign = [
|
|
1301
|
+
"GOOG4-RSA-SHA256",
|
|
1302
|
+
`${datestamp}T000000Z`,
|
|
1303
|
+
credentialScope,
|
|
1304
|
+
canonicalRequestHash
|
|
1305
|
+
].join("\n");
|
|
1306
|
+
const signature = await signData(stringToSign, serviceAccount.private_key);
|
|
1307
|
+
const signedUrl = `https://storage.googleapis.com${canonicalUri}?${canonicalQueryString}&X-Goog-Signature=${signature}`;
|
|
1308
|
+
return signedUrl;
|
|
1309
|
+
}
|
|
984
1310
|
// Annotate the CommonJS export names for ESM import in node:
|
|
985
1311
|
0 && (module.exports = {
|
|
986
1312
|
FieldValue,
|
|
@@ -989,16 +1315,23 @@ async function batchWrite(operations) {
|
|
|
989
1315
|
clearConfig,
|
|
990
1316
|
clearTokenCache,
|
|
991
1317
|
deleteDocument,
|
|
1318
|
+
deleteFile,
|
|
1319
|
+
downloadFile,
|
|
1320
|
+
fileExists,
|
|
1321
|
+
generateSignedUrl,
|
|
992
1322
|
getAdminAccessToken,
|
|
993
1323
|
getAuth,
|
|
994
1324
|
getConfig,
|
|
995
1325
|
getDocument,
|
|
1326
|
+
getFileMetadata,
|
|
996
1327
|
getProjectId,
|
|
997
1328
|
getServiceAccount,
|
|
998
1329
|
getUserFromToken,
|
|
999
1330
|
initializeApp,
|
|
1331
|
+
listFiles,
|
|
1000
1332
|
queryDocuments,
|
|
1001
1333
|
setDocument,
|
|
1002
1334
|
updateDocument,
|
|
1335
|
+
uploadFile,
|
|
1003
1336
|
verifyIdToken
|
|
1004
1337
|
});
|
package/dist/index.mjs
CHANGED
|
@@ -636,6 +636,26 @@ function clearTokenCache() {
|
|
|
636
636
|
tokenExpiry = 0;
|
|
637
637
|
}
|
|
638
638
|
|
|
639
|
+
// src/firestore/path-validation.ts
|
|
640
|
+
function validateCollectionPath(argumentName, collectionPath) {
|
|
641
|
+
const segments = collectionPath.split("/").filter((s) => s.length > 0);
|
|
642
|
+
if (segments.length % 2 === 0) {
|
|
643
|
+
throw new Error(
|
|
644
|
+
`Value for argument "${argumentName}" must point to a collection, but was "${collectionPath}". Your path does not contain an odd number of components.`
|
|
645
|
+
);
|
|
646
|
+
}
|
|
647
|
+
}
|
|
648
|
+
function validateDocumentPath(argumentName, collectionPath, documentId) {
|
|
649
|
+
const collectionSegments = collectionPath.split("/").filter((s) => s.length > 0);
|
|
650
|
+
const totalSegments = collectionSegments.length + 1;
|
|
651
|
+
if (totalSegments % 2 !== 0) {
|
|
652
|
+
const fullPath = `${collectionPath}/${documentId}`;
|
|
653
|
+
throw new Error(
|
|
654
|
+
`Value for argument "${argumentName}" must point to a document, but was "${fullPath}". Your path does not contain an even number of components.`
|
|
655
|
+
);
|
|
656
|
+
}
|
|
657
|
+
}
|
|
658
|
+
|
|
639
659
|
// src/firestore/operations.ts
|
|
640
660
|
var FIRESTORE_API = "https://firestore.googleapis.com/v1";
|
|
641
661
|
async function commitWrites(writes) {
|
|
@@ -656,6 +676,7 @@ async function commitWrites(writes) {
|
|
|
656
676
|
}
|
|
657
677
|
}
|
|
658
678
|
async function setDocument(collectionPath, documentId, data, options) {
|
|
679
|
+
validateDocumentPath("collectionPath", collectionPath, documentId);
|
|
659
680
|
const projectId = getProjectId();
|
|
660
681
|
const documentPath = `projects/${projectId}/databases/(default)/documents/${collectionPath}/${documentId}`;
|
|
661
682
|
const cleanData = removeFieldTransforms(data);
|
|
@@ -705,6 +726,7 @@ async function setDocument(collectionPath, documentId, data, options) {
|
|
|
705
726
|
}
|
|
706
727
|
}
|
|
707
728
|
async function addDocument(collectionPath, data, documentId) {
|
|
729
|
+
validateCollectionPath("collectionPath", collectionPath);
|
|
708
730
|
const accessToken = await getAdminAccessToken();
|
|
709
731
|
const projectId = getProjectId();
|
|
710
732
|
const baseUrl = `${FIRESTORE_API}/projects/${projectId}/databases/(default)/documents/${collectionPath}`;
|
|
@@ -736,6 +758,7 @@ async function addDocument(collectionPath, data, documentId) {
|
|
|
736
758
|
};
|
|
737
759
|
}
|
|
738
760
|
async function getDocument(collectionPath, documentId) {
|
|
761
|
+
validateDocumentPath("collectionPath", collectionPath, documentId);
|
|
739
762
|
const accessToken = await getAdminAccessToken();
|
|
740
763
|
const projectId = getProjectId();
|
|
741
764
|
const url = `${FIRESTORE_API}/projects/${projectId}/databases/(default)/documents/${collectionPath}/${documentId}`;
|
|
@@ -755,6 +778,7 @@ async function getDocument(collectionPath, documentId) {
|
|
|
755
778
|
return convertFromFirestoreFormat(result.fields);
|
|
756
779
|
}
|
|
757
780
|
async function updateDocument(collectionPath, documentId, data) {
|
|
781
|
+
validateDocumentPath("collectionPath", collectionPath, documentId);
|
|
758
782
|
const projectId = getProjectId();
|
|
759
783
|
const documentPath = `projects/${projectId}/databases/(default)/documents/${collectionPath}/${documentId}`;
|
|
760
784
|
const cleanData = removeFieldTransforms(data);
|
|
@@ -807,6 +831,7 @@ async function updateDocument(collectionPath, documentId, data) {
|
|
|
807
831
|
}
|
|
808
832
|
}
|
|
809
833
|
async function deleteDocument(collectionPath, documentId) {
|
|
834
|
+
validateDocumentPath("collectionPath", collectionPath, documentId);
|
|
810
835
|
const accessToken = await getAdminAccessToken();
|
|
811
836
|
const projectId = getProjectId();
|
|
812
837
|
const url = `${FIRESTORE_API}/projects/${projectId}/databases/(default)/documents/${collectionPath}/${documentId}`;
|
|
@@ -822,6 +847,7 @@ async function deleteDocument(collectionPath, documentId) {
|
|
|
822
847
|
}
|
|
823
848
|
}
|
|
824
849
|
async function queryDocuments(collectionPath, options) {
|
|
850
|
+
validateCollectionPath("collectionPath", collectionPath);
|
|
825
851
|
const accessToken = await getAdminAccessToken();
|
|
826
852
|
const projectId = getProjectId();
|
|
827
853
|
if (!options || Object.keys(options).length === 0) {
|
|
@@ -938,6 +964,299 @@ async function batchWrite(operations) {
|
|
|
938
964
|
}
|
|
939
965
|
return await response.json();
|
|
940
966
|
}
|
|
967
|
+
|
|
968
|
+
// src/storage/client.ts
|
|
969
|
+
var STORAGE_API_BASE = "https://storage.googleapis.com/storage/v1";
|
|
970
|
+
var UPLOAD_API_BASE = "https://storage.googleapis.com/upload/storage/v1";
|
|
971
|
+
function getDefaultBucket() {
|
|
972
|
+
const projectId = getProjectId();
|
|
973
|
+
return `${projectId}.appspot.com`;
|
|
974
|
+
}
|
|
975
|
+
function detectContentType(filename) {
|
|
976
|
+
const ext = filename.split(".").pop()?.toLowerCase();
|
|
977
|
+
const mimeTypes = {
|
|
978
|
+
"txt": "text/plain",
|
|
979
|
+
"html": "text/html",
|
|
980
|
+
"htm": "text/html",
|
|
981
|
+
"css": "text/css",
|
|
982
|
+
"js": "application/javascript",
|
|
983
|
+
"json": "application/json",
|
|
984
|
+
"xml": "application/xml",
|
|
985
|
+
"jpg": "image/jpeg",
|
|
986
|
+
"jpeg": "image/jpeg",
|
|
987
|
+
"png": "image/png",
|
|
988
|
+
"gif": "image/gif",
|
|
989
|
+
"svg": "image/svg+xml",
|
|
990
|
+
"webp": "image/webp",
|
|
991
|
+
"pdf": "application/pdf",
|
|
992
|
+
"zip": "application/zip",
|
|
993
|
+
"mp4": "video/mp4",
|
|
994
|
+
"mp3": "audio/mpeg",
|
|
995
|
+
"wav": "audio/wav"
|
|
996
|
+
};
|
|
997
|
+
return mimeTypes[ext || ""] || "application/octet-stream";
|
|
998
|
+
}
|
|
999
|
+
async function uploadFile(path, data, options = {}) {
|
|
1000
|
+
const token = await getAdminAccessToken();
|
|
1001
|
+
const bucket = getDefaultBucket();
|
|
1002
|
+
const contentType = options.contentType || detectContentType(path);
|
|
1003
|
+
const url = `${UPLOAD_API_BASE}/b/${encodeURIComponent(bucket)}/o?uploadType=media&name=${encodeURIComponent(path)}`;
|
|
1004
|
+
let body;
|
|
1005
|
+
if (data instanceof Blob) {
|
|
1006
|
+
body = await data.arrayBuffer();
|
|
1007
|
+
} else if (data instanceof Uint8Array) {
|
|
1008
|
+
const buffer = new ArrayBuffer(data.byteLength);
|
|
1009
|
+
new Uint8Array(buffer).set(data);
|
|
1010
|
+
body = buffer;
|
|
1011
|
+
} else {
|
|
1012
|
+
body = data;
|
|
1013
|
+
}
|
|
1014
|
+
const response = await fetch(url, {
|
|
1015
|
+
method: "POST",
|
|
1016
|
+
headers: {
|
|
1017
|
+
"Authorization": `Bearer ${token}`,
|
|
1018
|
+
"Content-Type": contentType,
|
|
1019
|
+
"Content-Length": body.byteLength.toString()
|
|
1020
|
+
},
|
|
1021
|
+
body
|
|
1022
|
+
});
|
|
1023
|
+
if (!response.ok) {
|
|
1024
|
+
const errorText = await response.text();
|
|
1025
|
+
throw new Error(`Failed to upload file: ${response.status} ${errorText}`);
|
|
1026
|
+
}
|
|
1027
|
+
const result = await response.json();
|
|
1028
|
+
if (options.public) {
|
|
1029
|
+
await makeFilePublic(path);
|
|
1030
|
+
}
|
|
1031
|
+
if (options.metadata) {
|
|
1032
|
+
return await updateFileMetadata(path, options.metadata);
|
|
1033
|
+
}
|
|
1034
|
+
return result;
|
|
1035
|
+
}
|
|
1036
|
+
async function downloadFile(path, _options = {}) {
|
|
1037
|
+
const token = await getAdminAccessToken();
|
|
1038
|
+
const bucket = getDefaultBucket();
|
|
1039
|
+
const url = `${STORAGE_API_BASE}/b/${encodeURIComponent(bucket)}/o/${encodeURIComponent(path)}?alt=media`;
|
|
1040
|
+
const response = await fetch(url, {
|
|
1041
|
+
method: "GET",
|
|
1042
|
+
headers: {
|
|
1043
|
+
"Authorization": `Bearer ${token}`
|
|
1044
|
+
}
|
|
1045
|
+
});
|
|
1046
|
+
if (!response.ok) {
|
|
1047
|
+
const errorText = await response.text();
|
|
1048
|
+
throw new Error(`Failed to download file: ${response.status} ${errorText}`);
|
|
1049
|
+
}
|
|
1050
|
+
return await response.arrayBuffer();
|
|
1051
|
+
}
|
|
1052
|
+
async function deleteFile(path) {
|
|
1053
|
+
const token = await getAdminAccessToken();
|
|
1054
|
+
const bucket = getDefaultBucket();
|
|
1055
|
+
const url = `${STORAGE_API_BASE}/b/${encodeURIComponent(bucket)}/o/${encodeURIComponent(path)}`;
|
|
1056
|
+
const response = await fetch(url, {
|
|
1057
|
+
method: "DELETE",
|
|
1058
|
+
headers: {
|
|
1059
|
+
"Authorization": `Bearer ${token}`
|
|
1060
|
+
}
|
|
1061
|
+
});
|
|
1062
|
+
if (!response.ok) {
|
|
1063
|
+
const errorText = await response.text();
|
|
1064
|
+
throw new Error(`Failed to delete file: ${response.status} ${errorText}`);
|
|
1065
|
+
}
|
|
1066
|
+
}
|
|
1067
|
+
async function getFileMetadata(path) {
|
|
1068
|
+
const token = await getAdminAccessToken();
|
|
1069
|
+
const bucket = getDefaultBucket();
|
|
1070
|
+
const url = `${STORAGE_API_BASE}/b/${encodeURIComponent(bucket)}/o/${encodeURIComponent(path)}`;
|
|
1071
|
+
const response = await fetch(url, {
|
|
1072
|
+
method: "GET",
|
|
1073
|
+
headers: {
|
|
1074
|
+
"Authorization": `Bearer ${token}`
|
|
1075
|
+
}
|
|
1076
|
+
});
|
|
1077
|
+
if (!response.ok) {
|
|
1078
|
+
const errorText = await response.text();
|
|
1079
|
+
throw new Error(`Failed to get file metadata: ${response.status} ${errorText}`);
|
|
1080
|
+
}
|
|
1081
|
+
return await response.json();
|
|
1082
|
+
}
|
|
1083
|
+
async function updateFileMetadata(path, metadata) {
|
|
1084
|
+
const token = await getAdminAccessToken();
|
|
1085
|
+
const bucket = getDefaultBucket();
|
|
1086
|
+
const url = `${STORAGE_API_BASE}/b/${encodeURIComponent(bucket)}/o/${encodeURIComponent(path)}`;
|
|
1087
|
+
const response = await fetch(url, {
|
|
1088
|
+
method: "PATCH",
|
|
1089
|
+
headers: {
|
|
1090
|
+
"Authorization": `Bearer ${token}`,
|
|
1091
|
+
"Content-Type": "application/json"
|
|
1092
|
+
},
|
|
1093
|
+
body: JSON.stringify({ metadata })
|
|
1094
|
+
});
|
|
1095
|
+
if (!response.ok) {
|
|
1096
|
+
const errorText = await response.text();
|
|
1097
|
+
throw new Error(`Failed to update file metadata: ${response.status} ${errorText}`);
|
|
1098
|
+
}
|
|
1099
|
+
return await response.json();
|
|
1100
|
+
}
|
|
1101
|
+
async function makeFilePublic(path) {
|
|
1102
|
+
const token = await getAdminAccessToken();
|
|
1103
|
+
const bucket = getDefaultBucket();
|
|
1104
|
+
const url = `${STORAGE_API_BASE}/b/${encodeURIComponent(bucket)}/o/${encodeURIComponent(path)}/acl`;
|
|
1105
|
+
const response = await fetch(url, {
|
|
1106
|
+
method: "POST",
|
|
1107
|
+
headers: {
|
|
1108
|
+
"Authorization": `Bearer ${token}`,
|
|
1109
|
+
"Content-Type": "application/json"
|
|
1110
|
+
},
|
|
1111
|
+
body: JSON.stringify({
|
|
1112
|
+
entity: "allUsers",
|
|
1113
|
+
role: "READER"
|
|
1114
|
+
})
|
|
1115
|
+
});
|
|
1116
|
+
if (!response.ok) {
|
|
1117
|
+
const errorText = await response.text();
|
|
1118
|
+
throw new Error(`Failed to make file public: ${response.status} ${errorText}`);
|
|
1119
|
+
}
|
|
1120
|
+
}
|
|
1121
|
+
async function listFiles(options = {}) {
|
|
1122
|
+
const token = await getAdminAccessToken();
|
|
1123
|
+
const bucket = getDefaultBucket();
|
|
1124
|
+
const params = new URLSearchParams();
|
|
1125
|
+
if (options.prefix) params.append("prefix", options.prefix);
|
|
1126
|
+
if (options.delimiter) params.append("delimiter", options.delimiter);
|
|
1127
|
+
if (options.maxResults) params.append("maxResults", options.maxResults.toString());
|
|
1128
|
+
if (options.pageToken) params.append("pageToken", options.pageToken);
|
|
1129
|
+
const url = `${STORAGE_API_BASE}/b/${encodeURIComponent(bucket)}/o?${params.toString()}`;
|
|
1130
|
+
const response = await fetch(url, {
|
|
1131
|
+
method: "GET",
|
|
1132
|
+
headers: {
|
|
1133
|
+
"Authorization": `Bearer ${token}`
|
|
1134
|
+
}
|
|
1135
|
+
});
|
|
1136
|
+
if (!response.ok) {
|
|
1137
|
+
const errorText = await response.text();
|
|
1138
|
+
throw new Error(`Failed to list files: ${response.status} ${errorText}`);
|
|
1139
|
+
}
|
|
1140
|
+
const result = await response.json();
|
|
1141
|
+
return {
|
|
1142
|
+
files: result.items || [],
|
|
1143
|
+
nextPageToken: result.nextPageToken
|
|
1144
|
+
};
|
|
1145
|
+
}
|
|
1146
|
+
async function fileExists(path) {
|
|
1147
|
+
try {
|
|
1148
|
+
await getFileMetadata(path);
|
|
1149
|
+
return true;
|
|
1150
|
+
} catch (error) {
|
|
1151
|
+
if (error instanceof Error && error.message.includes("404")) {
|
|
1152
|
+
return false;
|
|
1153
|
+
}
|
|
1154
|
+
throw error;
|
|
1155
|
+
}
|
|
1156
|
+
}
|
|
1157
|
+
|
|
1158
|
+
// src/storage/signed-urls.ts
|
|
1159
|
+
function getExpirationTimestamp(expires) {
|
|
1160
|
+
if (expires instanceof Date) {
|
|
1161
|
+
return Math.floor(expires.getTime() / 1e3);
|
|
1162
|
+
}
|
|
1163
|
+
return Math.floor(Date.now() / 1e3) + expires;
|
|
1164
|
+
}
|
|
1165
|
+
function actionToMethod(action) {
|
|
1166
|
+
switch (action) {
|
|
1167
|
+
case "read":
|
|
1168
|
+
return "GET";
|
|
1169
|
+
case "write":
|
|
1170
|
+
return "PUT";
|
|
1171
|
+
case "delete":
|
|
1172
|
+
return "DELETE";
|
|
1173
|
+
}
|
|
1174
|
+
}
|
|
1175
|
+
function stringToHex(str) {
|
|
1176
|
+
const encoder = new TextEncoder();
|
|
1177
|
+
const bytes = encoder.encode(str);
|
|
1178
|
+
return Array.from(bytes).map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
1179
|
+
}
|
|
1180
|
+
async function signData(data, privateKey) {
|
|
1181
|
+
const pemHeader = "-----BEGIN PRIVATE KEY-----";
|
|
1182
|
+
const pemFooter = "-----END PRIVATE KEY-----";
|
|
1183
|
+
const pemContents = privateKey.replace(pemHeader, "").replace(pemFooter, "").replace(/\s/g, "");
|
|
1184
|
+
const binaryString = atob(pemContents);
|
|
1185
|
+
const bytes = new Uint8Array(binaryString.length);
|
|
1186
|
+
for (let i = 0; i < binaryString.length; i++) {
|
|
1187
|
+
bytes[i] = binaryString.charCodeAt(i);
|
|
1188
|
+
}
|
|
1189
|
+
const key = await crypto.subtle.importKey(
|
|
1190
|
+
"pkcs8",
|
|
1191
|
+
bytes,
|
|
1192
|
+
{
|
|
1193
|
+
name: "RSASSA-PKCS1-v1_5",
|
|
1194
|
+
hash: "SHA-256"
|
|
1195
|
+
},
|
|
1196
|
+
false,
|
|
1197
|
+
["sign"]
|
|
1198
|
+
);
|
|
1199
|
+
const encoder = new TextEncoder();
|
|
1200
|
+
const dataBytes = encoder.encode(data);
|
|
1201
|
+
const signature = await crypto.subtle.sign(
|
|
1202
|
+
"RSASSA-PKCS1-v1_5",
|
|
1203
|
+
key,
|
|
1204
|
+
dataBytes
|
|
1205
|
+
);
|
|
1206
|
+
const signatureArray = new Uint8Array(signature);
|
|
1207
|
+
return Array.from(signatureArray).map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
1208
|
+
}
|
|
1209
|
+
async function generateSignedUrl(path, options) {
|
|
1210
|
+
const serviceAccount = getServiceAccount();
|
|
1211
|
+
const projectId = getProjectId();
|
|
1212
|
+
const bucket = `${projectId}.appspot.com`;
|
|
1213
|
+
const method = actionToMethod(options.action);
|
|
1214
|
+
const expiration = getExpirationTimestamp(options.expires);
|
|
1215
|
+
const timestamp = Math.floor(Date.now() / 1e3);
|
|
1216
|
+
const datestamp = new Date(timestamp * 1e3).toISOString().split("T")[0].replace(/-/g, "");
|
|
1217
|
+
const credentialScope = `${datestamp}/auto/storage/goog4_request`;
|
|
1218
|
+
const credential = `${serviceAccount.client_email}/${credentialScope}`;
|
|
1219
|
+
const canonicalHeaders = `host:storage.googleapis.com
|
|
1220
|
+
`;
|
|
1221
|
+
const signedHeaders = "host";
|
|
1222
|
+
const queryParams = {
|
|
1223
|
+
"X-Goog-Algorithm": "GOOG4-RSA-SHA256",
|
|
1224
|
+
"X-Goog-Credential": credential,
|
|
1225
|
+
"X-Goog-Date": `${datestamp}T000000Z`,
|
|
1226
|
+
"X-Goog-Expires": (expiration - timestamp).toString(),
|
|
1227
|
+
"X-Goog-SignedHeaders": signedHeaders
|
|
1228
|
+
};
|
|
1229
|
+
if (options.contentType) {
|
|
1230
|
+
queryParams["response-content-type"] = options.contentType;
|
|
1231
|
+
}
|
|
1232
|
+
if (options.responseDisposition) {
|
|
1233
|
+
queryParams["response-content-disposition"] = options.responseDisposition;
|
|
1234
|
+
}
|
|
1235
|
+
if (options.responseType) {
|
|
1236
|
+
queryParams["response-content-type"] = options.responseType;
|
|
1237
|
+
}
|
|
1238
|
+
const sortedParams = Object.keys(queryParams).sort();
|
|
1239
|
+
const canonicalQueryString = sortedParams.map((key) => `${encodeURIComponent(key)}=${encodeURIComponent(queryParams[key])}`).join("&");
|
|
1240
|
+
const canonicalUri = `/${bucket}/${path}`;
|
|
1241
|
+
const canonicalRequest = [
|
|
1242
|
+
method,
|
|
1243
|
+
canonicalUri,
|
|
1244
|
+
canonicalQueryString,
|
|
1245
|
+
canonicalHeaders,
|
|
1246
|
+
signedHeaders,
|
|
1247
|
+
"UNSIGNED-PAYLOAD"
|
|
1248
|
+
].join("\n");
|
|
1249
|
+
const canonicalRequestHash = stringToHex(canonicalRequest);
|
|
1250
|
+
const stringToSign = [
|
|
1251
|
+
"GOOG4-RSA-SHA256",
|
|
1252
|
+
`${datestamp}T000000Z`,
|
|
1253
|
+
credentialScope,
|
|
1254
|
+
canonicalRequestHash
|
|
1255
|
+
].join("\n");
|
|
1256
|
+
const signature = await signData(stringToSign, serviceAccount.private_key);
|
|
1257
|
+
const signedUrl = `https://storage.googleapis.com${canonicalUri}?${canonicalQueryString}&X-Goog-Signature=${signature}`;
|
|
1258
|
+
return signedUrl;
|
|
1259
|
+
}
|
|
941
1260
|
export {
|
|
942
1261
|
FieldValue,
|
|
943
1262
|
addDocument,
|
|
@@ -945,16 +1264,23 @@ export {
|
|
|
945
1264
|
clearConfig,
|
|
946
1265
|
clearTokenCache,
|
|
947
1266
|
deleteDocument,
|
|
1267
|
+
deleteFile,
|
|
1268
|
+
downloadFile,
|
|
1269
|
+
fileExists,
|
|
1270
|
+
generateSignedUrl,
|
|
948
1271
|
getAdminAccessToken,
|
|
949
1272
|
getAuth,
|
|
950
1273
|
getConfig,
|
|
951
1274
|
getDocument,
|
|
1275
|
+
getFileMetadata,
|
|
952
1276
|
getProjectId,
|
|
953
1277
|
getServiceAccount,
|
|
954
1278
|
getUserFromToken,
|
|
955
1279
|
initializeApp,
|
|
1280
|
+
listFiles,
|
|
956
1281
|
queryDocuments,
|
|
957
1282
|
setDocument,
|
|
958
1283
|
updateDocument,
|
|
1284
|
+
uploadFile,
|
|
959
1285
|
verifyIdToken
|
|
960
1286
|
};
|
package/package.json
CHANGED
|
Binary file
|