-
Notifications
You must be signed in to change notification settings - Fork 937
Stop using WebChannelConnection in Lite SDK #3482
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
Binary Size ReportAffected SDKs
Test Logs |
Looks like I need to plumb the JSON serialization settings through to all places where we do serialization. Back to drawing board. |
💥 No ChangesetLatest commit: c38f0fa Merging this PR will not cause any packages to be released. If these changes should not cause updates to packages in this repo, this is fine 🙂 If these changes should be published to npm, you need to add a changeset. This PR includes no changesetsWhen changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types Click here to learn what changesets are, and how to add one. Click here if you're a maintainer who wants to add a changeset to this PR |
8e04faa
to
46c46df
Compare
46c46df
to
e63bea7
Compare
* limitations under the License. | ||
*/ | ||
|
||
import axios, { AxiosRequestConfig, AxiosResponse } from 'axios'; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
How about using a fetch polyfill e.g. isomorphic-fetch
, so browser and node can share implementation? So this file can be removed and node_lite/connection.ts
becomes:
import `isomorphic-fetch`;
export * from '../browser_lite/connection';
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I like the idea. I ended up using node-fetch
, because:
- isomorphic-fetch is not maintained anymore
node-fetch
is not a Polyfill. I think it is better if we don't inject our own code into the global namspace.
return node.newConnection(databaseInfo); | ||
return isLite() | ||
? nodeLite.newConnection(databaseInfo) | ||
: node.newConnection(databaseInfo); | ||
} else if (isReactNative()) { | ||
return rn.newConnection(databaseInfo); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we add the isLite()
check for RN for completeness? I know we won't hit this code path currently. and similarly in platform/serializer.ts
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I actually reworked this code. We would have to change every callsite and it gets quite ugly with all those branches. Instead, I am now using an environment variable to set the platform for ts-node. It feels a little less ugly to me, but opinions can differ here...
); | ||
} | ||
|
||
return JSON.parse(await response.text()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is it different than just await response.json()
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nope. Shortened.
@@ -16,7 +16,7 @@ | |||
*/ | |||
|
|||
import * as firestore from '@firebase/firestore-types'; | |||
import { Deferred } from '@firebase/util'; | |||
import { Deferred } from '../../util/promise'; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we just use Deferred
from @firebase/util
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is the only usage of the Deferred promise in Firestore. Since I realized this is test-only, it probably doesn't matter much. I can revert this change with my next update to this PR (I just got the CI to green after one week...)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Removed this change.
try { | ||
await connection.invokeRPC('RunQuery', {}, null); | ||
fail(); | ||
} catch (e) { | ||
expect(e.code).to.equal(Code.UNKNOWN); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Does this work?
expect(connection.invokeRPC('RunQuery', {}, null)).to.be.eventually.rejectedWith(Error).and.have.property('code', Code.UNKNOWN);
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Updated (with a simplification)
@@ -24,6 +24,8 @@ import '../../index'; | |||
* https://github.com/webpack-contrib/karma-webpack#alternative-usage | |||
*/ | |||
|
|||
process.env.TEST_PLATFORM = 'browser'; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why do you need to set it if if it is the browser test? TEST_PLATFORM
is only used for node test, right?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes... I had to change the webpack bundler to allow me to inject browser-lite. With that change, we are now using the new networking stack for the Lite test. The change in this file was meant to do this, but was 100% wrong (but with the best of intentions!).
urlRpcName !== undefined, | ||
'Unknown REST mapping for: ' + rpcName | ||
); | ||
const path = ((req as unknown) as Indexable).parent || this.databaseRoot; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This seems like it could be more self-contained if the caller passed the path they wanted based on the RPC they were invoking. As it stands this is kind of magical because Indexable
doesn't say anything about how the type should work.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I changed the callsites to pass in the path and also removed the manual deletion of parent
and database
.
@@ -243,9 +248,9 @@ export function mapCodeFromHttpStatus(status: number): Code { | |||
return Code.OK; | |||
|
|||
case 400: // Bad Request | |||
return Code.INVALID_ARGUMENT; | |||
return Code.FAILED_PRECONDITION; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This doesn't seem right. A 400 status most typically indicates that the client has sent a bad request, not that the request is valid but can't be performed now. INVALID_ARGUMENT
is the primary status in https://cloud.google.com/apis/design/errors#handling_errors.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So we currently send this request to write a non-existing document in a transaction:
writes: [
{
update: {
name: 'projects/test-emulator/databases/(default)/documents/test-collection/noHB7gQj6Tp98raxkpxU',
fields: { counter: { integerValue: '1' } }
},
currentDocument: { updateTime: '1970-01-01T00:00:00.000000000Z' }
}
]
This updateTime: epoch
trick/hack seems to do the same thing as { currentDocument: { exists: false }}
but it returns a 400 (against Prod and the emulator). If we return INVALID_ARGUMENT here, the transaction is not retried.
One thing to note is that mapCodeFromHttpStatus
was actually never used before, so at least this change should not affect any existing callsites.
* Base class for all Rest-based connections to the backend (WebChannel and | ||
* HTTP). | ||
*/ | ||
export abstract class RestConnection implements Connection { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Within any given build isn't the case that there will ever be one connection type? It seems like the bundler could pick which "instance" you get and we could make these all free functions. Or is that coming in a separate PR?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We can do this either way, but I thought it would be cleaner to use inheritance here. There will only ever be one implementation for this class in any given bundle.
7869a3e
to
135b557
Compare
135b557
to
c38f0fa
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM
This PR changes the Lite SDK to use the native networking stack in favor of WebChannel. It uses
fetch
on Browsers, andnode-fetch
on Node (which is API compatible withfetch
).Most of the code in this PR is to plumb new platform settings through (which I am not happy about, but I wasn't able to come up with another approach). The main problem is that the Node Lite SDK needs to use Proto3-Json if we switch away from WebChannel, which means that we need to change all callsites for
newSerializer
. To make this even worse, I added a test-only environment variable to allow me to dynamically import the correct platform. Suggestions welcome.