UNPKG

react-native-meteor

Version:
316 lines (279 loc) 9.29 kB
import { NetInfo, Platform, View } from 'react-native'; import reactMixin from 'react-mixin'; import Trackr from 'trackr'; import EJSON from 'ejson'; import DDP from '../lib/ddp.js'; import Random from '../lib/Random'; import Data from './Data'; import { Collection } from './Collection'; import call from './Call'; import Mixin from './components/Mixin'; import MeteorListView from './components/ListView'; import MeteorComplexListView from './components/ComplexListView'; import createContainer from './components/createContainer'; import withTracker from './components/ReactMeteorData'; import composeWithTracker from './components/composeWithTracker'; import FSCollection from './CollectionFS/FSCollection'; import FSCollectionImagesPreloader from './CollectionFS/FSCollectionImagesPreloader'; import ReactiveDict from './ReactiveDict'; import User from './user/User'; import Accounts from './user/Accounts'; module.exports = { composeWithTracker, Accounts, Tracker: Trackr, EJSON, MeteorListView, MeteorComplexListView, ReactiveDict, Collection, FSCollectionImagesPreloader: Platform.OS == 'android' ? View : FSCollectionImagesPreloader, collection(name, options) { return new Collection(name, options); }, FSCollection, createContainer, withTracker, getData() { return Data; }, connectMeteor(reactClass) { return reactMixin.onClass(reactClass, Mixin); }, ...User, status() { return { connected: Data.ddp ? Data.ddp.status == 'connected' : false, status: Data.ddp ? Data.ddp.status : 'disconnected', //retryCount: 0 //retryTime: //reason: }; }, call: call, disconnect() { if (Data.ddp) { Data.ddp.disconnect(); } }, _subscriptionsRestart() { for (var i in Data.subscriptions) { const sub = Data.subscriptions[i]; Data.ddp.unsub(sub.subIdRemember); sub.subIdRemember = Data.ddp.sub(sub.name, sub.params); } }, waitDdpConnected: Data.waitDdpConnected.bind(Data), reconnect() { Data.ddp && Data.ddp.connect(); }, connect(endpoint, options) { if (!endpoint) endpoint = Data._endpoint; if (!options) options = Data._options; Data._endpoint = endpoint; Data._options = options; this.ddp = Data.ddp = new DDP({ endpoint: endpoint, SocketConstructor: WebSocket, ...options, }); NetInfo.isConnected.addEventListener('connectionChange', isConnected => { if (isConnected && Data.ddp.autoReconnect) { Data.ddp.connect(); } }); Data.ddp.on('connected', () => { // Clear the collections of any stale data in case this is a reconnect if (Data.db && Data.db.collections) { for (var collection in Data.db.collections) { Data.db[collection].remove({}); } } Data.notify('change'); console.info('Connected to DDP server.'); this._loadInitialUser().then(() => { this._subscriptionsRestart(); }); }); let lastDisconnect = null; Data.ddp.on('disconnected', () => { Data.notify('change'); console.info('Disconnected from DDP server.'); if (!Data.ddp.autoReconnect) return; if (!lastDisconnect || new Date() - lastDisconnect > 3000) { Data.ddp.connect(); } lastDisconnect = new Date(); }); Data.ddp.on('added', message => { if (!Data.db[message.collection]) { Data.db.addCollection(message.collection); } Data.db[message.collection].upsert({ _id: message.id, ...message.fields, }); }); Data.ddp.on('ready', message => { const idsMap = new Map(); for (var i in Data.subscriptions) { const sub = Data.subscriptions[i]; idsMap.set(sub.subIdRemember, sub.id); } for (var i in message.subs) { const subId = idsMap.get(message.subs[i]); if (subId) { const sub = Data.subscriptions[subId]; sub.ready = true; sub.readyDeps.changed(); sub.readyCallback && sub.readyCallback(); } } }); Data.ddp.on('changed', message => { const unset = {}; if (message.cleared) { message.cleared.forEach(field => { unset[field] = null; }); } Data.db[message.collection] && Data.db[message.collection].upsert({ _id: message.id, ...message.fields, ...unset, }); }); Data.ddp.on('removed', message => { Data.db[message.collection] && Data.db[message.collection].del(message.id); }); Data.ddp.on('result', message => { const call = Data.calls.find(call => call.id == message.id); if (typeof call.callback == 'function') call.callback(message.error, message.result); Data.calls.splice(Data.calls.findIndex(call => call.id == message.id), 1); }); Data.ddp.on('nosub', message => { for (var i in Data.subscriptions) { const sub = Data.subscriptions[i]; if (sub.subIdRemember == message.id) { console.warn('No subscription existing for', sub.name); } } }); }, subscribe(name) { var params = Array.prototype.slice.call(arguments, 1); var callbacks = {}; if (params.length) { var lastParam = params[params.length - 1]; if (typeof lastParam == 'function') { callbacks.onReady = params.pop(); } else if ( lastParam && (typeof lastParam.onReady == 'function' || typeof lastParam.onError == 'function' || typeof lastParam.onStop == 'function') ) { callbacks = params.pop(); } } // Is there an existing sub with the same name and param, run in an // invalidated Computation? This will happen if we are rerunning an // existing computation. // // For example, consider a rerun of: // // Tracker.autorun(function () { // Meteor.subscribe("foo", Session.get("foo")); // Meteor.subscribe("bar", Session.get("bar")); // }); // // If "foo" has changed but "bar" has not, we will match the "bar" // subcribe to an existing inactive subscription in order to not // unsub and resub the subscription unnecessarily. // // We only look for one such sub; if there are N apparently-identical subs // being invalidated, we will require N matching subscribe calls to keep // them all active. let existing = false; for (var i in Data.subscriptions) { const sub = Data.subscriptions[i]; if (sub.inactive && sub.name === name && EJSON.equals(sub.params, params)) existing = sub; } let id; if (existing) { id = existing.id; existing.inactive = false; if (callbacks.onReady) { // If the sub is not already ready, replace any ready callback with the // one provided now. (It's not really clear what users would expect for // an onReady callback inside an autorun; the semantics we provide is // that at the time the sub first becomes ready, we call the last // onReady callback provided, if any.) if (!existing.ready) existing.readyCallback = callbacks.onReady; } if (callbacks.onStop) { existing.stopCallback = callbacks.onStop; } } else { // New sub! Generate an id, save it locally, and send message. id = Random.id(); const subIdRemember = Data.ddp.sub(name, params); Data.subscriptions[id] = { id: id, subIdRemember: subIdRemember, name: name, params: EJSON.clone(params), inactive: false, ready: false, readyDeps: new Trackr.Dependency(), readyCallback: callbacks.onReady, stopCallback: callbacks.onStop, stop: function() { Data.ddp.unsub(this.subIdRemember); delete Data.subscriptions[this.id]; this.ready && this.readyDeps.changed(); if (callbacks.onStop) { callbacks.onStop(); } }, }; } // return a handle to the application. var handle = { stop: function() { if (Data.subscriptions[id]) Data.subscriptions[id].stop(); }, ready: function() { if (!Data.subscriptions[id]) return false; var record = Data.subscriptions[id]; record.readyDeps.depend(); return record.ready; }, subscriptionId: id, }; if (Trackr.active) { // We're in a reactive computation, so we'd like to unsubscribe when the // computation is invalidated... but not if the rerun just re-subscribes // to the same subscription! When a rerun happens, we use onInvalidate // as a change to mark the subscription "inactive" so that it can // be reused from the rerun. If it isn't reused, it's killed from // an afterFlush. Trackr.onInvalidate(function(c) { if (Data.subscriptions[id]) { Data.subscriptions[id].inactive = true; } Trackr.afterFlush(function() { if (Data.subscriptions[id] && Data.subscriptions[id].inactive) { handle.stop(); } }); }); } return handle; }, };