Skip to content

Commit 1c19c29

Browse files
committed
WIP: patch database code in nodeJS
1 parent 1748c85 commit 1c19c29

File tree

8 files changed

+299
-70
lines changed

8 files changed

+299
-70
lines changed

src/database.ts

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,10 +23,11 @@ import { enableLogging } from "./database/core/util/util";
2323
import { RepoManager } from "./database/core/RepoManager";
2424
import * as INTERNAL from './database/api/internal';
2525
import * as TEST_ACCESS from './database/api/test_access';
26+
import { isNodeSdk } from "./utils/environment";
2627

2728
export function registerDatabase(instance) {
2829
// Register the Database Service with the 'firebase' namespace.
29-
instance.INTERNAL.registerService(
30+
const namespace = instance.INTERNAL.registerService(
3031
'database',
3132
app => RepoManager.getInstance().databaseFromApp(app),
3233
// firebase.database namespace properties
@@ -40,6 +41,10 @@ export function registerDatabase(instance) {
4041
TEST_ACCESS
4142
}
4243
);
44+
45+
if (isNodeSdk()) {
46+
module.exports = namespace;
47+
}
4348
}
4449

4550
/**
@@ -57,4 +62,4 @@ declare module './app/firebase_app' {
5762
}
5863
}
5964

60-
registerDatabase(firebase);
65+
registerDatabase(firebase);

src/database/core/PersistentConnection.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,9 @@ import { Connection } from "../realtime/Connection";
1111
import { CONSTANTS } from "../../utils/constants";
1212
import {
1313
isMobileCordova,
14-
isReactNative
15-
} from "../login/util/environment";
14+
isReactNative,
15+
isNodeSdk
16+
} from "../../utils/environment";
1617

1718
var RECONNECT_MIN_DELAY = 1000;
1819
var RECONNECT_MAX_DELAY_DEFAULT = 60 * 5 * 1000; // 5 minutes in milliseconds (Case: 1858)
@@ -120,7 +121,7 @@ export class PersistentConnection {
120121
this.authTokenProvider_ = authTokenProvider;
121122
this.forceTokenRefresh_ = false;
122123
this.invalidAuthTokenCount_ = 0;
123-
if (authOverride) {
124+
if (authOverride && !isNodeSdk()) {
124125
throw new Error('Auth override specified in options, but not supported on non Node.js platforms');
125126
}
126127
/** private {Object|null|undefined} */

src/database/core/util/OnlineMonitor.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { assert } from "../../../utils/assert";
22
import { EventEmitter } from "./EventEmitter";
3-
import { isMobileCordova } from "../../login/util/environment";
3+
import { isMobileCordova } from "../../../utils/environment";
44

55
/**
66
* Monitors online state (as reported by window.online/offline events).

src/database/core/util/util.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import { stringToByteArray } from "../../../utils/utf8";
1212
import { stringify } from "../../../utils/json";
1313
import { SessionStorage } from "../storage/storage";
1414
import { RepoInfo } from "../RepoInfo";
15-
import { isNodeSdk } from "../../login/util/environment";
15+
import { isNodeSdk } from "../../../utils/environment";
1616

1717
/**
1818
* Returns a locally-unique ID (generated by just incrementing up from 0 each time its called).

src/database/realtime/BrowserPollConnection.ts

Lines changed: 133 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import { StatsManager } from "../core/stats/StatsManager";
1313
import { PacketReceiver } from "./polling/PacketReceiver";
1414
import { CONSTANTS } from "./Constants";
1515
import { stringify } from "../../utils/json";
16+
import { isNodeSdk } from "../../utils/environment";
1617

1718
// URL query parameters associated with longpolling
1819
// TODO: move more of these out of the global namespace
@@ -175,7 +176,8 @@ export class BrowserPollConnection {
175176
if (self.lastSessionId) {
176177
urlParams[CONSTANTS.LAST_SESSION_PARAM] = self.lastSessionId;
177178
}
178-
if (typeof location !== 'undefined' &&
179+
if (!isNodeSdk() &&
180+
typeof location !== 'undefined' &&
179181
location.href &&
180182
location.href.indexOf(CONSTANTS.FORGE_DOMAIN) !== -1) {
181183
urlParams[CONSTANTS.REFERER_PARAM] = CONSTANTS.FORGE_REF;
@@ -220,7 +222,8 @@ export class BrowserPollConnection {
220222
!BrowserPollConnection.forceDisallow_ &&
221223
typeof document !== 'undefined' && document.createElement != null &&
222224
!isChromeExtensionContentScript() &&
223-
!isWindowsStoreApp()
225+
!isWindowsStoreApp() &&
226+
!isNodeSdk()
224227
);
225228
};
226229

@@ -313,6 +316,7 @@ export class BrowserPollConnection {
313316
* @param {!string} pw
314317
*/
315318
addDisconnectPingFrame(id, pw) {
319+
if (isNodeSdk()) return;
316320
this.myDisconnFrame = document.createElement('iframe');
317321
var urlParams = {};
318322
urlParams[FIREBASE_LONGPOLL_DISCONN_FRAME_REQUEST_PARAM] = 't';
@@ -370,36 +374,42 @@ function FirebaseIFrameScriptHolder(commandCB, onMessageCB, onDisconnectCB, urlF
370374
// incoming data from the server that we're waiting for).
371375
this.sendNewPolls = true;
372376

373-
//Each script holder registers a couple of uniquely named callbacks with the window. These are called from the
374-
//iframes where we put the long-polling script tags. We have two callbacks:
375-
// 1) Command Callback - Triggered for control issues, like starting a connection.
376-
// 2) Message Callback - Triggered when new data arrives.
377-
this.uniqueCallbackIdentifier = LUIDGenerator();
378-
window[FIREBASE_LONGPOLL_COMMAND_CB_NAME + this.uniqueCallbackIdentifier] = commandCB;
379-
window[FIREBASE_LONGPOLL_DATA_CB_NAME + this.uniqueCallbackIdentifier] = onMessageCB;
380-
381-
//Create an iframe for us to add script tags to.
382-
this.myIFrame = this.createIFrame_();
383-
384-
// Set the iframe's contents.
385-
var script = '';
386-
// if we set a javascript url, it's IE and we need to set the document domain. The javascript url is sufficient
387-
// for ie9, but ie8 needs to do it again in the document itself.
388-
if (this.myIFrame.src && this.myIFrame.src.substr(0, 'javascript:'.length) === 'javascript:') {
389-
var currentDomain = document.domain;
390-
script = '<script>document.domain="' + currentDomain + '";</script>';
391-
}
392-
var iframeContents = '<html><body>' + script + '</body></html>';
393-
try {
394-
this.myIFrame.doc.open();
395-
this.myIFrame.doc.write(iframeContents);
396-
this.myIFrame.doc.close();
397-
} catch (e) {
398-
log('frame writing exception');
399-
if (e.stack) {
400-
log(e.stack);
377+
378+
if (!isNodeSdk()) {
379+
//Each script holder registers a couple of uniquely named callbacks with the window. These are called from the
380+
//iframes where we put the long-polling script tags. We have two callbacks:
381+
// 1) Command Callback - Triggered for control issues, like starting a connection.
382+
// 2) Message Callback - Triggered when new data arrives.
383+
this.uniqueCallbackIdentifier = LUIDGenerator();
384+
window[FIREBASE_LONGPOLL_COMMAND_CB_NAME + this.uniqueCallbackIdentifier] = commandCB;
385+
window[FIREBASE_LONGPOLL_DATA_CB_NAME + this.uniqueCallbackIdentifier] = onMessageCB;
386+
387+
//Create an iframe for us to add script tags to.
388+
this.myIFrame = this.createIFrame_();
389+
390+
// Set the iframe's contents.
391+
var script = '';
392+
// if we set a javascript url, it's IE and we need to set the document domain. The javascript url is sufficient
393+
// for ie9, but ie8 needs to do it again in the document itself.
394+
if (this.myIFrame.src && this.myIFrame.src.substr(0, 'javascript:'.length) === 'javascript:') {
395+
var currentDomain = document.domain;
396+
script = '<script>document.domain="' + currentDomain + '";</script>';
397+
}
398+
var iframeContents = '<html><body>' + script + '</body></html>';
399+
try {
400+
this.myIFrame.doc.open();
401+
this.myIFrame.doc.write(iframeContents);
402+
this.myIFrame.doc.close();
403+
} catch (e) {
404+
log('frame writing exception');
405+
if (e.stack) {
406+
log(e.stack);
407+
}
408+
log(e);
401409
}
402-
log(e);
410+
} else {
411+
this.commandCB = commandCB;
412+
this.onMessageCB = onMessageCB;
403413
}
404414
}
405415

@@ -468,6 +478,15 @@ FirebaseIFrameScriptHolder.prototype.close = function() {
468478
}, Math.floor(0));
469479
}
470480

481+
if (isNodeSdk() && this.myID) {
482+
var urlParams = {};
483+
urlParams[FIREBASE_LONGPOLL_DISCONN_FRAME_PARAM] = 't';
484+
urlParams[FIREBASE_LONGPOLL_ID_PARAM] = this.myID;
485+
urlParams[FIREBASE_LONGPOLL_PW_PARAM] = this.myPW;
486+
var theURL = this.urlFn(urlParams);
487+
FirebaseIFrameScriptHolder.nodeRestRequest(theURL);
488+
}
489+
471490
// Protect from being called recursively.
472491
var onDisconnect = this.onDisconnect;
473492
if (onDisconnect) {
@@ -590,33 +609,87 @@ FirebaseIFrameScriptHolder.prototype.addLongPollTag_ = function(url, serial) {
590609
* @param {!function()} loadCB - A callback to be triggered once the script has loaded.
591610
*/
592611
FirebaseIFrameScriptHolder.prototype.addTag = function(url, loadCB) {
593-
var self = this;
594-
setTimeout(function() {
595-
try {
596-
// if we're already closed, don't add this poll
597-
if (!self.sendNewPolls) return;
598-
var newScript = self.myIFrame.doc.createElement('script');
599-
newScript.type = 'text/javascript';
600-
newScript.async = true;
601-
newScript.src = url;
602-
newScript.onload = newScript.onreadystatechange = function() {
603-
var rstate = newScript.readyState;
604-
if (!rstate || rstate === 'loaded' || rstate === 'complete') {
605-
newScript.onload = newScript.onreadystatechange = null;
606-
if (newScript.parentNode) {
607-
newScript.parentNode.removeChild(newScript);
612+
if (isNodeSdk()) {
613+
this.doNodeLongPoll(url, loadCB);
614+
} else {
615+
var self = this;
616+
setTimeout(function() {
617+
try {
618+
// if we're already closed, don't add this poll
619+
if (!self.sendNewPolls) return;
620+
var newScript = self.myIFrame.doc.createElement('script');
621+
newScript.type = 'text/javascript';
622+
newScript.async = true;
623+
newScript.src = url;
624+
newScript.onload = newScript.onreadystatechange = function() {
625+
var rstate = newScript.readyState;
626+
if (!rstate || rstate === 'loaded' || rstate === 'complete') {
627+
newScript.onload = newScript.onreadystatechange = null;
628+
if (newScript.parentNode) {
629+
newScript.parentNode.removeChild(newScript);
630+
}
631+
loadCB();
608632
}
609-
loadCB();
610-
}
611-
};
612-
newScript.onerror = function() {
613-
log('Long-poll script failed to load: ' + url);
614-
self.sendNewPolls = false;
615-
self.close();
616-
};
617-
self.myIFrame.doc.body.appendChild(newScript);
618-
} catch (e) {
619-
// TODO: we should make this error visible somehow
620-
}
621-
}, Math.floor(1));
622-
};
633+
};
634+
newScript.onerror = function() {
635+
log('Long-poll script failed to load: ' + url);
636+
self.sendNewPolls = false;
637+
self.close();
638+
};
639+
self.myIFrame.doc.body.appendChild(newScript);
640+
} catch (e) {
641+
// TODO: we should make this error visible somehow
642+
}
643+
}, Math.floor(1));
644+
}
645+
};
646+
647+
if (isNodeSdk()) {
648+
/**
649+
* @type {?function({url: string, forever: boolean}, function(Error, number, string))}
650+
*/
651+
(FirebaseIFrameScriptHolder as any).request = null;
652+
653+
/**
654+
* @param {{url: string, forever: boolean}} req
655+
* @param {function(string)=} onComplete
656+
*/
657+
(FirebaseIFrameScriptHolder as any).nodeRestRequest = function(req, onComplete) {
658+
if (!(FirebaseIFrameScriptHolder as any).request)
659+
(FirebaseIFrameScriptHolder as any).request =
660+
/** @type {function({url: string, forever: boolean}, function(Error, number, string))} */ (require('request'));
661+
662+
(FirebaseIFrameScriptHolder as any).request(req, function(error, response, body) {
663+
if (error)
664+
throw 'Rest request for ' + req.url + ' failed.';
665+
666+
if (onComplete)
667+
onComplete(body);
668+
});
669+
};
670+
671+
/**
672+
* @param {!string} url
673+
* @param {function()} loadCB
674+
*/
675+
FirebaseIFrameScriptHolder.prototype.doNodeLongPoll = function(url, loadCB) {
676+
var self = this;
677+
(FirebaseIFrameScriptHolder as any).nodeRestRequest({ url: url, forever: true }, function(body) {
678+
self.evalBody(body);
679+
loadCB();
680+
});
681+
};
682+
683+
/**
684+
* Evaluates the string contents of a jsonp response.
685+
* @param {!string} body
686+
*/
687+
FirebaseIFrameScriptHolder.prototype.evalBody = function(body) {
688+
var jsonpCB;
689+
//jsonpCB is externed in firebase-extern.js
690+
eval('jsonpCB = function(' + FIREBASE_LONGPOLL_COMMAND_CB_NAME + ', ' + FIREBASE_LONGPOLL_DATA_CB_NAME + ') {' +
691+
body +
692+
'}');
693+
jsonpCB(this.commandCB, this.onMessageCB);
694+
};
695+
}

src/database/realtime/WebSocketConnection.ts

Lines changed: 29 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,15 @@ import { CONSTANTS } from "./Constants";
88
import { CONSTANTS as ENV_CONSTANTS } from "../../utils/constants";
99
import { PersistentStorage } from "../core/storage/storage";
1010
import { jsonEval, stringify } from "../../utils/json";
11+
import { isNodeSdk } from "../../utils/environment";
1112

1213
const WEBSOCKET_MAX_FRAME_SIZE = 16384;
1314
const WEBSOCKET_KEEPALIVE_INTERVAL = 45000;
1415

1516
let WebSocketImpl = null;
16-
if (typeof MozWebSocket !== 'undefined') {
17+
if (isNodeSdk()) {
18+
WebSocketImpl = require('faye-websocket')['Client'];
19+
} else if (typeof MozWebSocket !== 'undefined') {
1720
WebSocketImpl = MozWebSocket;
1821
} else if (typeof WebSocket !== 'undefined') {
1922
WebSocketImpl = WebSocket;
@@ -69,7 +72,8 @@ export class WebSocketConnection {
6972
var urlParams = {};
7073
urlParams[CONSTANTS.VERSION_PARAM] = CONSTANTS.PROTOCOL_VERSION;
7174

72-
if (typeof location !== 'undefined' &&
75+
if (!isNodeSdk() &&
76+
typeof location !== 'undefined' &&
7377
location.href &&
7478
location.href.indexOf(CONSTANTS.FORGE_DOMAIN) !== -1) {
7579
urlParams[CONSTANTS.REFERER_PARAM] = CONSTANTS.FORGE_REF;
@@ -99,6 +103,29 @@ export class WebSocketConnection {
99103
PersistentStorage.set('previous_websocket_failure', true);
100104

101105
try {
106+
if (isNodeSdk()) {
107+
var device = ENV_CONSTANTS.NODE_ADMIN ? 'AdminNode' : 'Node';
108+
// UA Format: Firebase/<wire_protocol>/<sdk_version>/<platform>/<device>
109+
var options = {
110+
'headers': {
111+
'User-Agent': 'Firebase/' + CONSTANTS.PROTOCOL_VERSION + '/' + firebase.SDK_VERSION + '/' + process.platform + '/' + device
112+
}};
113+
114+
// Plumb appropriate http_proxy environment variable into faye-websocket if it exists.
115+
var env = process['env'];
116+
var proxy = (this.connURL.indexOf("wss://") == 0)
117+
? (env['HTTPS_PROXY'] || env['https_proxy'])
118+
: (env['HTTP_PROXY'] || env['http_proxy']);
119+
120+
if (proxy) {
121+
options['proxy'] = { origin: proxy };
122+
}
123+
124+
this.mySock = new WebSocketImpl(this.connURL, [], options);
125+
}
126+
else {
127+
this.mySock = new WebSocketImpl(this.connURL);
128+
}
102129
this.mySock = new WebSocketImpl(this.connURL);
103130
} catch (e) {
104131
this.log_('Error instantiating WebSocket.');

src/database/login/util/environment.ts renamed to src/utils/environment.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { CONSTANTS } from "../../../utils/constants";
1+
import { CONSTANTS } from "./constants";
22

33
/**
44
* Returns navigator.userAgent string or '' if it's not defined.

0 commit comments

Comments
 (0)