Skip to content

Commit

Permalink
fix(firestore, bytes): return Bytes for modular API usage, fix deprec…
Browse files Browse the repository at this point in the history
…ation

- deprecation warnings were still emitted because internally Bytes and Blob were confused
- now with a simple type inheritance trick, Bytes *is* FirestoreBlob, so at a low-level
  we always return Bytes (so modular works), and at a high level we just construct Bytes
  carefully so it is still a FirestoreBlob and doesn't do extra parsing
  • Loading branch information
mikehardy committed Feb 19, 2025
1 parent c3b582f commit 5a29425
Show file tree
Hide file tree
Showing 6 changed files with 38 additions and 27 deletions.
12 changes: 7 additions & 5 deletions packages/firestore/e2e/Bytes.e2e.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@
*
*/

import FirestoreBlob from '../lib/FirestoreBlob';

const testObject = { hello: 'world' };
const testString = JSON.stringify(testObject);
const testBuffer = [123, 34, 104, 101, 108, 108, 111, 34, 58, 34, 119, 111, 114, 108, 100, 34, 125];
Expand All @@ -25,12 +27,12 @@ describe('Bytes modular', function () {
const { Bytes } = firestoreModular;
const myBytes = Bytes.fromBase64String(testBase64);
myBytes.should.be.instanceOf(Bytes);
myBytes._blob.should.be.instanceOf(firebase.firestore.Blob);
myBytes._blob._binaryString.should.equal(testString);
myBytes.should.be.instanceOf(FirestoreBlob);
myBytes._binaryString.should.equal(testString);
should.deepEqual(
JSON.parse(myBytes._blob._binaryString),
JSON.parse(myBytes._binaryString),
testObject,
'Expected Blob _binaryString internals to serialize to json and match test object',
'Expected Bytes _binaryString internals to serialize to json and match test object',
);
});

Expand All @@ -47,7 +49,7 @@ describe('Bytes modular', function () {
const { Bytes } = firestoreModular;
const myBytes = Bytes.fromUint8Array(testUInt8Array);
myBytes.should.be.instanceOf(Bytes);
const json = JSON.parse(myBytes._blob._binaryString);
const json = JSON.parse(myBytes._binaryString);
json.hello.should.equal('world');
});

