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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -443,8 +443,7 @@ var FieldValue = {
443
443
  delete: deleteField
444
444
  };
445
445
 
446
- // src/firestore-rest.ts
447
- var FIRESTORE_API = "https://firestore.googleapis.com/v1";
446
+ // src/firestore/converters.ts
448
447
  function toFirestoreValue(value) {
449
448
  if (value === null || value === void 0) {
450
449
  return { nullValue: null };
@@ -504,58 +503,6 @@ function convertToFirestoreFormat(data) {
504
503
  }
505
504
  return result;
506
505
  }
507
- function extractFieldTransforms(data, fieldPrefix = "") {
508
- const transforms = [];
509
- for (const [key, value] of Object.entries(data)) {
510
- const fieldPath = fieldPrefix ? `${fieldPrefix}.${key}` : key;
511
- if (isFieldValue(value)) {
512
- switch (value._type) {
513
- case "serverTimestamp":
514
- transforms.push({
515
- fieldPath,
516
- setToServerValue: "REQUEST_TIME"
517
- });
518
- break;
519
- case "increment":
520
- transforms.push({
521
- fieldPath,
522
- increment: toFirestoreValue(value._value)
523
- });
524
- break;
525
- case "arrayUnion":
526
- transforms.push({
527
- fieldPath,
528
- appendMissingElements: {
529
- values: value._value.map((v) => toFirestoreValue(v))
530
- }
531
- });
532
- break;
533
- case "arrayRemove":
534
- transforms.push({
535
- fieldPath,
536
- removeAllFromArray: {
537
- values: value._value.map((v) => toFirestoreValue(v))
538
- }
539
- });
540
- break;
541
- }
542
- }
543
- }
544
- return transforms;
545
- }
546
- function removeFieldTransforms(data) {
547
- const result = {};
548
- for (const [key, value] of Object.entries(data)) {
549
- if (isFieldValue(value)) {
550
- if (value._type === "delete") {
551
- continue;
552
- }
553
- continue;
554
- }
555
- result[key] = value;
556
- }
557
- return result;
558
- }
559
506
  function fromFirestoreValue(value) {
560
507
  if ("stringValue" in value) {
561
508
  return value.stringValue;
@@ -584,12 +531,17 @@ function fromFirestoreValue(value) {
584
531
  return null;
585
532
  }
586
533
  function convertFromFirestoreFormat(fields) {
534
+ if (!fields) {
535
+ return {};
536
+ }
587
537
  const result = {};
588
538
  for (const [key, value] of Object.entries(fields)) {
589
539
  result[key] = fromFirestoreValue(value);
590
540
  }
591
541
  return result;
592
542
  }
543
+
544
+ // src/firestore/query-builder.ts
593
545
  function buildStructuredQuery(collectionPath, options) {
594
546
  const pathSegments = collectionPath.split("/");
595
547
  const collectionId = pathSegments[pathSegments.length - 1];
@@ -672,14 +624,109 @@ function mapWhereOp(op) {
672
624
  };
673
625
  return opMap[op] || "EQUAL";
674
626
  }
675
- async function setDocument(collectionPath, documentId, data, options) {
627
+
628
+ // src/firestore/transforms.ts
629
+ function extractFieldTransforms(data, fieldPrefix = "") {
630
+ const transforms = [];
631
+ for (const [key, value] of Object.entries(data)) {
632
+ const fieldPath = fieldPrefix ? `${fieldPrefix}.${key}` : key;
633
+ if (isFieldValue(value)) {
634
+ switch (value._type) {
635
+ case "serverTimestamp":
636
+ transforms.push({
637
+ fieldPath,
638
+ setToServerValue: "REQUEST_TIME"
639
+ });
640
+ break;
641
+ case "increment":
642
+ transforms.push({
643
+ fieldPath,
644
+ increment: toFirestoreValue(value._value)
645
+ });
646
+ break;
647
+ case "arrayUnion":
648
+ transforms.push({
649
+ fieldPath,
650
+ appendMissingElements: {
651
+ values: value._value.map((v) => toFirestoreValue(v))
652
+ }
653
+ });
654
+ break;
655
+ case "arrayRemove":
656
+ transforms.push({
657
+ fieldPath,
658
+ removeAllFromArray: {
659
+ values: value._value.map((v) => toFirestoreValue(v))
660
+ }
661
+ });
662
+ break;
663
+ }
664
+ }
665
+ }
666
+ return transforms;
667
+ }
668
+ function removeFieldTransforms(data) {
669
+ const result = {};
670
+ for (const [key, value] of Object.entries(data)) {
671
+ if (isFieldValue(value)) {
672
+ if (value._type === "delete") {
673
+ continue;
674
+ }
675
+ continue;
676
+ }
677
+ result[key] = value;
678
+ }
679
+ return result;
680
+ }
681
+
682
+ // src/firestore-rest.ts
683
+ var FIRESTORE_API = "https://firestore.googleapis.com/v1";
684
+ async function commitWrites(writes) {
676
685
  const accessToken = await getAdminAccessToken();
677
686
  const projectId = getProjectId();
678
- const url = `${FIRESTORE_API}/projects/${projectId}/databases/(default)/documents/${collectionPath}/${documentId}`;
687
+ const url = `${FIRESTORE_API}/projects/${projectId}/databases/(default)/documents:commit`;
688
+ const response = await fetch(url, {
689
+ method: "POST",
690
+ headers: {
691
+ "Authorization": `Bearer ${accessToken}`,
692
+ "Content-Type": "application/json"
693
+ },
694
+ body: JSON.stringify({ writes })
695
+ });
696
+ if (!response.ok) {
697
+ const errorText = await response.text();
698
+ throw new Error(`Failed to commit writes: ${errorText}`);
699
+ }
700
+ }
701
+ async function setDocument(collectionPath, documentId, data, options) {
702
+ const projectId = getProjectId();
703
+ const documentPath = `projects/${projectId}/databases/(default)/documents/${collectionPath}/${documentId}`;
679
704
  const cleanData = removeFieldTransforms(data);
680
705
  const firestoreData = convertToFirestoreFormat(cleanData);
681
706
  const transforms = extractFieldTransforms(data);
682
- const body = { fields: firestoreData };
707
+ if (transforms.length > 0) {
708
+ const updateWrite = {
709
+ update: {
710
+ name: documentPath,
711
+ fields: firestoreData
712
+ },
713
+ updateTransforms: transforms
714
+ };
715
+ const nonTransformFields = Object.keys(cleanData);
716
+ if (nonTransformFields.length > 0) {
717
+ if (options?.merge) {
718
+ updateWrite.updateMask = { fieldPaths: ["*"] };
719
+ } else if (options?.mergeFields && options.mergeFields.length > 0) {
720
+ updateWrite.updateMask = { fieldPaths: options.mergeFields };
721
+ } else {
722
+ updateWrite.updateMask = { fieldPaths: nonTransformFields };
723
+ }
724
+ }
725
+ await commitWrites([updateWrite]);
726
+ return;
727
+ }
728
+ const accessToken = await getAdminAccessToken();
729
+ const url = `${FIRESTORE_API}/${documentPath}`;
683
730
  let queryParams = "";
684
731
  if (options?.merge) {
685
732
  queryParams = "?updateMask.fieldPaths=*";
@@ -687,16 +734,13 @@ async function setDocument(collectionPath, documentId, data, options) {
687
734
  const fieldPaths = options.mergeFields.join("&updateMask.fieldPaths=");
688
735
  queryParams = `?updateMask.fieldPaths=${fieldPaths}`;
689
736
  }
690
- if (transforms.length > 0) {
691
- body.transforms = transforms;
692
- }
693
737
  const response = await fetch(`${url}${queryParams}`, {
694
738
  method: "PATCH",
695
739
  headers: {
696
740
  "Authorization": `Bearer ${accessToken}`,
697
741
  "Content-Type": "application/json"
698
742
  },
699
- body: JSON.stringify(body)
743
+ body: JSON.stringify({ fields: firestoreData })
700
744
  });
701
745
  if (!response.ok) {
702
746
  const errorText = await response.text();
@@ -754,25 +798,51 @@ async function getDocument(collectionPath, documentId) {
754
798
  return convertFromFirestoreFormat(result.fields);
755
799
  }
756
800
  async function updateDocument(collectionPath, documentId, data) {
757
- const accessToken = await getAdminAccessToken();
758
801
  const projectId = getProjectId();
759
- const url = `${FIRESTORE_API}/projects/${projectId}/databases/(default)/documents/${collectionPath}/${documentId}`;
802
+ const documentPath = `projects/${projectId}/databases/(default)/documents/${collectionPath}/${documentId}`;
760
803
  const cleanData = removeFieldTransforms(data);
761
804
  const firestoreData = convertToFirestoreFormat(cleanData);
762
805
  const transforms = extractFieldTransforms(data);
763
- const updateMaskFields = Object.keys(data).filter((key) => !isFieldValue(data[key]) || data[key]._type !== "delete");
764
- const updateMaskParams = updateMaskFields.map((field) => `updateMask.fieldPaths=${encodeURIComponent(field)}`).join("&");
765
- const body = { fields: firestoreData };
806
+ const updateMaskFields = Object.keys(data).filter((key) => {
807
+ if (!isFieldValue(data[key])) {
808
+ return true;
809
+ }
810
+ return data[key]._type === "delete";
811
+ });
766
812
  if (transforms.length > 0) {
767
- body.transforms = transforms;
813
+ const nonTransformFields = Object.keys(cleanData);
814
+ if (nonTransformFields.length === 0) {
815
+ const transformWrite = {
816
+ transform: {
817
+ document: documentPath,
818
+ fieldTransforms: transforms
819
+ }
820
+ };
821
+ await commitWrites([transformWrite]);
822
+ return;
823
+ }
824
+ const updateWrite = {
825
+ update: {
826
+ name: documentPath,
827
+ fields: firestoreData
828
+ },
829
+ updateMask: { fieldPaths: updateMaskFields },
830
+ updateTransforms: transforms,
831
+ currentDocument: { exists: true }
832
+ };
833
+ await commitWrites([updateWrite]);
834
+ return;
768
835
  }
836
+ const accessToken = await getAdminAccessToken();
837
+ const url = `${FIRESTORE_API}/${documentPath}`;
838
+ const updateMaskParams = updateMaskFields.map((field) => `updateMask.fieldPaths=${encodeURIComponent(field)}`).join("&");
769
839
  const response = await fetch(`${url}?${updateMaskParams}&currentDocument.exists=true`, {
770
840
  method: "PATCH",
771
841
  headers: {
772
842
  "Authorization": `Bearer ${accessToken}`,
773
843
  "Content-Type": "application/json"
774
844
  },
775
- body: JSON.stringify(body)
845
+ body: JSON.stringify({ fields: firestoreData })
776
846
  });
777
847
  if (!response.ok) {
778
848
  const errorText = await response.text();
package/dist/index.mjs CHANGED
@@ -400,8 +400,7 @@ var FieldValue = {
400
400
  delete: deleteField
401
401
  };
402
402
 
403
- // src/firestore-rest.ts
404
- var FIRESTORE_API = "https://firestore.googleapis.com/v1";
403
+ // src/firestore/converters.ts
405
404
  function toFirestoreValue(value) {
406
405
  if (value === null || value === void 0) {
407
406
  return { nullValue: null };
@@ -461,58 +460,6 @@ function convertToFirestoreFormat(data) {
461
460
  }
462
461
  return result;
463
462
  }
464
- function extractFieldTransforms(data, fieldPrefix = "") {
465
- const transforms = [];
466
- for (const [key, value] of Object.entries(data)) {
467
- const fieldPath = fieldPrefix ? `${fieldPrefix}.${key}` : key;
468
- if (isFieldValue(value)) {
469
- switch (value._type) {
470
- case "serverTimestamp":
471
- transforms.push({
472
- fieldPath,
473
- setToServerValue: "REQUEST_TIME"
474
- });
475
- break;
476
- case "increment":
477
- transforms.push({
478
- fieldPath,
479
- increment: toFirestoreValue(value._value)
480
- });
481
- break;
482
- case "arrayUnion":
483
- transforms.push({
484
- fieldPath,
485
- appendMissingElements: {
486
- values: value._value.map((v) => toFirestoreValue(v))
487
- }
488
- });
489
- break;
490
- case "arrayRemove":
491
- transforms.push({
492
- fieldPath,
493
- removeAllFromArray: {
494
- values: value._value.map((v) => toFirestoreValue(v))
495
- }
496
- });
497
- break;
498
- }
499
- }
500
- }
501
- return transforms;
502
- }
503
- function removeFieldTransforms(data) {
504
- const result = {};
505
- for (const [key, value] of Object.entries(data)) {
506
- if (isFieldValue(value)) {
507
- if (value._type === "delete") {
508
- continue;
509
- }
510
- continue;
511
- }
512
- result[key] = value;
513
- }
514
- return result;
515
- }
516
463
  function fromFirestoreValue(value) {
517
464
  if ("stringValue" in value) {
518
465
  return value.stringValue;
@@ -541,12 +488,17 @@ function fromFirestoreValue(value) {
541
488
  return null;
542
489
  }
543
490
  function convertFromFirestoreFormat(fields) {
491
+ if (!fields) {
492
+ return {};
493
+ }
544
494
  const result = {};
545
495
  for (const [key, value] of Object.entries(fields)) {
546
496
  result[key] = fromFirestoreValue(value);
547
497
  }
548
498
  return result;
549
499
  }
500
+
501
+ // src/firestore/query-builder.ts
550
502
  function buildStructuredQuery(collectionPath, options) {
551
503
  const pathSegments = collectionPath.split("/");
552
504
  const collectionId = pathSegments[pathSegments.length - 1];
@@ -629,14 +581,109 @@ function mapWhereOp(op) {
629
581
  };
630
582
  return opMap[op] || "EQUAL";
631
583
  }
632
- async function setDocument(collectionPath, documentId, data, options) {
584
+
585
+ // src/firestore/transforms.ts
586
+ function extractFieldTransforms(data, fieldPrefix = "") {
587
+ const transforms = [];
588
+ for (const [key, value] of Object.entries(data)) {
589
+ const fieldPath = fieldPrefix ? `${fieldPrefix}.${key}` : key;
590
+ if (isFieldValue(value)) {
591
+ switch (value._type) {
592
+ case "serverTimestamp":
593
+ transforms.push({
594
+ fieldPath,
595
+ setToServerValue: "REQUEST_TIME"
596
+ });
597
+ break;
598
+ case "increment":
599
+ transforms.push({
600
+ fieldPath,
601
+ increment: toFirestoreValue(value._value)
602
+ });
603
+ break;
604
+ case "arrayUnion":
605
+ transforms.push({
606
+ fieldPath,
607
+ appendMissingElements: {
608
+ values: value._value.map((v) => toFirestoreValue(v))
609
+ }
610
+ });
611
+ break;
612
+ case "arrayRemove":
613
+ transforms.push({
614
+ fieldPath,
615
+ removeAllFromArray: {
616
+ values: value._value.map((v) => toFirestoreValue(v))
617
+ }
618
+ });
619
+ break;
620
+ }
621
+ }
622
+ }
623
+ return transforms;
624
+ }
625
+ function removeFieldTransforms(data) {
626
+ const result = {};
627
+ for (const [key, value] of Object.entries(data)) {
628
+ if (isFieldValue(value)) {
629
+ if (value._type === "delete") {
630
+ continue;
631
+ }
632
+ continue;
633
+ }
634
+ result[key] = value;
635
+ }
636
+ return result;
637
+ }
638
+
639
+ // src/firestore-rest.ts
640
+ var FIRESTORE_API = "https://firestore.googleapis.com/v1";
641
+ async function commitWrites(writes) {
633
642
  const accessToken = await getAdminAccessToken();
634
643
  const projectId = getProjectId();
635
- const url = `${FIRESTORE_API}/projects/${projectId}/databases/(default)/documents/${collectionPath}/${documentId}`;
644
+ const url = `${FIRESTORE_API}/projects/${projectId}/databases/(default)/documents:commit`;
645
+ const response = await fetch(url, {
646
+ method: "POST",
647
+ headers: {
648
+ "Authorization": `Bearer ${accessToken}`,
649
+ "Content-Type": "application/json"
650
+ },
651
+ body: JSON.stringify({ writes })
652
+ });
653
+ if (!response.ok) {
654
+ const errorText = await response.text();
655
+ throw new Error(`Failed to commit writes: ${errorText}`);
656
+ }
657
+ }
658
+ async function setDocument(collectionPath, documentId, data, options) {
659
+ const projectId = getProjectId();
660
+ const documentPath = `projects/${projectId}/databases/(default)/documents/${collectionPath}/${documentId}`;
636
661
  const cleanData = removeFieldTransforms(data);
637
662
  const firestoreData = convertToFirestoreFormat(cleanData);
638
663
  const transforms = extractFieldTransforms(data);
639
- const body = { fields: firestoreData };
664
+ if (transforms.length > 0) {
665
+ const updateWrite = {
666
+ update: {
667
+ name: documentPath,
668
+ fields: firestoreData
669
+ },
670
+ updateTransforms: transforms
671
+ };
672
+ const nonTransformFields = Object.keys(cleanData);
673
+ if (nonTransformFields.length > 0) {
674
+ if (options?.merge) {
675
+ updateWrite.updateMask = { fieldPaths: ["*"] };
676
+ } else if (options?.mergeFields && options.mergeFields.length > 0) {
677
+ updateWrite.updateMask = { fieldPaths: options.mergeFields };
678
+ } else {
679
+ updateWrite.updateMask = { fieldPaths: nonTransformFields };
680
+ }
681
+ }
682
+ await commitWrites([updateWrite]);
683
+ return;
684
+ }
685
+ const accessToken = await getAdminAccessToken();
686
+ const url = `${FIRESTORE_API}/${documentPath}`;
640
687
  let queryParams = "";
641
688
  if (options?.merge) {
642
689
  queryParams = "?updateMask.fieldPaths=*";
@@ -644,16 +691,13 @@ async function setDocument(collectionPath, documentId, data, options) {
644
691
  const fieldPaths = options.mergeFields.join("&updateMask.fieldPaths=");
645
692
  queryParams = `?updateMask.fieldPaths=${fieldPaths}`;
646
693
  }
647
- if (transforms.length > 0) {
648
- body.transforms = transforms;
649
- }
650
694
  const response = await fetch(`${url}${queryParams}`, {
651
695
  method: "PATCH",
652
696
  headers: {
653
697
  "Authorization": `Bearer ${accessToken}`,
654
698
  "Content-Type": "application/json"
655
699
  },
656
- body: JSON.stringify(body)
700
+ body: JSON.stringify({ fields: firestoreData })
657
701
  });
658
702
  if (!response.ok) {
659
703
  const errorText = await response.text();
@@ -711,25 +755,51 @@ async function getDocument(collectionPath, documentId) {
711
755
  return convertFromFirestoreFormat(result.fields);
712
756
  }
713
757
  async function updateDocument(collectionPath, documentId, data) {
714
- const accessToken = await getAdminAccessToken();
715
758
  const projectId = getProjectId();
716
- const url = `${FIRESTORE_API}/projects/${projectId}/databases/(default)/documents/${collectionPath}/${documentId}`;
759
+ const documentPath = `projects/${projectId}/databases/(default)/documents/${collectionPath}/${documentId}`;
717
760
  const cleanData = removeFieldTransforms(data);
718
761
  const firestoreData = convertToFirestoreFormat(cleanData);
719
762
  const transforms = extractFieldTransforms(data);
720
- const updateMaskFields = Object.keys(data).filter((key) => !isFieldValue(data[key]) || data[key]._type !== "delete");
721
- const updateMaskParams = updateMaskFields.map((field) => `updateMask.fieldPaths=${encodeURIComponent(field)}`).join("&");
722
- const body = { fields: firestoreData };
763
+ const updateMaskFields = Object.keys(data).filter((key) => {
764
+ if (!isFieldValue(data[key])) {
765
+ return true;
766
+ }
767
+ return data[key]._type === "delete";
768
+ });
723
769
  if (transforms.length > 0) {
724
- body.transforms = transforms;
770
+ const nonTransformFields = Object.keys(cleanData);
771
+ if (nonTransformFields.length === 0) {
772
+ const transformWrite = {
773
+ transform: {
774
+ document: documentPath,
775
+ fieldTransforms: transforms
776
+ }
777
+ };
778
+ await commitWrites([transformWrite]);
779
+ return;
780
+ }
781
+ const updateWrite = {
782
+ update: {
783
+ name: documentPath,
784
+ fields: firestoreData
785
+ },
786
+ updateMask: { fieldPaths: updateMaskFields },
787
+ updateTransforms: transforms,
788
+ currentDocument: { exists: true }
789
+ };
790
+ await commitWrites([updateWrite]);
791
+ return;
725
792
  }
793
+ const accessToken = await getAdminAccessToken();
794
+ const url = `${FIRESTORE_API}/${documentPath}`;
795
+ const updateMaskParams = updateMaskFields.map((field) => `updateMask.fieldPaths=${encodeURIComponent(field)}`).join("&");
726
796
  const response = await fetch(`${url}?${updateMaskParams}&currentDocument.exists=true`, {
727
797
  method: "PATCH",
728
798
  headers: {
729
799
  "Authorization": `Bearer ${accessToken}`,
730
800
  "Content-Type": "application/json"
731
801
  },
732
- body: JSON.stringify(body)
802
+ body: JSON.stringify({ fields: firestoreData })
733
803
  });
734
804
  if (!response.ok) {
735
805
  const errorText = await response.text();
package/firebase.json ADDED
@@ -0,0 +1,9 @@
1
+ {
2
+ "projects": {
3
+ "default": "prmichaelsen-firebase-e2e"
4
+ },
5
+ "firestore": {
6
+ "rules": "firestore.rules",
7
+ "indexes": "firestore.indexes.json"
8
+ }
9
+ }
@@ -0,0 +1,33 @@
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
+ }
@@ -0,0 +1,11 @@
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
+ }
@@ -0,0 +1,14 @@
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
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@prmichaelsen/firebase-admin-sdk-v8",
3
- "version": "2.0.15",
3
+ "version": "2.0.17",
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",
@@ -26,7 +26,10 @@
26
26
  "typecheck": "tsc --noEmit",
27
27
  "test": "jest",
28
28
  "test:watch": "jest --watch",
29
- "publish": "npm run build && npm publish"
29
+ "test:e2e": "jest --config jest.e2e.config.js",
30
+ "test:e2e:watch": "jest --config jest.e2e.config.js --watch",
31
+ "test:all": "npm test && npm run test:e2e",
32
+ "prepublishOnly": "npm run build"
30
33
  },
31
34
  "keywords": [
32
35
  "firebase",
@@ -0,0 +1,13 @@
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
+ }