Expand Down
24 changes: 14 additions & 10 deletions packages/firestore/e2e/DocumentSnapshot/data.e2e.js
Original file line number Diff line number Diff line change
Expand Up @@ -183,7 +183,8 @@ describe('firestore().doc() -> snapshot.data()', function () {
// });

it('handles all data types', async function () {
const { getFirestore, doc, setDoc, getDoc, deleteDoc } = firestoreModular;
const { getFirestore, doc, setDoc, getDoc, deleteDoc, Timestamp, Bytes, GeoPoint } =
firestoreModular;
const types = {
string: '123456',
stringEmpty: '',
Expand All @@ -196,11 +197,11 @@ describe('firestore().doc() -> snapshot.data()', function () {
map: {}, // set after
array: [], // set after,
nullValue: null,
timestamp: new firebase.firestore.Timestamp(123, 123456),
timestamp: new Timestamp(123, 123456),
date: new Date(),
geopoint: new firebase.firestore.GeoPoint(1, 2),
reference: firebase.firestore().doc(`${COLLECTION}/foobar`),
blob: firebase.firestore.Blob.fromBase64String(blobBase64),
geopoint: new GeoPoint(1, 2),
reference: doc(getFirestore(), `${COLLECTION}/foobar`),
bytes: Bytes.fromBase64String(blobBase64),
};

const map = { foo: 'bar' };
Expand Down Expand Up @@ -246,24 +247,27 @@ describe('firestore().doc() -> snapshot.data()', function () {
should.equal(data.nullValue, null);

// Timestamp
data.timestamp.should.be.an.instanceOf(firebase.firestore.Timestamp);
data.timestamp.should.be.an.instanceOf(Timestamp);
data.timestamp.seconds.should.be.a.Number();
data.timestamp.nanoseconds.should.be.a.Number();
data.date.should.be.an.instanceOf(firebase.firestore.Timestamp);
data.date.should.be.an.instanceOf(Timestamp);
data.date.seconds.should.be.a.Number();
data.date.nanoseconds.should.be.a.Number();

// GeoPoint
data.geopoint.should.be.an.instanceOf(firebase.firestore.GeoPoint);
data.geopoint.should.be.an.instanceOf(GeoPoint);
data.geopoint.latitude.should.be.a.Number();
data.geopoint.longitude.should.be.a.Number();

// Reference
// data.reference.should.be.an.instanceOf();
data.reference.path.should.equal(`${COLLECTION}/foobar`);

// Blob
data.blob.toBase64.should.be.a.Function();
// Bytes
data.bytes.should.be.an.instanceOf(Bytes);
types.bytes.isEqual(data.bytes);
data.bytes.isEqual(types.bytes);
data.bytes.toBase64.should.be.a.Function();

await deleteDoc(ref);
});
Expand Down
2 changes: 1 addition & 1 deletion packages/firestore/lib/FirestoreBlob.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
import { Base64, isString } from '@react-native-firebase/app/lib/common';

export default class FirestoreBlob {
constructor(internal = false, binaryString) {
constructor(internal = false, binaryString = undefined) {
if (internal === false) {
throw new Error(
'firebase.firestore.Blob constructor is private, use Blob.<field>() instead.',
Expand Down
2 changes: 1 addition & 1 deletion packages/firestore/lib/modular/Bytes.d.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
export declare class Bytes {
export declare class Bytes extends FirestoreBlob {
static fromBase64String(base64: string): Bytes;

static fromUint8Array(array: Uint8Array): Bytes;
Expand Down
19 changes: 11 additions & 8 deletions packages/firestore/lib/modular/Bytes.js
Original file line number Diff line number Diff line change
@@ -1,45 +1,48 @@
import { firebase } from '../index';
import FirestoreBlob from '../FirestoreBlob';

/**
* An immutable object representing an array of bytes.
*/
export class Bytes {
export class Bytes extends FirestoreBlob {
/**
* @hideconstructor
* @param {firebase.firestore.Blob} blob
*/
constructor(blob) {
this._blob = blob;
super(true);
// binary string was already parsed and created, potentially expensive
// don't parse it again, just set it into the new FirebaseBlob
this._binaryString = blob._binaryString;
}

/**
* @param {string} base64
* @returns {Bytes}
*/
static fromBase64String(base64) {
return new Bytes(firebase.firestore.Blob.fromBase64String(base64));
return new Bytes(FirestoreBlob.fromBase64String(base64));
}

/**
* @param {Uint8Array} array
* @returns {Bytes}
*/
static fromUint8Array(array) {
return new Bytes(firebase.firestore.Blob.fromUint8Array(array));
return new Bytes(FirestoreBlob.fromUint8Array(array));
}

/**
* @returns {string}
*/
toBase64() {
return this._blob.toBase64();
return super.toBase64();
}

/**
* @returns {Uint8Array}
*/
toUint8Array() {
return this._blob.toUint8Array();
return super.toUint8Array();
}

/**
Expand All @@ -54,6 +57,6 @@ export class Bytes {
* @returns {boolean}
*/
isEqual(other) {
return this._blob.isEqual(other._blob);
return super.isEqual(other);
}
}
6 changes: 4 additions & 2 deletions packages/firestore/lib/utils/serialize.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ import FirestoreGeoPoint from '../FirestoreGeoPoint';
import FirestorePath from '../FirestorePath';
import FirestoreTimestamp from '../FirestoreTimestamp';
import { getTypeMapInt, getTypeMapName } from './typemap';
import { Bytes } from '../modular/Bytes';

// To avoid React Native require cycle warnings
let FirestoreDocumentReference = null;
Expand Down Expand Up @@ -179,7 +180,8 @@ export function generateNativeData(value, ignoreUndefined) {
return getTypeMapInt('timestamp', [value.seconds, value.nanoseconds]);
}

if (value instanceof FirestoreBlob) {
// Modular API uses Bytes instead of Blob
if (value instanceof FirestoreBlob || value instanceof Bytes) {
return getTypeMapInt('blob', value.toBase64());
}

Expand Down Expand Up @@ -276,7 +278,7 @@ export function parseNativeData(firestore, nativeArray) {
case 'timestamp':
return new FirestoreTimestamp(value[0], value[1]);
case 'blob':
return FirestoreBlob.fromBase64String(value);
return Bytes.fromBase64String(value);
default:
// eslint-disable-next-line no-console
console.warn(`Unknown data type received from native channel: ${type}`);
Expand Down

0 comments on commit 5a29425

Please sign in to comment.