├── .npmignore ├── .jshintrc ├── .babelrc ├── lib ├── .babelrc ├── Random.js ├── utils.js ├── queue.js ├── socket.js └── ddp.js ├── src ├── .babelrc ├── Call.js ├── components │ ├── createContainer.js │ └── Mixin.js ├── CollectionFS │ ├── FSCollectionImagesPreloader.js │ ├── FSCollection.js │ └── setProperties.js ├── user │ ├── Accounts.js │ └── User.js ├── Data.js ├── Collection.js └── Meteor.js ├── dist ├── lib │ ├── Random.js │ ├── utils.js │ ├── queue.js │ ├── socket.js │ └── ddp.js ├── Call.js ├── src │ ├── Call.js │ ├── components │ │ ├── createContainer.js │ │ └── Mixin.js │ ├── Data.js │ ├── CollectionFS │ │ ├── FSCollection.js │ │ ├── setProperties.js │ │ └── FSCollectionImagesPreloader.js │ ├── user │ │ ├── Accounts.js │ │ └── User.js │ ├── Collection.js │ └── Meteor.js ├── components │ ├── createContainer.js │ ├── ComplexListView.js │ ├── ListView.js │ └── Mixin.js ├── Data.js ├── CollectionFS │ ├── FSCollection.js │ ├── setProperties.js │ └── FSCollectionImagesPreloader.js ├── user │ ├── Accounts.js │ └── User.js ├── Collection.js └── Meteor.js ├── .gitignore ├── docs ├── Install.md └── FSCollection.md ├── LICENSE ├── package.json └── README.md /.npmignore: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "esnext": true 3 | } 4 | -------------------------------------------------------------------------------- /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ "es2015", "stage-0", "react" ], 3 | "plugins": [ 4 | ["transform-runtime", { 5 | "polyfill": false, 6 | "regenerator": true 7 | }] 8 | ] 9 | } 10 | -------------------------------------------------------------------------------- /lib/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ "es2015", "stage-0", "react" ], 3 | "plugins": [ 4 | ["transform-runtime", { 5 | "polyfill": false, 6 | "regenerator": true 7 | }] 8 | ] 9 | } 10 | -------------------------------------------------------------------------------- /src/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ "es2015", "stage-0", "react" ], 3 | "plugins": [ 4 | ["transform-runtime", { 5 | "polyfill": false, 6 | "regenerator": true 7 | }] 8 | ] 9 | } 10 | -------------------------------------------------------------------------------- /lib/Random.js: -------------------------------------------------------------------------------- 1 | const UNMISTAKABLE_CHARS = "23456789ABCDEFGHJKLMNPQRSTWXYZabcdefghijkmnopqrstuvwxyz"; 2 | 3 | module.exports = { 4 | id(count = 17) { 5 | let res = ""; 6 | for(let i=0;i; 29 | }, 30 | }); 31 | } 32 | -------------------------------------------------------------------------------- /docs/Install.md: -------------------------------------------------------------------------------- 1 | # Android 2 | 3 | Add this to your AndroidManifest.xml file to autoreconnect fastly to DDP server if your device reconnects to network 4 | 5 | ```xml 6 | 7 | ``` 8 | 9 | 10 | 11 | # Installing decorators 12 | 13 | ## With RN >= 0.16.0 (Babel 6) 14 | 15 | - `npm i --save-dev babel-plugin-transform-decorators-legacy babel-preset-react-native` in your project 16 | - Create a .babelrc file at the root of your project : 17 | 18 | ```json 19 | { 20 | "presets": ["react-native"], 21 | "plugins": ["transform-decorators-legacy"] 22 | } 23 | ``` 24 | 25 | ## With RN <0.16.0 (Babel 5) 26 | 27 | Use a .babelrc file at the root of your project that contains : 28 | 29 | ```json 30 | { 31 | "optional": ["es7.decorators"], 32 | } 33 | ``` 34 | -------------------------------------------------------------------------------- /lib/queue.js: -------------------------------------------------------------------------------- 1 | export default class Queue { 2 | 3 | /* 4 | * As the name implies, `consumer` is the (sole) consumer of the queue. 5 | * It gets called with each element of the queue and its return value 6 | * serves as a ack, determining whether the element is removed or not from 7 | * the queue, allowing then subsequent elements to be processed. 8 | */ 9 | 10 | constructor (consumer) { 11 | this.consumer = consumer; 12 | this.queue = []; 13 | } 14 | 15 | push (element) { 16 | this.queue.push(element); 17 | this.process(); 18 | } 19 | 20 | process () { 21 | if (this.queue.length !== 0) { 22 | const ack = this.consumer(this.queue[0]); 23 | if (ack) { 24 | this.queue.shift(); 25 | this.process(); 26 | } 27 | } 28 | } 29 | 30 | empty () { 31 | this.queue = []; 32 | } 33 | 34 | } -------------------------------------------------------------------------------- /docs/FSCollection.md: -------------------------------------------------------------------------------- 1 | # Implementation for Meteor-CollectionFS 2 | 3 | ## Example usage 4 | 5 | ```javascript 6 | @connectMeteor 7 | export default class ImageFS extends Component { 8 | startMeteorSubscriptions() { 9 | Meteor.subscribe('imagesFiles'); 10 | } 11 | getMeteorData() { 12 | return { 13 | image: Meteor.FSCollection('imagesFiles').findOne() 14 | } 15 | } 16 | render() { 17 | const { image } = this.data; 18 | 19 | if(!image) return null; 20 | 21 | return ( 22 | 26 | ); 27 | } 28 | } 29 | ``` 30 | 31 | ## Available methods on FSFile 32 | 33 | All methods accept an optional parameter to choose another store. Example `file.url({store: 'thumbnail'})` 34 | 35 | * url([store]) 36 | * isImage([store]) 37 | * isVideo([store]) 38 | * isAudio([store]) 39 | * isUploaded([store]) 40 | * name([store]) 41 | * extension([store]) 42 | * size([store]) 43 | * type([store]) 44 | * updatedAt([store]) 45 | 46 | ## Something wrong or missing ? 47 | 48 | Please create an issue or make a PR ;) 49 | 50 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 inProgress 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /src/CollectionFS/FSCollectionImagesPreloader.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import React, { 4 | Component, 5 | PropTypes 6 | } from 'react'; 7 | 8 | import Data from '../Data'; 9 | import setProperties from './setProperties'; 10 | 11 | export default class FSCollectionImagesPreloader extends Component { 12 | static propTypes = { 13 | collection: PropTypes.string.isRequired, 14 | selector: PropTypes.oneOfType([ PropTypes.string, PropTypes.object ]) 15 | }; 16 | static defaultProps = { 17 | selector: {} 18 | }; 19 | constructor(props) { 20 | super(props); 21 | this.state = { 22 | items: [] 23 | }; 24 | } 25 | componentWillMount() { 26 | const { collection, selector } = this.props; 27 | 28 | 29 | this.update = results=>{ 30 | this.setState({ 31 | items: results.map(elem=>setProperties(collection, elem)) 32 | }); 33 | }; 34 | 35 | const collectionName = 'cfs.'+collection+'.filerecord'; 36 | 37 | if(!Data.db[collectionName]) { 38 | Data.db.addCollection(collectionName) 39 | } 40 | 41 | this.items = Data.db.observe(() => { 42 | return Data.db[collectionName].find(selector); 43 | }); 44 | 45 | this.items.subscribe(this.update); 46 | } 47 | componentWillUnmount() { 48 | this.items.dispose(); 49 | } 50 | render() { 51 | const { items } = this.state; 52 | 53 | return ( 54 | 55 | ); 56 | } 57 | } -------------------------------------------------------------------------------- /dist/src/components/createContainer.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | exports.default = undefined; 7 | 8 | var _extends2 = require('babel-runtime/helpers/extends'); 9 | 10 | var _extends3 = _interopRequireDefault(_extends2); 11 | 12 | var _react = require('react'); 13 | 14 | var _react2 = _interopRequireDefault(_react); 15 | 16 | var _Mixin = require('./Mixin'); 17 | 18 | var _Mixin2 = _interopRequireDefault(_Mixin); 19 | 20 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } 21 | 22 | /** 23 | * Container helper using react-meteor-data. 24 | */ 25 | 26 | function createContainer() { 27 | var options = arguments.length <= 0 || arguments[0] === undefined ? {} : arguments[0]; 28 | var Component = arguments[1]; 29 | 30 | var expandedOptions = options; 31 | if (typeof options === 'function') { 32 | expandedOptions = { 33 | getMeteorData: options 34 | }; 35 | } 36 | 37 | var _expandedOptions = expandedOptions; 38 | var _getMeteorData = _expandedOptions.getMeteorData; 39 | 40 | 41 | return _react2.default.createClass({ 42 | displayName: 'MeteorDataContainer', 43 | mixins: [_Mixin2.default], 44 | getMeteorData: function getMeteorData() { 45 | return _getMeteorData(this.props); 46 | }, 47 | render: function render() { 48 | return _react2.default.createElement(Component, (0, _extends3.default)({}, this.props, this.data)); 49 | } 50 | }); 51 | } 52 | exports.default = createContainer; -------------------------------------------------------------------------------- /src/CollectionFS/FSCollection.js: -------------------------------------------------------------------------------- 1 | import EJSON from "ejson"; 2 | 3 | import Collection from '../Collection'; 4 | import Data from '../Data'; 5 | import setProperties from './setProperties'; 6 | 7 | 8 | if(!EJSON._getTypes()['FS.File']) { 9 | EJSON.addType('FS.File', function(value) { 10 | return { 11 | getFileRecord() { 12 | const collection = Data.db['cfs.'+value.collectionName+'.filerecord']; 13 | 14 | const item = collection && collection.get(value._id); 15 | 16 | if(!item) return value; 17 | 18 | return setProperties(value.collectionName, item); 19 | } 20 | }; 21 | }); 22 | } 23 | 24 | export default function(name) { 25 | const Meteor = this; 26 | const collectionName = 'cfs.'+name+'.filerecord'; 27 | 28 | 29 | return { 30 | find(selector, options) { 31 | const elems = Collection(collectionName).find(selector, options); 32 | return elems.map(elem=>{ 33 | return setProperties(name, elem); 34 | }); 35 | }, 36 | findOne(selector, options) { 37 | const elem = Collection(collectionName).findOne(selector, options); 38 | return elem && setProperties(name, elem); 39 | }, 40 | insert: function() { Collection.apply(Meteor, [collectionName]).insert.apply(Meteor, arguments); }, 41 | update: function() { Collection.apply(Meteor, [collectionName]).update.apply(Meteor, arguments); }, 42 | remove: function() { Collection.apply(Meteor, [collectionName]).remove.apply(Meteor, arguments); }, 43 | }; 44 | } 45 | 46 | 47 | 48 | 49 | 50 | 51 | -------------------------------------------------------------------------------- /src/user/Accounts.js: -------------------------------------------------------------------------------- 1 | import Data from '../Data'; 2 | import call from '../Call'; 3 | import User from './User'; 4 | import { hashPassword } from '../../lib/utils'; 5 | 6 | 7 | module.exports = { 8 | createUser(options, callback = ()=>{}) { 9 | if (options.username) options.username = options.username; 10 | if (options.email) options.email = options.email; 11 | 12 | // Replace password with the hashed password. 13 | options.password = hashPassword(options.password); 14 | 15 | User._startLoggingIn(); 16 | call("createUser", options, (err, result)=>{ 17 | User._endLoggingIn(); 18 | 19 | User._handleLoginCallback(err, result); 20 | 21 | callback(err); 22 | }); 23 | }, 24 | changePassword(oldPassword, newPassword, callback = ()=>{}) { 25 | 26 | //TODO check Meteor.user() to prevent if not logged 27 | 28 | if(typeof newPassword != 'string' || !newPassword) { 29 | return callback("Password may not be empty"); 30 | } 31 | 32 | call("changePassword", 33 | oldPassword ? hashPassword(oldPassword) : null, 34 | hashPassword(newPassword), 35 | (err, res) => { 36 | 37 | callback(err); 38 | }); 39 | }, 40 | forgotPassword(options, callback = ()=>{}) { 41 | if (!options.email) { 42 | return callback("Must pass options.email"); 43 | } 44 | 45 | call("forgotPassword", options, err => { 46 | callback(err); 47 | }); 48 | }, 49 | onLogin(cb) { 50 | Data.on('onLogin', cb); 51 | }, 52 | onLoginFailure(cb) { 53 | Data.on('onLoginFailure', cb); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/Data.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import minimongo from 'minimongo-cache'; 3 | process.nextTick = setImmediate; 4 | 5 | const db = new minimongo(); 6 | db.debug = false; 7 | //db.batchedUpdates = React.addons.batchedUpdates; 8 | 9 | export default { 10 | _endpoint: null, 11 | _options: null, 12 | ddp: null, 13 | subscriptions: {}, 14 | db: db, 15 | calls: [], 16 | 17 | getUrl() { 18 | return this._endpoint.substring(0, this._endpoint.indexOf('/websocket')); 19 | }, 20 | 21 | waitDdpReady(cb) { 22 | if(this.ddp) { 23 | cb(); 24 | } else { 25 | setTimeout(()=>{ 26 | this.waitDdpReady(cb); 27 | }, 10); 28 | } 29 | }, 30 | 31 | _cbs: [], 32 | onChange(cb) { 33 | this.db.on('change', cb); 34 | this.ddp.on('connected', cb); 35 | this.ddp.on('disconnected', cb); 36 | this.on('loggingIn', cb); 37 | this.on('change', cb); 38 | }, 39 | offChange(cb) { 40 | this.db.off('change', cb); 41 | this.ddp.off('connected', cb); 42 | this.ddp.off('disconnected', cb); 43 | this.off('loggingIn', cb); 44 | this.off('change', cb); 45 | }, 46 | on(eventName, cb) { 47 | this._cbs.push({ 48 | eventName: eventName, 49 | callback: cb 50 | }); 51 | }, 52 | off(eventName, cb) { 53 | this._cbs.splice(this._cbs.findIndex(_cb=>_cb.callback == cb && _cb.eventName == eventName), 1); 54 | }, 55 | notify(eventName) { 56 | this._cbs.map(cb=>{ 57 | if(cb.eventName == eventName && typeof cb.callback == 'function') { 58 | cb.callback(); 59 | } 60 | }); 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "electron-meteor", 3 | "version": "0.3.0", 4 | "description": "Full Meteor Client for Electron", 5 | "main": "dist/src/Meteor.js", 6 | "scripts": { 7 | "compile": "npm run compilesrc && npm run compilelib", 8 | "watch": "npm run compilelib && ./node_modules/babel-cli/bin/babel.js -d dist/src src/ -w", 9 | "compilesrc": "./node_modules/babel-cli/bin/babel.js -d dist/src src/", 10 | "compilelib": "./node_modules/babel-cli/bin/babel.js -d dist/lib lib/", 11 | "prepublish": "npm run compile" 12 | }, 13 | "repository": { 14 | "type": "git", 15 | "url": "git+https://github.com/inProgress-team/electron-meteor.git" 16 | }, 17 | "keywords": [ 18 | "react-component", 19 | "ddp", 20 | "meteor", 21 | "react", 22 | "electron" 23 | ], 24 | "author": "Théo Mathieu", 25 | "license": "MIT", 26 | "bugs": { 27 | "url": "https://github.com/inProgress-team/electron-meteor/issues" 28 | }, 29 | "homepage": "https://github.com/inProgress-team/electron-meteor#readme", 30 | "dependencies": { 31 | "base-64": "^0.1.0", 32 | "crypto-js": "^3.1.6", 33 | "ejson": "^2.1.2", 34 | "electron-json-storage": "^2.0.0", 35 | "minimongo-cache": "0.0.48", 36 | "react": "^0.14.0 || ^15.0.1", 37 | "react-mixin": "^3.0.3", 38 | "trackr": "^2.0.2", 39 | "wolfy87-eventemitter": "^4.3.0" 40 | }, 41 | "devDependencies": { 42 | "babel-cli": "^6.8.0", 43 | "babel-loader": "^6.2.4", 44 | "babel-plugin-transform-runtime": "^6.8.0", 45 | "babel-polyfill": "^6.8.0", 46 | "babel-preset-es2015": "^6.6.0", 47 | "babel-preset-react": "^6.5.0", 48 | "babel-preset-stage-0": "^6.5.0" 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /dist/lib/queue.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | 7 | var _classCallCheck2 = require("babel-runtime/helpers/classCallCheck"); 8 | 9 | var _classCallCheck3 = _interopRequireDefault(_classCallCheck2); 10 | 11 | var _createClass2 = require("babel-runtime/helpers/createClass"); 12 | 13 | var _createClass3 = _interopRequireDefault(_createClass2); 14 | 15 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } 16 | 17 | var Queue = function () { 18 | 19 | /* 20 | * As the name implies, `consumer` is the (sole) consumer of the queue. 21 | * It gets called with each element of the queue and its return value 22 | * serves as a ack, determining whether the element is removed or not from 23 | * the queue, allowing then subsequent elements to be processed. 24 | */ 25 | 26 | function Queue(consumer) { 27 | (0, _classCallCheck3.default)(this, Queue); 28 | 29 | this.consumer = consumer; 30 | this.queue = []; 31 | } 32 | 33 | (0, _createClass3.default)(Queue, [{ 34 | key: "push", 35 | value: function push(element) { 36 | this.queue.push(element); 37 | this.process(); 38 | } 39 | }, { 40 | key: "process", 41 | value: function process() { 42 | if (this.queue.length !== 0) { 43 | var ack = this.consumer(this.queue[0]); 44 | if (ack) { 45 | this.queue.shift(); 46 | this.process(); 47 | } 48 | } 49 | } 50 | }, { 51 | key: "empty", 52 | value: function empty() { 53 | this.queue = []; 54 | } 55 | }]); 56 | return Queue; 57 | }(); 58 | 59 | exports.default = Queue; -------------------------------------------------------------------------------- /src/CollectionFS/setProperties.js: -------------------------------------------------------------------------------- 1 | 2 | import base64 from 'base-64'; 3 | import Data from '../Data'; 4 | 5 | export default (name, file)=> { 6 | const getStoreName = (params = {store: name}) => { 7 | return params.store; 8 | }; 9 | const getImageInfos = params => { 10 | if(!params || !params.store) return file.original || {}; 11 | return file.copies[params.store] || {}; 12 | }; 13 | const getType = params => { 14 | return getImageInfos(params).type; 15 | }; 16 | return { 17 | ...file, 18 | url: params => { 19 | const token = Data._tokenIdSaved; 20 | const fileName = getImageInfos(params).name; 21 | return Data.getUrl().replace('ws://', 'http://').replace('wss://', 'https://')+'/cfs/files/'+name+'/'+file._id+'/'+fileName+'?store='+getStoreName(params)+(token ? '&token='+base64.encode(JSON.stringify({authToken: token})) : ""); 22 | }, 23 | isImage: params => { 24 | const type = getType(params); 25 | return type && type.indexOf('image/')===0; 26 | }, 27 | isAudio: params => { 28 | const type = getType(params); 29 | return type && type.indexOf('audio/')===0; 30 | }, 31 | isVideo: params => { 32 | const type = getType(params); 33 | return type && type.indexOf('video/')===0; 34 | }, 35 | isUploaded: params => { 36 | return !!(getImageInfos(params).updatedAt); 37 | }, 38 | name: params => { 39 | return getImageInfos(params).name; 40 | }, 41 | extension: params => { 42 | const imageName = getImageInfos(params).name; 43 | if(!imageName) return; 44 | return imageName.substring(imageName.lastIndexOf('.')+1); 45 | }, 46 | size: params => { 47 | return getImageInfos(params).size; 48 | }, 49 | type: getType, 50 | updatedAt: params=>{ 51 | return getImageInfos(params).updatedAt; 52 | } 53 | } 54 | } -------------------------------------------------------------------------------- /dist/components/createContainer.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | exports.default = undefined; 7 | 8 | var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; /** 9 | * Container helper using react-meteor-data. 10 | */ 11 | 12 | var _react = require('react'); 13 | 14 | var _react2 = _interopRequireDefault(_react); 15 | 16 | var _Mixin = require('./Mixin'); 17 | 18 | var _Mixin2 = _interopRequireDefault(_Mixin); 19 | 20 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } 21 | 22 | function createContainer() { 23 | var options = arguments.length <= 0 || arguments[0] === undefined ? {} : arguments[0]; 24 | var Component = arguments[1]; 25 | 26 | var expandedOptions = options; 27 | if (typeof options === 'function') { 28 | expandedOptions = { 29 | getMeteorData: options 30 | }; 31 | } 32 | 33 | var _expandedOptions = expandedOptions; 34 | var _getMeteorData = _expandedOptions.getMeteorData; 35 | 36 | 37 | return _react2.default.createClass({ 38 | displayName: 'MeteorDataContainer', 39 | mixins: [_Mixin2.default], 40 | getMeteorData: function getMeteorData() { 41 | return _getMeteorData(this.props); 42 | }, 43 | render: function render() { 44 | return _react2.default.createElement(Component, _extends({}, this.props, this.data)); 45 | } 46 | }); 47 | } 48 | exports.default = createContainer; -------------------------------------------------------------------------------- /dist/src/Data.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | 7 | var _react = require('react'); 8 | 9 | var _react2 = _interopRequireDefault(_react); 10 | 11 | var _minimongoCache = require('minimongo-cache'); 12 | 13 | var _minimongoCache2 = _interopRequireDefault(_minimongoCache); 14 | 15 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } 16 | 17 | process.nextTick = setImmediate; 18 | 19 | var db = new _minimongoCache2.default(); 20 | db.debug = false; 21 | //db.batchedUpdates = React.addons.batchedUpdates; 22 | 23 | exports.default = { 24 | _endpoint: null, 25 | _options: null, 26 | ddp: null, 27 | subscriptions: {}, 28 | db: db, 29 | calls: [], 30 | 31 | getUrl: function getUrl() { 32 | return this._endpoint.substring(0, this._endpoint.indexOf('/websocket')); 33 | }, 34 | waitDdpReady: function waitDdpReady(cb) { 35 | var _this = this; 36 | 37 | if (this.ddp) { 38 | cb(); 39 | } else { 40 | setTimeout(function () { 41 | _this.waitDdpReady(cb); 42 | }, 10); 43 | } 44 | }, 45 | 46 | 47 | _cbs: [], 48 | onChange: function onChange(cb) { 49 | this.db.on('change', cb); 50 | this.ddp.on('connected', cb); 51 | this.ddp.on('disconnected', cb); 52 | this.on('loggingIn', cb); 53 | this.on('change', cb); 54 | }, 55 | offChange: function offChange(cb) { 56 | this.db.off('change', cb); 57 | this.ddp.off('connected', cb); 58 | this.ddp.off('disconnected', cb); 59 | this.off('loggingIn', cb); 60 | this.off('change', cb); 61 | }, 62 | on: function on(eventName, cb) { 63 | this._cbs.push({ 64 | eventName: eventName, 65 | callback: cb 66 | }); 67 | }, 68 | off: function off(eventName, cb) { 69 | this._cbs.splice(this._cbs.findIndex(function (_cb) { 70 | return _cb.callback == cb && _cb.eventName == eventName; 71 | }), 1); 72 | }, 73 | notify: function notify(eventName) { 74 | this._cbs.map(function (cb) { 75 | if (cb.eventName == eventName && typeof cb.callback == 'function') { 76 | cb.callback(); 77 | } 78 | }); 79 | } 80 | }; -------------------------------------------------------------------------------- /dist/Data.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | 7 | var _reactNative = require('react-native'); 8 | 9 | var _reactNative2 = _interopRequireDefault(_reactNative); 10 | 11 | var _minimongoCache = require('minimongo-cache'); 12 | 13 | var _minimongoCache2 = _interopRequireDefault(_minimongoCache); 14 | 15 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } 16 | 17 | process.nextTick = setImmediate; 18 | 19 | var db = new _minimongoCache2.default(); 20 | db.debug = false; 21 | db.batchedUpdates = _reactNative2.default.addons.batchedUpdates; 22 | 23 | exports.default = { 24 | _endpoint: null, 25 | _options: null, 26 | ddp: null, 27 | subscriptions: {}, 28 | db: db, 29 | calls: [], 30 | 31 | getUrl: function getUrl() { 32 | return this._endpoint.substring(0, this._endpoint.indexOf('/websocket')); 33 | }, 34 | waitDdpReady: function waitDdpReady(cb) { 35 | var _this = this; 36 | 37 | if (this.ddp) { 38 | cb(); 39 | } else { 40 | setTimeout(function () { 41 | _this.waitDdpReady(cb); 42 | }, 10); 43 | } 44 | }, 45 | 46 | 47 | _cbs: [], 48 | onChange: function onChange(cb) { 49 | this.db.on('change', cb); 50 | this.ddp.on('connected', cb); 51 | this.ddp.on('disconnected', cb); 52 | this.on('loggingIn', cb); 53 | this.on('change', cb); 54 | }, 55 | offChange: function offChange(cb) { 56 | this.db.off('change', cb); 57 | this.ddp.off('connected', cb); 58 | this.ddp.off('disconnected', cb); 59 | this.off('loggingIn', cb); 60 | this.off('change', cb); 61 | }, 62 | on: function on(eventName, cb) { 63 | this._cbs.push({ 64 | eventName: eventName, 65 | callback: cb 66 | }); 67 | }, 68 | off: function off(eventName, cb) { 69 | this._cbs.splice(this._cbs.findIndex(function (_cb) { 70 | return _cb.callback == cb && _cb.eventName == eventName; 71 | }), 1); 72 | }, 73 | notify: function notify(eventName) { 74 | this._cbs.map(function (cb) { 75 | if (cb.eventName == eventName && typeof cb.callback == 'function') { 76 | cb.callback(); 77 | } 78 | }); 79 | } 80 | }; -------------------------------------------------------------------------------- /dist/CollectionFS/FSCollection.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | 7 | exports.default = function (name) { 8 | var Meteor = this; 9 | var collectionName = 'cfs.' + name + '.filerecord'; 10 | 11 | return { 12 | find: function find(selector, options) { 13 | var elems = (0, _Collection2.default)(collectionName).find(selector, options); 14 | return elems.map(function (elem) { 15 | return (0, _setProperties2.default)(name, elem); 16 | }); 17 | }, 18 | findOne: function findOne(selector, options) { 19 | var elem = (0, _Collection2.default)(collectionName).findOne(selector, options); 20 | return elem && (0, _setProperties2.default)(name, elem); 21 | }, 22 | 23 | insert: function insert() { 24 | _Collection2.default.apply(Meteor, [collectionName]).insert.apply(Meteor, arguments); 25 | }, 26 | update: function update() { 27 | _Collection2.default.apply(Meteor, [collectionName]).update.apply(Meteor, arguments); 28 | }, 29 | remove: function remove() { 30 | _Collection2.default.apply(Meteor, [collectionName]).remove.apply(Meteor, arguments); 31 | } 32 | }; 33 | }; 34 | 35 | var _ejson = require('ejson'); 36 | 37 | var _ejson2 = _interopRequireDefault(_ejson); 38 | 39 | var _Collection = require('../Collection'); 40 | 41 | var _Collection2 = _interopRequireDefault(_Collection); 42 | 43 | var _Data = require('../Data'); 44 | 45 | var _Data2 = _interopRequireDefault(_Data); 46 | 47 | var _setProperties = require('./setProperties'); 48 | 49 | var _setProperties2 = _interopRequireDefault(_setProperties); 50 | 51 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } 52 | 53 | if (!_ejson2.default._getTypes()['FS.File']) { 54 | _ejson2.default.addType('FS.File', function (value) { 55 | return { 56 | getFileRecord: function getFileRecord() { 57 | var collection = _Data2.default.db['cfs.' + value.collectionName + '.filerecord']; 58 | 59 | var item = collection && collection.get(value._id); 60 | 61 | if (!item) return value; 62 | 63 | return (0, _setProperties2.default)(value.collectionName, item); 64 | } 65 | }; 66 | }); 67 | } -------------------------------------------------------------------------------- /dist/src/CollectionFS/FSCollection.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | 7 | exports.default = function (name) { 8 | var Meteor = this; 9 | var collectionName = 'cfs.' + name + '.filerecord'; 10 | 11 | return { 12 | find: function find(selector, options) { 13 | var elems = (0, _Collection2.default)(collectionName).find(selector, options); 14 | return elems.map(function (elem) { 15 | return (0, _setProperties2.default)(name, elem); 16 | }); 17 | }, 18 | findOne: function findOne(selector, options) { 19 | var elem = (0, _Collection2.default)(collectionName).findOne(selector, options); 20 | return elem && (0, _setProperties2.default)(name, elem); 21 | }, 22 | 23 | insert: function insert() { 24 | _Collection2.default.apply(Meteor, [collectionName]).insert.apply(Meteor, arguments); 25 | }, 26 | update: function update() { 27 | _Collection2.default.apply(Meteor, [collectionName]).update.apply(Meteor, arguments); 28 | }, 29 | remove: function remove() { 30 | _Collection2.default.apply(Meteor, [collectionName]).remove.apply(Meteor, arguments); 31 | } 32 | }; 33 | }; 34 | 35 | var _ejson = require('ejson'); 36 | 37 | var _ejson2 = _interopRequireDefault(_ejson); 38 | 39 | var _Collection = require('../Collection'); 40 | 41 | var _Collection2 = _interopRequireDefault(_Collection); 42 | 43 | var _Data = require('../Data'); 44 | 45 | var _Data2 = _interopRequireDefault(_Data); 46 | 47 | var _setProperties = require('./setProperties'); 48 | 49 | var _setProperties2 = _interopRequireDefault(_setProperties); 50 | 51 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } 52 | 53 | if (!_ejson2.default._getTypes()['FS.File']) { 54 | _ejson2.default.addType('FS.File', function (value) { 55 | return { 56 | getFileRecord: function getFileRecord() { 57 | var collection = _Data2.default.db['cfs.' + value.collectionName + '.filerecord']; 58 | 59 | var item = collection && collection.get(value._id); 60 | 61 | if (!item) return value; 62 | 63 | return (0, _setProperties2.default)(value.collectionName, item); 64 | } 65 | }; 66 | }); 67 | } -------------------------------------------------------------------------------- /dist/user/Accounts.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var _Data = require('../Data'); 4 | 5 | var _Data2 = _interopRequireDefault(_Data); 6 | 7 | var _Call = require('../Call'); 8 | 9 | var _Call2 = _interopRequireDefault(_Call); 10 | 11 | var _User = require('./User'); 12 | 13 | var _User2 = _interopRequireDefault(_User); 14 | 15 | var _utils = require('../../lib/utils'); 16 | 17 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } 18 | 19 | module.exports = { 20 | createUser: function createUser(options) { 21 | var callback = arguments.length <= 1 || arguments[1] === undefined ? function () {} : arguments[1]; 22 | 23 | if (options.username) options.username = options.username; 24 | if (options.email) options.email = options.email; 25 | 26 | // Replace password with the hashed password. 27 | options.password = (0, _utils.hashPassword)(options.password); 28 | 29 | _User2.default._startLoggingIn(); 30 | (0, _Call2.default)("createUser", options, function (err, result) { 31 | _User2.default._endLoggingIn(); 32 | 33 | _User2.default._handleLoginCallback(err, result); 34 | 35 | callback(err); 36 | }); 37 | }, 38 | changePassword: function changePassword(oldPassword, newPassword) { 39 | var callback = arguments.length <= 2 || arguments[2] === undefined ? function () {} : arguments[2]; 40 | 41 | 42 | //TODO check Meteor.user() to prevent if not logged 43 | 44 | if (typeof newPassword != 'string' || !newPassword) { 45 | return callback("Password may not be empty"); 46 | } 47 | 48 | (0, _Call2.default)("changePassword", oldPassword ? (0, _utils.hashPassword)(oldPassword) : null, (0, _utils.hashPassword)(newPassword), function (err, res) { 49 | 50 | callback(err); 51 | }); 52 | }, 53 | forgotPassword: function forgotPassword(options) { 54 | var callback = arguments.length <= 1 || arguments[1] === undefined ? function () {} : arguments[1]; 55 | 56 | if (!options.email) { 57 | return callback("Must pass options.email"); 58 | } 59 | 60 | (0, _Call2.default)("forgotPassword", options, function (err) { 61 | callback(err); 62 | }); 63 | }, 64 | onLogin: function onLogin(cb) { 65 | _Data2.default.on('onLogin', cb); 66 | }, 67 | onLoginFailure: function onLoginFailure(cb) { 68 | _Data2.default.on('onLoginFailure', cb); 69 | } 70 | }; -------------------------------------------------------------------------------- /dist/src/user/Accounts.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var _Data = require('../Data'); 4 | 5 | var _Data2 = _interopRequireDefault(_Data); 6 | 7 | var _Call = require('../Call'); 8 | 9 | var _Call2 = _interopRequireDefault(_Call); 10 | 11 | var _User = require('./User'); 12 | 13 | var _User2 = _interopRequireDefault(_User); 14 | 15 | var _utils = require('../../lib/utils'); 16 | 17 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } 18 | 19 | module.exports = { 20 | createUser: function createUser(options) { 21 | var callback = arguments.length <= 1 || arguments[1] === undefined ? function () {} : arguments[1]; 22 | 23 | if (options.username) options.username = options.username; 24 | if (options.email) options.email = options.email; 25 | 26 | // Replace password with the hashed password. 27 | options.password = (0, _utils.hashPassword)(options.password); 28 | 29 | _User2.default._startLoggingIn(); 30 | (0, _Call2.default)("createUser", options, function (err, result) { 31 | _User2.default._endLoggingIn(); 32 | 33 | _User2.default._handleLoginCallback(err, result); 34 | 35 | callback(err); 36 | }); 37 | }, 38 | changePassword: function changePassword(oldPassword, newPassword) { 39 | var callback = arguments.length <= 2 || arguments[2] === undefined ? function () {} : arguments[2]; 40 | 41 | 42 | //TODO check Meteor.user() to prevent if not logged 43 | 44 | if (typeof newPassword != 'string' || !newPassword) { 45 | return callback("Password may not be empty"); 46 | } 47 | 48 | (0, _Call2.default)("changePassword", oldPassword ? (0, _utils.hashPassword)(oldPassword) : null, (0, _utils.hashPassword)(newPassword), function (err, res) { 49 | 50 | callback(err); 51 | }); 52 | }, 53 | forgotPassword: function forgotPassword(options) { 54 | var callback = arguments.length <= 1 || arguments[1] === undefined ? function () {} : arguments[1]; 55 | 56 | if (!options.email) { 57 | return callback("Must pass options.email"); 58 | } 59 | 60 | (0, _Call2.default)("forgotPassword", options, function (err) { 61 | callback(err); 62 | }); 63 | }, 64 | onLogin: function onLogin(cb) { 65 | _Data2.default.on('onLogin', cb); 66 | }, 67 | onLoginFailure: function onLoginFailure(cb) { 68 | _Data2.default.on('onLoginFailure', cb); 69 | } 70 | }; -------------------------------------------------------------------------------- /dist/src/CollectionFS/setProperties.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | 7 | var _extends2 = require('babel-runtime/helpers/extends'); 8 | 9 | var _extends3 = _interopRequireDefault(_extends2); 10 | 11 | var _base = require('base-64'); 12 | 13 | var _base2 = _interopRequireDefault(_base); 14 | 15 | var _Data = require('../Data'); 16 | 17 | var _Data2 = _interopRequireDefault(_Data); 18 | 19 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } 20 | 21 | exports.default = function (name, file) { 22 | var getStoreName = function getStoreName() { 23 | var params = arguments.length <= 0 || arguments[0] === undefined ? { store: name } : arguments[0]; 24 | 25 | return params.store; 26 | }; 27 | var getImageInfos = function getImageInfos(params) { 28 | if (!params || !params.store) return file.original || {}; 29 | return file.copies[params.store] || {}; 30 | }; 31 | var getType = function getType(params) { 32 | return getImageInfos(params).type; 33 | }; 34 | return (0, _extends3.default)({}, file, { 35 | url: function url(params) { 36 | var token = _Data2.default._tokenIdSaved; 37 | var fileName = getImageInfos(params).name; 38 | return _Data2.default.getUrl().replace('ws://', 'http://').replace('wss://', 'https://') + '/cfs/files/' + name + '/' + file._id + '/' + fileName + '?store=' + getStoreName(params) + (token ? '&token=' + _base2.default.encode(JSON.stringify({ authToken: token })) : ""); 39 | }, 40 | isImage: function isImage(params) { 41 | var type = getType(params); 42 | return type && type.indexOf('image/') === 0; 43 | }, 44 | isAudio: function isAudio(params) { 45 | var type = getType(params); 46 | return type && type.indexOf('audio/') === 0; 47 | }, 48 | isVideo: function isVideo(params) { 49 | var type = getType(params); 50 | return type && type.indexOf('video/') === 0; 51 | }, 52 | isUploaded: function isUploaded(params) { 53 | return !!getImageInfos(params).updatedAt; 54 | }, 55 | name: function name(params) { 56 | return getImageInfos(params).name; 57 | }, 58 | extension: function extension(params) { 59 | var imageName = getImageInfos(params).name; 60 | if (!imageName) return; 61 | return imageName.substring(imageName.lastIndexOf('.') + 1); 62 | }, 63 | size: function size(params) { 64 | return getImageInfos(params).size; 65 | }, 66 | type: getType, 67 | updatedAt: function updatedAt(params) { 68 | return getImageInfos(params).updatedAt; 69 | } 70 | }); 71 | }; -------------------------------------------------------------------------------- /dist/CollectionFS/setProperties.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | 7 | var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; 8 | 9 | var _base = require('base-64'); 10 | 11 | var _base2 = _interopRequireDefault(_base); 12 | 13 | var _Data = require('../Data'); 14 | 15 | var _Data2 = _interopRequireDefault(_Data); 16 | 17 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } 18 | 19 | exports.default = function (name, file) { 20 | var getStoreName = function getStoreName() { 21 | var params = arguments.length <= 0 || arguments[0] === undefined ? { store: name } : arguments[0]; 22 | 23 | return params.store; 24 | }; 25 | var getImageInfos = function getImageInfos(params) { 26 | if (!params || !params.store) return file.original || {}; 27 | return file.copies[params.store] || {}; 28 | }; 29 | var getType = function getType(params) { 30 | return getImageInfos(params).type; 31 | }; 32 | return _extends({}, file, { 33 | url: function url(params) { 34 | var token = _Data2.default._tokenIdSaved; 35 | var fileName = getImageInfos(params).name; 36 | return _Data2.default.getUrl().replace('ws://', 'http://').replace('wss://', 'https://') + '/cfs/files/' + name + '/' + file._id + '/' + fileName + '?store=' + getStoreName(params) + (token ? '&token=' + _base2.default.encode(JSON.stringify({ authToken: token })) : ""); 37 | }, 38 | isImage: function isImage(params) { 39 | var type = getType(params); 40 | return type && type.indexOf('image/') === 0; 41 | }, 42 | isAudio: function isAudio(params) { 43 | var type = getType(params); 44 | return type && type.indexOf('audio/') === 0; 45 | }, 46 | isVideo: function isVideo(params) { 47 | var type = getType(params); 48 | return type && type.indexOf('video/') === 0; 49 | }, 50 | isUploaded: function isUploaded(params) { 51 | return !!getImageInfos(params).updatedAt; 52 | }, 53 | name: function name(params) { 54 | return getImageInfos(params).name; 55 | }, 56 | extension: function extension(params) { 57 | var imageName = getImageInfos(params).name; 58 | if (!imageName) return; 59 | return imageName.substring(imageName.lastIndexOf('.') + 1); 60 | }, 61 | size: function size(params) { 62 | return getImageInfos(params).size; 63 | }, 64 | type: getType, 65 | updatedAt: function updatedAt(params) { 66 | return getImageInfos(params).updatedAt; 67 | } 68 | }); 69 | }; -------------------------------------------------------------------------------- /src/Collection.js: -------------------------------------------------------------------------------- 1 | import Data from './Data'; 2 | import Random from '../lib/Random'; 3 | 4 | export default function(name) { 5 | const Meteor = this; 6 | if(!Data.db[name]) { Data.db.addCollection(name) } 7 | 8 | return { 9 | find(selector, options) { 10 | if(typeof selector == 'string') { 11 | if(options) { 12 | return [Data.db[name].findOne({_id: selector}, options)]; 13 | } else { 14 | return [Data.db[name].get(selector)]; 15 | } 16 | } 17 | return Data.db[name].find(selector, options); 18 | 19 | }, 20 | findOne(selector, options) { 21 | 22 | if(typeof selector == 'string') { 23 | if(options) { 24 | return Data.db[name].findOne({_id: selector}, options); 25 | } else { 26 | return Data.db[name].get(selector); 27 | } 28 | } 29 | return Data.db[name] && Data.db[name].findOne(selector, options) 30 | 31 | }, 32 | insert(item, callback = ()=>{}) { 33 | 34 | let id; 35 | 36 | if('_id' in item) { 37 | if(!item._id || typeof item._id != 'string') { 38 | return callback("Meteor requires document _id fields to be non-empty strings"); 39 | } 40 | id = item._id; 41 | } else { 42 | id = item._id = Random.id(); 43 | } 44 | 45 | if(Data.db[name].get(id)) return callback({error: 409, reason: "Duplicate key _id with value "+id}); 46 | 47 | 48 | Data.db[name].upsert(item); 49 | 50 | Meteor.waitDdpConnected(()=>{ 51 | Meteor.call('/'+name+'/insert', item, err => { 52 | if(err) { 53 | Data.db[name].del(id); 54 | return callback(err); 55 | } 56 | 57 | callback(null, id); 58 | }); 59 | }); 60 | 61 | 62 | return id; 63 | }, 64 | update(id, modifier, options={}, callback=()=>{}) { 65 | 66 | if(typeof options == 'function') { 67 | callback = options; 68 | options = {}; 69 | } 70 | 71 | if(!Data.db[name].get(id)) return callback({error: 409, reason: "Item not found in collection "+name+" with id "+id}); 72 | 73 | Meteor.waitDdpConnected(()=>{ 74 | Meteor.call('/'+name+'/update', {_id: id}, modifier, err => { 75 | if(err) { 76 | return callback(err); 77 | } 78 | 79 | callback(null, id); 80 | }); 81 | }); 82 | }, 83 | remove(id, callback = ()=>{}) { 84 | 85 | const element = this.findOne(id); 86 | if(element) { 87 | Data.db[name].del(element._id); 88 | 89 | Meteor.waitDdpConnected(()=>{ 90 | Meteor.call('/'+name+'/remove', {_id: id}, (err, res) => { 91 | if(err) { 92 | Data.db[name].upsert(element); 93 | return callback(err); 94 | } 95 | callback(null, res); 96 | 97 | }); 98 | }); 99 | 100 | } else { 101 | callback('No document with _id : ' + id); 102 | } 103 | } 104 | }; 105 | } -------------------------------------------------------------------------------- /src/user/User.js: -------------------------------------------------------------------------------- 1 | //import { AsyncStorage } from 'react-native'; 2 | import storage from 'electron-json-storage'; 3 | 4 | import Data from '../Data'; 5 | import { hashPassword } from '../../lib/utils'; 6 | 7 | const TOKEN_KEY = 'reactnativemeteor_usertoken'; 8 | 9 | module.exports = { 10 | user() { 11 | if(!this._userIdSaved) return null; 12 | 13 | return this.collection('users').findOne(this._userIdSaved); 14 | }, 15 | userId() { 16 | if(!this._userIdSaved) return null; 17 | 18 | const user = this.collection('users').findOne(this._userIdSaved); 19 | return user && user._id; 20 | }, 21 | _isLoggingIn: true, 22 | loggingIn() { 23 | return this._isLoggingIn; 24 | }, 25 | logout(callback) { 26 | this.call("logout", err => { 27 | this.handleLogout(); 28 | this.connect(); 29 | 30 | typeof callback == 'function' && callback(err); 31 | }); 32 | }, 33 | handleLogout() { 34 | storage.remove(TOKEN_KEY); 35 | Data._tokenIdSaved = null; 36 | this._userIdSaved = null; 37 | }, 38 | loginWithPassword(selector, password, callback) { 39 | if (typeof selector === 'string') { 40 | if (selector.indexOf('@') === -1) 41 | selector = {username: selector}; 42 | else 43 | selector = {email: selector}; 44 | } 45 | 46 | this._startLoggingIn(); 47 | this.call("login", { 48 | user: selector, 49 | password: hashPassword(password) 50 | }, (err, result)=>{ 51 | this._endLoggingIn(); 52 | 53 | this._handleLoginCallback(err, result); 54 | 55 | typeof callback == 'function' && callback(err); 56 | }); 57 | }, 58 | logoutOtherClients(callback = ()=>{}) { 59 | this.call('getNewToken', (err, res) => { 60 | if(err) return callback(err); 61 | 62 | this._handleLoginCallback(err, res); 63 | 64 | this.call('removeOtherTokens', err=>{ 65 | callback(err); 66 | }) 67 | }); 68 | }, 69 | _startLoggingIn() { 70 | this._isLoggingIn = true; 71 | Data.notify('loggingIn'); 72 | }, 73 | _endLoggingIn() { 74 | this._isLoggingIn = false; 75 | Data.notify('loggingIn'); 76 | }, 77 | _handleLoginCallback(err, result) { 78 | if(!err) {//save user id and token 79 | storage.set(TOKEN_KEY, {token: result.token}); 80 | Data._tokenIdSaved = result.token; 81 | this._userIdSaved = result.id; 82 | Data.notify('onLogin'); 83 | } else { 84 | Data.notify('onLoginFailure'); 85 | this.handleLogout(); 86 | } 87 | Data.notify('change'); 88 | }, 89 | async _loadInitialUser() { 90 | var value = null; 91 | 92 | try { 93 | const getStorage = function() { 94 | return new Promise(function(resolve, reject) { 95 | storage.get(TOKEN_KEY, function(err, res) { 96 | resolve(res) 97 | }); 98 | }) 99 | } 100 | value = await getStorage(); 101 | value = value.token; 102 | } catch (error) { 103 | console.warn('json-storage error: ' + error.message); 104 | } finally { 105 | Data._tokenIdSaved = value; 106 | if (value !== null){ 107 | this._startLoggingIn(); 108 | this.call('login', { resume: value }, (err, result) => { 109 | this._endLoggingIn(); 110 | this._handleLoginCallback(err, result); 111 | }); 112 | } else { 113 | this._endLoggingIn(); 114 | } 115 | } 116 | 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /dist/src/CollectionFS/FSCollectionImagesPreloader.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | 7 | var _classCallCheck2 = require('babel-runtime/helpers/classCallCheck'); 8 | 9 | var _classCallCheck3 = _interopRequireDefault(_classCallCheck2); 10 | 11 | var _createClass2 = require('babel-runtime/helpers/createClass'); 12 | 13 | var _createClass3 = _interopRequireDefault(_createClass2); 14 | 15 | var _possibleConstructorReturn2 = require('babel-runtime/helpers/possibleConstructorReturn'); 16 | 17 | var _possibleConstructorReturn3 = _interopRequireDefault(_possibleConstructorReturn2); 18 | 19 | var _inherits2 = require('babel-runtime/helpers/inherits'); 20 | 21 | var _inherits3 = _interopRequireDefault(_inherits2); 22 | 23 | var _react = require('react'); 24 | 25 | var _react2 = _interopRequireDefault(_react); 26 | 27 | var _Data = require('../Data'); 28 | 29 | var _Data2 = _interopRequireDefault(_Data); 30 | 31 | var _setProperties = require('./setProperties'); 32 | 33 | var _setProperties2 = _interopRequireDefault(_setProperties); 34 | 35 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } 36 | 37 | var FSCollectionImagesPreloader = function (_Component) { 38 | (0, _inherits3.default)(FSCollectionImagesPreloader, _Component); 39 | 40 | function FSCollectionImagesPreloader(props) { 41 | (0, _classCallCheck3.default)(this, FSCollectionImagesPreloader); 42 | 43 | var _this = (0, _possibleConstructorReturn3.default)(this, Object.getPrototypeOf(FSCollectionImagesPreloader).call(this, props)); 44 | 45 | _this.state = { 46 | items: [] 47 | }; 48 | return _this; 49 | } 50 | 51 | (0, _createClass3.default)(FSCollectionImagesPreloader, [{ 52 | key: 'componentWillMount', 53 | value: function componentWillMount() { 54 | var _this2 = this; 55 | 56 | var _props = this.props; 57 | var collection = _props.collection; 58 | var selector = _props.selector; 59 | 60 | 61 | this.update = function (results) { 62 | _this2.setState({ 63 | items: results.map(function (elem) { 64 | return (0, _setProperties2.default)(collection, elem); 65 | }) 66 | }); 67 | }; 68 | 69 | var collectionName = 'cfs.' + collection + '.filerecord'; 70 | 71 | if (!_Data2.default.db[collectionName]) { 72 | _Data2.default.db.addCollection(collectionName); 73 | } 74 | 75 | this.items = _Data2.default.db.observe(function () { 76 | return _Data2.default.db[collectionName].find(selector); 77 | }); 78 | 79 | this.items.subscribe(this.update); 80 | } 81 | }, { 82 | key: 'componentWillUnmount', 83 | value: function componentWillUnmount() { 84 | this.items.dispose(); 85 | } 86 | }, { 87 | key: 'render', 88 | value: function render() { 89 | var items = this.state.items; 90 | 91 | 92 | return _react2.default.createElement('img', { src: item.url(), style: { width: 1, height: 1, position: 'absolute', top: '-5000px', left: '-5000px' } }); 93 | } 94 | }]); 95 | return FSCollectionImagesPreloader; 96 | }(_react.Component); 97 | 98 | FSCollectionImagesPreloader.propTypes = { 99 | collection: _react.PropTypes.string.isRequired, 100 | selector: _react.PropTypes.oneOfType([_react.PropTypes.string, _react.PropTypes.object]) 101 | }; 102 | FSCollectionImagesPreloader.defaultProps = { 103 | selector: {} 104 | }; 105 | exports.default = FSCollectionImagesPreloader; -------------------------------------------------------------------------------- /lib/socket.js: -------------------------------------------------------------------------------- 1 | import EventEmitter from "wolfy87-eventemitter"; 2 | import EJSON from "ejson"; 3 | 4 | export default class Socket extends EventEmitter { 5 | 6 | constructor (SocketConstructor, endpoint) { 7 | super(); 8 | this.SocketConstructor = SocketConstructor; 9 | this.endpoint = endpoint; 10 | this.rawSocket = null; 11 | } 12 | 13 | send (object) { 14 | const message = EJSON.stringify(object); 15 | this.rawSocket.send(message); 16 | // Emit a copy of the object, as the listener might mutate it. 17 | this.emit("message:out", EJSON.parse(message)); 18 | } 19 | 20 | open () { 21 | 22 | /* 23 | * Makes `open` a no-op if there's already a `rawSocket`. This avoids 24 | * memory / socket leaks if `open` is called twice (e.g. by a user 25 | * calling `ddp.connect` twice) without properly disposing of the 26 | * socket connection. `rawSocket` gets automatically set to `null` only 27 | * when it goes into a closed or error state. This way `rawSocket` is 28 | * disposed of correctly: the socket connection is closed, and the 29 | * object can be garbage collected. 30 | */ 31 | if (this.rawSocket) { 32 | return; 33 | } 34 | this.rawSocket = new this.SocketConstructor(this.endpoint); 35 | 36 | /* 37 | * Calls to `onopen` and `onclose` directly trigger the `open` and 38 | * `close` events on the `Socket` instance. 39 | */ 40 | this.rawSocket.onopen = () => this.emit("open"); 41 | this.rawSocket.onclose = () => { 42 | this.rawSocket = null; 43 | this.emit("close"); 44 | }; 45 | /* 46 | * Calls to `onerror` trigger the `close` event on the `Socket` 47 | * instance, and cause the `rawSocket` object to be disposed of. 48 | * Since it's not clear what conditions could cause the error and if 49 | * it's possible to recover from it, we prefer to always close the 50 | * connection (if it isn't already) and dispose of the socket object. 51 | */ 52 | this.rawSocket.onerror = () => { 53 | // It's not clear what the socket lifecycle is when errors occurr. 54 | // Hence, to avoid the `close` event to be emitted twice, before 55 | // manually closing the socket we de-register the `onclose` 56 | // callback. 57 | delete this.rawSocket.onclose; 58 | // Safe to perform even if the socket is already closed 59 | this.rawSocket.close(); 60 | this.rawSocket = null; 61 | this.emit("close"); 62 | }; 63 | /* 64 | * Calls to `onmessage` trigger a `message:in` event on the `Socket` 65 | * instance only once the message (first parameter to `onmessage`) has 66 | * been successfully parsed into a javascript object. 67 | */ 68 | this.rawSocket.onmessage = message => { 69 | var object; 70 | try { 71 | object = EJSON.parse(message.data); 72 | } catch (ignore) { 73 | // Simply ignore the malformed message and return 74 | return; 75 | } 76 | // Outside the try-catch block as it must only catch JSON parsing 77 | // errors, not errors that may occur inside a "message:in" event 78 | // handler 79 | this.emit("message:in", object); 80 | }; 81 | 82 | } 83 | 84 | close () { 85 | /* 86 | * Avoid throwing an error if `rawSocket === null` 87 | */ 88 | if (this.rawSocket) { 89 | this.rawSocket.close(); 90 | } 91 | } 92 | 93 | } -------------------------------------------------------------------------------- /dist/Collection.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | 7 | exports.default = function (name) { 8 | var Meteor = this; 9 | if (!_Data2.default.db[name]) { 10 | _Data2.default.db.addCollection(name); 11 | } 12 | 13 | return { 14 | find: function find(selector, options) { 15 | if (typeof selector == 'string') { 16 | if (options) { 17 | return [_Data2.default.db[name].findOne({ _id: selector }, options)]; 18 | } else { 19 | return [_Data2.default.db[name].get(selector)]; 20 | } 21 | } 22 | return _Data2.default.db[name].find(selector, options); 23 | }, 24 | findOne: function findOne(selector, options) { 25 | 26 | if (typeof selector == 'string') { 27 | if (options) { 28 | return _Data2.default.db[name].findOne({ _id: selector }, options); 29 | } else { 30 | return _Data2.default.db[name].get(selector); 31 | } 32 | } 33 | return _Data2.default.db[name] && _Data2.default.db[name].findOne(selector, options); 34 | }, 35 | insert: function insert(item) { 36 | var callback = arguments.length <= 1 || arguments[1] === undefined ? function () {} : arguments[1]; 37 | 38 | 39 | var id = void 0; 40 | 41 | if ('_id' in item) { 42 | if (!item._id || typeof item._id != 'string') { 43 | return callback("Meteor requires document _id fields to be non-empty strings"); 44 | } 45 | id = item._id; 46 | } else { 47 | id = item._id = _Random2.default.id(); 48 | } 49 | 50 | if (_Data2.default.db[name].get(id)) return callback({ error: 409, reason: "Duplicate key _id with value " + id }); 51 | 52 | _Data2.default.db[name].upsert(item); 53 | 54 | Meteor.waitDdpConnected(function () { 55 | Meteor.call('/' + name + '/insert', item, function (err) { 56 | if (err) { 57 | _Data2.default.db[name].del(id); 58 | return callback(err); 59 | } 60 | 61 | callback(null, id); 62 | }); 63 | }); 64 | 65 | return id; 66 | }, 67 | update: function update(id, modifier) { 68 | var options = arguments.length <= 2 || arguments[2] === undefined ? {} : arguments[2]; 69 | var callback = arguments.length <= 3 || arguments[3] === undefined ? function () {} : arguments[3]; 70 | 71 | 72 | if (typeof options == 'function') { 73 | callback = options; 74 | options = {}; 75 | } 76 | 77 | if (!_Data2.default.db[name].get(id)) return callback({ error: 409, reason: "Item not found in collection " + name + " with id " + id }); 78 | 79 | Meteor.waitDdpConnected(function () { 80 | Meteor.call('/' + name + '/update', { _id: id }, modifier, function (err) { 81 | if (err) { 82 | return callback(err); 83 | } 84 | 85 | callback(null, id); 86 | }); 87 | }); 88 | }, 89 | remove: function remove(id) { 90 | var callback = arguments.length <= 1 || arguments[1] === undefined ? function () {} : arguments[1]; 91 | 92 | 93 | var element = this.findOne(id); 94 | if (element) { 95 | _Data2.default.db[name].del(element._id); 96 | 97 | Meteor.waitDdpConnected(function () { 98 | Meteor.call('/' + name + '/remove', { _id: id }, function (err, res) { 99 | if (err) { 100 | _Data2.default.db[name].upsert(element); 101 | return callback(err); 102 | } 103 | callback(null, res); 104 | }); 105 | }); 106 | } else { 107 | callback('No document with _id : ' + id); 108 | } 109 | } 110 | }; 111 | }; 112 | 113 | var _Data = require('./Data'); 114 | 115 | var _Data2 = _interopRequireDefault(_Data); 116 | 117 | var _Random = require('../lib/Random'); 118 | 119 | var _Random2 = _interopRequireDefault(_Random); 120 | 121 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } -------------------------------------------------------------------------------- /dist/src/Collection.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | 7 | exports.default = function (name) { 8 | var Meteor = this; 9 | if (!_Data2.default.db[name]) { 10 | _Data2.default.db.addCollection(name); 11 | } 12 | 13 | return { 14 | find: function find(selector, options) { 15 | if (typeof selector == 'string') { 16 | if (options) { 17 | return [_Data2.default.db[name].findOne({ _id: selector }, options)]; 18 | } else { 19 | return [_Data2.default.db[name].get(selector)]; 20 | } 21 | } 22 | return _Data2.default.db[name].find(selector, options); 23 | }, 24 | findOne: function findOne(selector, options) { 25 | 26 | if (typeof selector == 'string') { 27 | if (options) { 28 | return _Data2.default.db[name].findOne({ _id: selector }, options); 29 | } else { 30 | return _Data2.default.db[name].get(selector); 31 | } 32 | } 33 | return _Data2.default.db[name] && _Data2.default.db[name].findOne(selector, options); 34 | }, 35 | insert: function insert(item) { 36 | var callback = arguments.length <= 1 || arguments[1] === undefined ? function () {} : arguments[1]; 37 | 38 | 39 | var id = void 0; 40 | 41 | if ('_id' in item) { 42 | if (!item._id || typeof item._id != 'string') { 43 | return callback("Meteor requires document _id fields to be non-empty strings"); 44 | } 45 | id = item._id; 46 | } else { 47 | id = item._id = _Random2.default.id(); 48 | } 49 | 50 | if (_Data2.default.db[name].get(id)) return callback({ error: 409, reason: "Duplicate key _id with value " + id }); 51 | 52 | _Data2.default.db[name].upsert(item); 53 | 54 | Meteor.waitDdpConnected(function () { 55 | Meteor.call('/' + name + '/insert', item, function (err) { 56 | if (err) { 57 | _Data2.default.db[name].del(id); 58 | return callback(err); 59 | } 60 | 61 | callback(null, id); 62 | }); 63 | }); 64 | 65 | return id; 66 | }, 67 | update: function update(id, modifier) { 68 | var options = arguments.length <= 2 || arguments[2] === undefined ? {} : arguments[2]; 69 | var callback = arguments.length <= 3 || arguments[3] === undefined ? function () {} : arguments[3]; 70 | 71 | 72 | if (typeof options == 'function') { 73 | callback = options; 74 | options = {}; 75 | } 76 | 77 | if (!_Data2.default.db[name].get(id)) return callback({ error: 409, reason: "Item not found in collection " + name + " with id " + id }); 78 | 79 | Meteor.waitDdpConnected(function () { 80 | Meteor.call('/' + name + '/update', { _id: id }, modifier, function (err) { 81 | if (err) { 82 | return callback(err); 83 | } 84 | 85 | callback(null, id); 86 | }); 87 | }); 88 | }, 89 | remove: function remove(id) { 90 | var callback = arguments.length <= 1 || arguments[1] === undefined ? function () {} : arguments[1]; 91 | 92 | 93 | var element = this.findOne(id); 94 | if (element) { 95 | _Data2.default.db[name].del(element._id); 96 | 97 | Meteor.waitDdpConnected(function () { 98 | Meteor.call('/' + name + '/remove', { _id: id }, function (err, res) { 99 | if (err) { 100 | _Data2.default.db[name].upsert(element); 101 | return callback(err); 102 | } 103 | callback(null, res); 104 | }); 105 | }); 106 | } else { 107 | callback('No document with _id : ' + id); 108 | } 109 | } 110 | }; 111 | }; 112 | 113 | var _Data = require('./Data'); 114 | 115 | var _Data2 = _interopRequireDefault(_Data); 116 | 117 | var _Random = require('../lib/Random'); 118 | 119 | var _Random2 = _interopRequireDefault(_Random); 120 | 121 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } -------------------------------------------------------------------------------- /lib/ddp.js: -------------------------------------------------------------------------------- 1 | /** 2 | * DDP.JS 2.1.0 3 | */ 4 | 5 | 6 | import EventEmitter from "wolfy87-eventemitter"; 7 | import Queue from "./queue"; 8 | import Socket from "./socket"; 9 | import {contains, uniqueId} from "./utils"; 10 | 11 | const DDP_VERSION = "1"; 12 | const PUBLIC_EVENTS = [ 13 | // Subscription messages 14 | "ready", "nosub", "added", "changed", "removed", 15 | // Method messages 16 | "result", "updated", 17 | // Error messages 18 | "error" 19 | ]; 20 | const DEFAULT_RECONNECT_INTERVAL = 10000; 21 | 22 | export default class DDP extends EventEmitter { 23 | 24 | emit () { 25 | setTimeout(super.emit.bind(this, ...arguments), 0); 26 | } 27 | 28 | constructor (options) { 29 | 30 | super(); 31 | 32 | this.status = "disconnected"; 33 | 34 | // Default `autoConnect` and `autoReconnect` to true 35 | this.autoConnect = (options.autoConnect !== false); 36 | this.autoReconnect = (options.autoReconnect !== false); 37 | this.reconnectInterval = options.reconnectInterval || DEFAULT_RECONNECT_INTERVAL; 38 | 39 | this.messageQueue = new Queue(message => { 40 | if (this.status === "connected") { 41 | this.socket.send(message); 42 | return true; 43 | } else { 44 | return false; 45 | } 46 | }); 47 | 48 | this.socket = new Socket(options.SocketConstructor, options.endpoint); 49 | 50 | this.socket.on("open", () => { 51 | // When the socket opens, send the `connect` message 52 | // to establish the DDP connection 53 | this.socket.send({ 54 | msg: "connect", 55 | version: DDP_VERSION, 56 | support: [DDP_VERSION] 57 | }); 58 | }); 59 | 60 | this.socket.on("close", () => { 61 | this.status = "disconnected"; 62 | this.messageQueue.empty(); 63 | this.emit("disconnected"); 64 | if (this.autoReconnect) { 65 | // Schedule a reconnection 66 | setTimeout( 67 | this.socket.open.bind(this.socket), 68 | this.reconnectInterval 69 | ); 70 | } 71 | }); 72 | 73 | this.socket.on("message:in", message => { 74 | if (message.msg === "connected") { 75 | this.status = "connected"; 76 | this.messageQueue.process(); 77 | this.emit("connected"); 78 | } else if (message.msg === "ping") { 79 | // Reply with a `pong` message to prevent the server from 80 | // closing the connection 81 | this.socket.send({msg: "pong", id: message.id}); 82 | } else if (contains(PUBLIC_EVENTS, message.msg)) { 83 | this.emit(message.msg, message); 84 | } 85 | }); 86 | 87 | if (this.autoConnect) { 88 | this.connect(); 89 | } 90 | 91 | } 92 | 93 | connect () { 94 | this.socket.open(); 95 | } 96 | 97 | disconnect () { 98 | /* 99 | * If `disconnect` is called, the caller likely doesn't want the 100 | * the instance to try to auto-reconnect. Therefore we set the 101 | * `autoReconnect` flag to false. 102 | */ 103 | this.autoReconnect = false; 104 | this.socket.close(); 105 | } 106 | 107 | method (name, params) { 108 | const id = uniqueId(); 109 | this.messageQueue.push({ 110 | msg: "method", 111 | id: id, 112 | method: name, 113 | params: params 114 | }); 115 | return id; 116 | } 117 | 118 | sub (name, params) { 119 | const id = uniqueId(); 120 | this.messageQueue.push({ 121 | msg: "sub", 122 | id: id, 123 | name: name, 124 | params: params 125 | }); 126 | return id; 127 | } 128 | 129 | unsub (id) { 130 | this.messageQueue.push({ 131 | msg: "unsub", 132 | id: id 133 | }); 134 | return id; 135 | } 136 | 137 | } -------------------------------------------------------------------------------- /dist/components/ComplexListView.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | 7 | var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; 8 | 9 | var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); 10 | 11 | var _reactNative = require('react-native'); 12 | 13 | var _reactNative2 = _interopRequireDefault(_reactNative); 14 | 15 | var _Data = require('../Data'); 16 | 17 | var _Data2 = _interopRequireDefault(_Data); 18 | 19 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } 20 | 21 | function _objectWithoutProperties(obj, keys) { var target = {}; for (var i in obj) { if (keys.indexOf(i) >= 0) continue; if (!Object.prototype.hasOwnProperty.call(obj, i)) continue; target[i] = obj[i]; } return target; } 22 | 23 | function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } 24 | 25 | function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; } 26 | 27 | function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } 28 | 29 | var MeteorListView = function (_Component) { 30 | _inherits(MeteorListView, _Component); 31 | 32 | function MeteorListView(props) { 33 | _classCallCheck(this, MeteorListView); 34 | 35 | var _this = _possibleConstructorReturn(this, Object.getPrototypeOf(MeteorListView).call(this, props)); 36 | 37 | _this.state = { 38 | ds: new _reactNative.ListView.DataSource({ 39 | rowHasChanged: function rowHasChanged(row1, row2) { 40 | return row1 !== row2; 41 | } 42 | }) 43 | }; 44 | return _this; 45 | } 46 | 47 | _createClass(MeteorListView, [{ 48 | key: 'componentWillReceiveProps', 49 | value: function componentWillReceiveProps(props) { 50 | var elements = props.elements; 51 | 52 | 53 | var elems = elements(); 54 | this.setState({ 55 | ds: this.state.ds.cloneWithRows(elems) 56 | }); 57 | } 58 | }, { 59 | key: 'componentWillMount', 60 | value: function componentWillMount() { 61 | var _this2 = this; 62 | 63 | var elements = this.props.elements; 64 | 65 | 66 | this.onChange = function () { 67 | var elems = elements(); 68 | _this2.setState({ 69 | ds: _this2.state.ds.cloneWithRows(elems) 70 | }); 71 | }; 72 | 73 | this.onChange(); 74 | _Data2.default.onChange(this.onChange); 75 | } 76 | }, { 77 | key: 'componentWillUnmount', 78 | value: function componentWillUnmount() { 79 | _Data2.default.offChange(this.onChange); 80 | } 81 | }, { 82 | key: 'render', 83 | value: function render() { 84 | var ds = this.state.ds; 85 | var _props = this.props; 86 | var listViewRef = _props.listViewRef; 87 | 88 | var props = _objectWithoutProperties(_props, ['listViewRef']); 89 | 90 | return _reactNative2.default.createElement(_reactNative.ListView, _extends({}, props, { 91 | ref: listViewRef, 92 | dataSource: ds 93 | })); 94 | } 95 | }]); 96 | 97 | return MeteorListView; 98 | }(_reactNative.Component); 99 | 100 | MeteorListView.propTypes = { 101 | elements: _reactNative.PropTypes.func.isRequired, 102 | renderRow: _reactNative.PropTypes.func.isRequired, 103 | listViewRef: _reactNative.PropTypes.oneOfType([_reactNative.PropTypes.func, _reactNative.PropTypes.string]) 104 | }; 105 | exports.default = MeteorListView; -------------------------------------------------------------------------------- /dist/CollectionFS/FSCollectionImagesPreloader.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | 7 | var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); 8 | 9 | var _reactNative = require('react-native'); 10 | 11 | var _reactNative2 = _interopRequireDefault(_reactNative); 12 | 13 | var _Data = require('../Data'); 14 | 15 | var _Data2 = _interopRequireDefault(_Data); 16 | 17 | var _setProperties = require('./setProperties'); 18 | 19 | var _setProperties2 = _interopRequireDefault(_setProperties); 20 | 21 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } 22 | 23 | function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } 24 | 25 | function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; } 26 | 27 | function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } 28 | 29 | var FSCollectionImagesPreloader = function (_Component) { 30 | _inherits(FSCollectionImagesPreloader, _Component); 31 | 32 | function FSCollectionImagesPreloader(props) { 33 | _classCallCheck(this, FSCollectionImagesPreloader); 34 | 35 | var _this = _possibleConstructorReturn(this, Object.getPrototypeOf(FSCollectionImagesPreloader).call(this, props)); 36 | 37 | _this.state = { 38 | items: [] 39 | }; 40 | return _this; 41 | } 42 | 43 | _createClass(FSCollectionImagesPreloader, [{ 44 | key: 'componentWillMount', 45 | value: function componentWillMount() { 46 | var _this2 = this; 47 | 48 | var _props = this.props; 49 | var collection = _props.collection; 50 | var selector = _props.selector; 51 | 52 | 53 | this.update = function (results) { 54 | _this2.setState({ 55 | items: results.map(function (elem) { 56 | return (0, _setProperties2.default)(collection, elem); 57 | }) 58 | }); 59 | }; 60 | 61 | var collectionName = 'cfs.' + collection + '.filerecord'; 62 | 63 | if (!_Data2.default.db[collectionName]) { 64 | _Data2.default.db.addCollection(collectionName); 65 | } 66 | 67 | this.items = _Data2.default.db.observe(function () { 68 | return _Data2.default.db[collectionName].find(selector); 69 | }); 70 | 71 | this.items.subscribe(this.update); 72 | } 73 | }, { 74 | key: 'componentWillUnmount', 75 | value: function componentWillUnmount() { 76 | this.items.dispose(); 77 | } 78 | }, { 79 | key: 'render', 80 | value: function render() { 81 | var items = this.state.items; 82 | 83 | 84 | return _reactNative2.default.createElement( 85 | _reactNative.View, 86 | { style: styles.hidden }, 87 | items && items.map(function (item) { 88 | return _reactNative2.default.createElement(_reactNative.Image, { style: styles.hidden, key: item._id, source: { uri: item.url() } }); 89 | }) 90 | ); 91 | } 92 | }]); 93 | 94 | return FSCollectionImagesPreloader; 95 | }(_reactNative.Component); 96 | 97 | FSCollectionImagesPreloader.propTypes = { 98 | collection: _reactNative.PropTypes.string.isRequired, 99 | selector: _reactNative.PropTypes.oneOfType([_reactNative.PropTypes.string, _reactNative.PropTypes.object]) 100 | }; 101 | FSCollectionImagesPreloader.defaultProps = { 102 | selector: {} 103 | }; 104 | exports.default = FSCollectionImagesPreloader; 105 | 106 | 107 | var styles = _reactNative.StyleSheet.create({ 108 | hidden: { 109 | width: 1, 110 | height: 1, 111 | position: 'absolute', 112 | top: -100000, 113 | left: -10000, 114 | opacity: 0 115 | } 116 | }); -------------------------------------------------------------------------------- /dist/components/ListView.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | 7 | var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; 8 | 9 | var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); 10 | 11 | var _reactNative = require('react-native'); 12 | 13 | var _reactNative2 = _interopRequireDefault(_reactNative); 14 | 15 | var _Data = require('../Data'); 16 | 17 | var _Data2 = _interopRequireDefault(_Data); 18 | 19 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } 20 | 21 | function _objectWithoutProperties(obj, keys) { var target = {}; for (var i in obj) { if (keys.indexOf(i) >= 0) continue; if (!Object.prototype.hasOwnProperty.call(obj, i)) continue; target[i] = obj[i]; } return target; } 22 | 23 | function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } 24 | 25 | function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; } 26 | 27 | function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } 28 | 29 | var MeteorListView = function (_Component) { 30 | _inherits(MeteorListView, _Component); 31 | 32 | function MeteorListView(props) { 33 | _classCallCheck(this, MeteorListView); 34 | 35 | var _this = _possibleConstructorReturn(this, Object.getPrototypeOf(MeteorListView).call(this, props)); 36 | 37 | _this.state = { 38 | ds: new _reactNative.ListView.DataSource({ 39 | rowHasChanged: function rowHasChanged(row1, row2) { 40 | return row1 !== row2; 41 | } 42 | }) 43 | }; 44 | return _this; 45 | } 46 | 47 | _createClass(MeteorListView, [{ 48 | key: 'componentWillReceiveProps', 49 | value: function componentWillReceiveProps(props) { 50 | var collection = props.collection; 51 | var selector = props.selector; 52 | var options = props.options; 53 | 54 | 55 | this.update(_Data2.default.db[collection].find(selector, options)); 56 | } 57 | }, { 58 | key: 'componentWillMount', 59 | value: function componentWillMount() { 60 | var _this2 = this; 61 | 62 | var _props = this.props; 63 | var collection = _props.collection; 64 | var selector = _props.selector; 65 | var options = _props.options; 66 | 67 | 68 | this.update = function (results) { 69 | _this2.setState({ 70 | ds: _this2.state.ds.cloneWithRows(results) 71 | }); 72 | }; 73 | 74 | if (!_Data2.default.db[collection]) { 75 | _Data2.default.db.addCollection(collection); 76 | } 77 | 78 | this.items = _Data2.default.db.observe(function () { 79 | return _Data2.default.db[collection].find(selector, options); 80 | }); 81 | 82 | this.items.subscribe(this.update); 83 | } 84 | }, { 85 | key: 'componentWillUnmount', 86 | value: function componentWillUnmount() { 87 | this.items.dispose(); 88 | } 89 | }, { 90 | key: 'render', 91 | value: function render() { 92 | var ds = this.state.ds; 93 | var _props2 = this.props; 94 | var listViewRef = _props2.listViewRef; 95 | 96 | var props = _objectWithoutProperties(_props2, ['listViewRef']); 97 | 98 | return _reactNative2.default.createElement(_reactNative.ListView, _extends({}, props, { 99 | ref: listViewRef, 100 | dataSource: ds 101 | })); 102 | } 103 | }]); 104 | 105 | return MeteorListView; 106 | }(_reactNative.Component); 107 | 108 | MeteorListView.propTypes = { 109 | collection: _reactNative.PropTypes.string.isRequired, 110 | selector: _reactNative.PropTypes.oneOfType([_reactNative.PropTypes.string, _reactNative.PropTypes.object]), 111 | options: _reactNative.PropTypes.oneOfType([_reactNative.PropTypes.string, _reactNative.PropTypes.object]), 112 | renderRow: _reactNative.PropTypes.func.isRequired, 113 | listViewRef: _reactNative.PropTypes.oneOfType([_reactNative.PropTypes.func, _reactNative.PropTypes.string]) 114 | }; 115 | MeteorListView.defaultProps = { 116 | selector: {} 117 | }; 118 | exports.default = MeteorListView; -------------------------------------------------------------------------------- /dist/user/User.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var _reactNative = require('react-native'); 4 | 5 | var _Data = require('../Data'); 6 | 7 | var _Data2 = _interopRequireDefault(_Data); 8 | 9 | var _utils = require('../../lib/utils'); 10 | 11 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } 12 | 13 | function _asyncToGenerator(fn) { return function () { var gen = fn.apply(this, arguments); return new Promise(function (resolve, reject) { function step(key, arg) { try { var info = gen[key](arg); var value = info.value; } catch (error) { reject(error); return; } if (info.done) { resolve(value); } else { return Promise.resolve(value).then(function (value) { return step("next", value); }, function (err) { return step("throw", err); }); } } return step("next"); }); }; } 14 | 15 | var TOKEN_KEY = 'reactnativemeteor_usertoken'; 16 | 17 | module.exports = { 18 | user: function user() { 19 | if (!this._userIdSaved) return null; 20 | 21 | return this.collection('users').findOne(this._userIdSaved); 22 | }, 23 | userId: function userId() { 24 | if (!this._userIdSaved) return null; 25 | 26 | var user = this.collection('users').findOne(this._userIdSaved); 27 | return user && user._id; 28 | }, 29 | 30 | _isLoggingIn: true, 31 | loggingIn: function loggingIn() { 32 | return this._isLoggingIn; 33 | }, 34 | logout: function logout(callback) { 35 | var _this = this; 36 | 37 | this.call("logout", function (err) { 38 | _this.handleLogout(); 39 | _this.connect(); 40 | 41 | typeof callback == 'function' && callback(err); 42 | }); 43 | }, 44 | handleLogout: function handleLogout() { 45 | _reactNative.AsyncStorage.removeItem(TOKEN_KEY); 46 | _Data2.default._tokenIdSaved = null; 47 | this._userIdSaved = null; 48 | }, 49 | loginWithPassword: function loginWithPassword(selector, password, callback) { 50 | var _this2 = this; 51 | 52 | if (typeof selector === 'string') { 53 | if (selector.indexOf('@') === -1) selector = { username: selector };else selector = { email: selector }; 54 | } 55 | 56 | this._startLoggingIn(); 57 | this.call("login", { 58 | user: selector, 59 | password: (0, _utils.hashPassword)(password) 60 | }, function (err, result) { 61 | _this2._endLoggingIn(); 62 | 63 | _this2._handleLoginCallback(err, result); 64 | 65 | typeof callback == 'function' && callback(err); 66 | }); 67 | }, 68 | logoutOtherClients: function logoutOtherClients() { 69 | var _this3 = this; 70 | 71 | var callback = arguments.length <= 0 || arguments[0] === undefined ? function () {} : arguments[0]; 72 | 73 | this.call('getNewToken', function (err, res) { 74 | if (err) return callback(err); 75 | 76 | _this3._handleLoginCallback(err, res); 77 | 78 | _this3.call('removeOtherTokens', function (err) { 79 | callback(err); 80 | }); 81 | }); 82 | }, 83 | _startLoggingIn: function _startLoggingIn() { 84 | this._isLoggingIn = true; 85 | _Data2.default.notify('loggingIn'); 86 | }, 87 | _endLoggingIn: function _endLoggingIn() { 88 | this._isLoggingIn = false; 89 | _Data2.default.notify('loggingIn'); 90 | }, 91 | _handleLoginCallback: function _handleLoginCallback(err, result) { 92 | if (!err) { 93 | //save user id and token 94 | _reactNative.AsyncStorage.setItem(TOKEN_KEY, result.token); 95 | _Data2.default._tokenIdSaved = result.token; 96 | this._userIdSaved = result.id; 97 | _Data2.default.notify('onLogin'); 98 | } else { 99 | _Data2.default.notify('onLoginFailure'); 100 | this.handleLogout(); 101 | } 102 | _Data2.default.notify('change'); 103 | }, 104 | _loadInitialUser: function _loadInitialUser() { 105 | var _this4 = this; 106 | 107 | return _asyncToGenerator(regeneratorRuntime.mark(function _callee() { 108 | var value; 109 | return regeneratorRuntime.wrap(function _callee$(_context) { 110 | while (1) { 111 | switch (_context.prev = _context.next) { 112 | case 0: 113 | value = null; 114 | _context.prev = 1; 115 | _context.next = 4; 116 | return _reactNative.AsyncStorage.getItem(TOKEN_KEY); 117 | 118 | case 4: 119 | value = _context.sent; 120 | _context.next = 10; 121 | break; 122 | 123 | case 7: 124 | _context.prev = 7; 125 | _context.t0 = _context['catch'](1); 126 | 127 | console.warn('AsyncStorage error: ' + _context.t0.message); 128 | 129 | case 10: 130 | _context.prev = 10; 131 | 132 | _Data2.default._tokenIdSaved = value; 133 | if (value !== null) { 134 | _this4._startLoggingIn(); 135 | _this4.call('login', { resume: value }, function (err, result) { 136 | _this4._endLoggingIn(); 137 | _this4._handleLoginCallback(err, result); 138 | }); 139 | } else { 140 | _this4._endLoggingIn(); 141 | } 142 | return _context.finish(10); 143 | 144 | case 14: 145 | case 'end': 146 | return _context.stop(); 147 | } 148 | } 149 | }, _callee, _this4, [[1, 7, 10, 14]]); 150 | }))(); 151 | } 152 | }; -------------------------------------------------------------------------------- /dist/lib/socket.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | 7 | var _classCallCheck2 = require("babel-runtime/helpers/classCallCheck"); 8 | 9 | var _classCallCheck3 = _interopRequireDefault(_classCallCheck2); 10 | 11 | var _createClass2 = require("babel-runtime/helpers/createClass"); 12 | 13 | var _createClass3 = _interopRequireDefault(_createClass2); 14 | 15 | var _possibleConstructorReturn2 = require("babel-runtime/helpers/possibleConstructorReturn"); 16 | 17 | var _possibleConstructorReturn3 = _interopRequireDefault(_possibleConstructorReturn2); 18 | 19 | var _inherits2 = require("babel-runtime/helpers/inherits"); 20 | 21 | var _inherits3 = _interopRequireDefault(_inherits2); 22 | 23 | var _wolfy87Eventemitter = require("wolfy87-eventemitter"); 24 | 25 | var _wolfy87Eventemitter2 = _interopRequireDefault(_wolfy87Eventemitter); 26 | 27 | var _ejson = require("ejson"); 28 | 29 | var _ejson2 = _interopRequireDefault(_ejson); 30 | 31 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } 32 | 33 | var Socket = function (_EventEmitter) { 34 | (0, _inherits3.default)(Socket, _EventEmitter); 35 | 36 | function Socket(SocketConstructor, endpoint) { 37 | (0, _classCallCheck3.default)(this, Socket); 38 | 39 | var _this = (0, _possibleConstructorReturn3.default)(this, Object.getPrototypeOf(Socket).call(this)); 40 | 41 | _this.SocketConstructor = SocketConstructor; 42 | _this.endpoint = endpoint; 43 | _this.rawSocket = null; 44 | return _this; 45 | } 46 | 47 | (0, _createClass3.default)(Socket, [{ 48 | key: "send", 49 | value: function send(object) { 50 | var message = _ejson2.default.stringify(object); 51 | this.rawSocket.send(message); 52 | // Emit a copy of the object, as the listener might mutate it. 53 | this.emit("message:out", _ejson2.default.parse(message)); 54 | } 55 | }, { 56 | key: "open", 57 | value: function open() { 58 | var _this2 = this; 59 | 60 | /* 61 | * Makes `open` a no-op if there's already a `rawSocket`. This avoids 62 | * memory / socket leaks if `open` is called twice (e.g. by a user 63 | * calling `ddp.connect` twice) without properly disposing of the 64 | * socket connection. `rawSocket` gets automatically set to `null` only 65 | * when it goes into a closed or error state. This way `rawSocket` is 66 | * disposed of correctly: the socket connection is closed, and the 67 | * object can be garbage collected. 68 | */ 69 | if (this.rawSocket) { 70 | return; 71 | } 72 | this.rawSocket = new this.SocketConstructor(this.endpoint); 73 | 74 | /* 75 | * Calls to `onopen` and `onclose` directly trigger the `open` and 76 | * `close` events on the `Socket` instance. 77 | */ 78 | this.rawSocket.onopen = function () { 79 | return _this2.emit("open"); 80 | }; 81 | this.rawSocket.onclose = function () { 82 | _this2.rawSocket = null; 83 | _this2.emit("close"); 84 | }; 85 | /* 86 | * Calls to `onerror` trigger the `close` event on the `Socket` 87 | * instance, and cause the `rawSocket` object to be disposed of. 88 | * Since it's not clear what conditions could cause the error and if 89 | * it's possible to recover from it, we prefer to always close the 90 | * connection (if it isn't already) and dispose of the socket object. 91 | */ 92 | this.rawSocket.onerror = function () { 93 | // It's not clear what the socket lifecycle is when errors occurr. 94 | // Hence, to avoid the `close` event to be emitted twice, before 95 | // manually closing the socket we de-register the `onclose` 96 | // callback. 97 | delete _this2.rawSocket.onclose; 98 | // Safe to perform even if the socket is already closed 99 | _this2.rawSocket.close(); 100 | _this2.rawSocket = null; 101 | _this2.emit("close"); 102 | }; 103 | /* 104 | * Calls to `onmessage` trigger a `message:in` event on the `Socket` 105 | * instance only once the message (first parameter to `onmessage`) has 106 | * been successfully parsed into a javascript object. 107 | */ 108 | this.rawSocket.onmessage = function (message) { 109 | var object; 110 | try { 111 | object = _ejson2.default.parse(message.data); 112 | } catch (ignore) { 113 | // Simply ignore the malformed message and return 114 | return; 115 | } 116 | // Outside the try-catch block as it must only catch JSON parsing 117 | // errors, not errors that may occur inside a "message:in" event 118 | // handler 119 | _this2.emit("message:in", object); 120 | }; 121 | } 122 | }, { 123 | key: "close", 124 | value: function close() { 125 | /* 126 | * Avoid throwing an error if `rawSocket === null` 127 | */ 128 | if (this.rawSocket) { 129 | this.rawSocket.close(); 130 | } 131 | } 132 | }]); 133 | return Socket; 134 | }(_wolfy87Eventemitter2.default); 135 | 136 | exports.default = Socket; -------------------------------------------------------------------------------- /dist/src/user/User.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var _regenerator = require('babel-runtime/regenerator'); 4 | 5 | var _regenerator2 = _interopRequireDefault(_regenerator); 6 | 7 | var _asyncToGenerator2 = require('babel-runtime/helpers/asyncToGenerator'); 8 | 9 | var _asyncToGenerator3 = _interopRequireDefault(_asyncToGenerator2); 10 | 11 | var _electronJsonStorage = require('electron-json-storage'); 12 | 13 | var _electronJsonStorage2 = _interopRequireDefault(_electronJsonStorage); 14 | 15 | var _Data = require('../Data'); 16 | 17 | var _Data2 = _interopRequireDefault(_Data); 18 | 19 | var _utils = require('../../lib/utils'); 20 | 21 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } 22 | 23 | var TOKEN_KEY = 'reactnativemeteor_usertoken'; //import { AsyncStorage } from 'react-native'; 24 | 25 | 26 | module.exports = { 27 | user: function user() { 28 | if (!this._userIdSaved) return null; 29 | 30 | return this.collection('users').findOne(this._userIdSaved); 31 | }, 32 | userId: function userId() { 33 | if (!this._userIdSaved) return null; 34 | 35 | var user = this.collection('users').findOne(this._userIdSaved); 36 | return user && user._id; 37 | }, 38 | 39 | _isLoggingIn: true, 40 | loggingIn: function loggingIn() { 41 | return this._isLoggingIn; 42 | }, 43 | logout: function logout(callback) { 44 | var _this = this; 45 | 46 | this.call("logout", function (err) { 47 | _this.handleLogout(); 48 | _this.connect(); 49 | 50 | typeof callback == 'function' && callback(err); 51 | }); 52 | }, 53 | handleLogout: function handleLogout() { 54 | _electronJsonStorage2.default.remove(TOKEN_KEY); 55 | _Data2.default._tokenIdSaved = null; 56 | this._userIdSaved = null; 57 | }, 58 | loginWithPassword: function loginWithPassword(selector, password, callback) { 59 | var _this2 = this; 60 | 61 | if (typeof selector === 'string') { 62 | if (selector.indexOf('@') === -1) selector = { username: selector };else selector = { email: selector }; 63 | } 64 | 65 | this._startLoggingIn(); 66 | this.call("login", { 67 | user: selector, 68 | password: (0, _utils.hashPassword)(password) 69 | }, function (err, result) { 70 | _this2._endLoggingIn(); 71 | 72 | _this2._handleLoginCallback(err, result); 73 | 74 | typeof callback == 'function' && callback(err); 75 | }); 76 | }, 77 | logoutOtherClients: function logoutOtherClients() { 78 | var _this3 = this; 79 | 80 | var callback = arguments.length <= 0 || arguments[0] === undefined ? function () {} : arguments[0]; 81 | 82 | this.call('getNewToken', function (err, res) { 83 | if (err) return callback(err); 84 | 85 | _this3._handleLoginCallback(err, res); 86 | 87 | _this3.call('removeOtherTokens', function (err) { 88 | callback(err); 89 | }); 90 | }); 91 | }, 92 | _startLoggingIn: function _startLoggingIn() { 93 | this._isLoggingIn = true; 94 | _Data2.default.notify('loggingIn'); 95 | }, 96 | _endLoggingIn: function _endLoggingIn() { 97 | this._isLoggingIn = false; 98 | _Data2.default.notify('loggingIn'); 99 | }, 100 | _handleLoginCallback: function _handleLoginCallback(err, result) { 101 | if (!err) { 102 | //save user id and token 103 | _electronJsonStorage2.default.set(TOKEN_KEY, { token: result.token }); 104 | _Data2.default._tokenIdSaved = result.token; 105 | this._userIdSaved = result.id; 106 | _Data2.default.notify('onLogin'); 107 | } else { 108 | _Data2.default.notify('onLoginFailure'); 109 | this.handleLogout(); 110 | } 111 | _Data2.default.notify('change'); 112 | }, 113 | _loadInitialUser: function _loadInitialUser() { 114 | var _this4 = this; 115 | 116 | return (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee() { 117 | var value, getStorage; 118 | return _regenerator2.default.wrap(function _callee$(_context) { 119 | while (1) { 120 | switch (_context.prev = _context.next) { 121 | case 0: 122 | value = null; 123 | _context.prev = 1; 124 | 125 | getStorage = function getStorage() { 126 | return new Promise(function (resolve, reject) { 127 | _electronJsonStorage2.default.get(TOKEN_KEY, function (err, res) { 128 | resolve(res); 129 | }); 130 | }); 131 | }; 132 | 133 | _context.next = 5; 134 | return getStorage(); 135 | 136 | case 5: 137 | value = _context.sent; 138 | 139 | value = value.token; 140 | _context.next = 12; 141 | break; 142 | 143 | case 9: 144 | _context.prev = 9; 145 | _context.t0 = _context['catch'](1); 146 | 147 | console.warn('json-storage error: ' + _context.t0.message); 148 | 149 | case 12: 150 | _context.prev = 12; 151 | 152 | _Data2.default._tokenIdSaved = value; 153 | if (value !== null) { 154 | _this4._startLoggingIn(); 155 | _this4.call('login', { resume: value }, function (err, result) { 156 | _this4._endLoggingIn(); 157 | _this4._handleLoginCallback(err, result); 158 | }); 159 | } else { 160 | _this4._endLoggingIn(); 161 | } 162 | return _context.finish(12); 163 | 164 | case 16: 165 | case 'end': 166 | return _context.stop(); 167 | } 168 | } 169 | }, _callee, _this4, [[1, 9, 12, 16]]); 170 | }))(); 171 | } 172 | }; -------------------------------------------------------------------------------- /dist/lib/ddp.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | 7 | var _classCallCheck2 = require("babel-runtime/helpers/classCallCheck"); 8 | 9 | var _classCallCheck3 = _interopRequireDefault(_classCallCheck2); 10 | 11 | var _possibleConstructorReturn2 = require("babel-runtime/helpers/possibleConstructorReturn"); 12 | 13 | var _possibleConstructorReturn3 = _interopRequireDefault(_possibleConstructorReturn2); 14 | 15 | var _createClass2 = require("babel-runtime/helpers/createClass"); 16 | 17 | var _createClass3 = _interopRequireDefault(_createClass2); 18 | 19 | var _inherits2 = require("babel-runtime/helpers/inherits"); 20 | 21 | var _inherits3 = _interopRequireDefault(_inherits2); 22 | 23 | var _get3 = require("babel-runtime/helpers/get"); 24 | 25 | var _get4 = _interopRequireDefault(_get3); 26 | 27 | var _wolfy87Eventemitter = require("wolfy87-eventemitter"); 28 | 29 | var _wolfy87Eventemitter2 = _interopRequireDefault(_wolfy87Eventemitter); 30 | 31 | var _queue = require("./queue"); 32 | 33 | var _queue2 = _interopRequireDefault(_queue); 34 | 35 | var _socket = require("./socket"); 36 | 37 | var _socket2 = _interopRequireDefault(_socket); 38 | 39 | var _utils = require("./utils"); 40 | 41 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } 42 | 43 | /** 44 | * DDP.JS 2.1.0 45 | */ 46 | 47 | var DDP_VERSION = "1"; 48 | var PUBLIC_EVENTS = [ 49 | // Subscription messages 50 | "ready", "nosub", "added", "changed", "removed", 51 | // Method messages 52 | "result", "updated", 53 | // Error messages 54 | "error"]; 55 | var DEFAULT_RECONNECT_INTERVAL = 10000; 56 | 57 | var DDP = function (_EventEmitter) { 58 | (0, _inherits3.default)(DDP, _EventEmitter); 59 | (0, _createClass3.default)(DDP, [{ 60 | key: "emit", 61 | value: function emit() { 62 | var _get2; 63 | 64 | setTimeout((_get2 = (0, _get4.default)(Object.getPrototypeOf(DDP.prototype), "emit", this)).bind.apply(_get2, [this].concat(Array.prototype.slice.call(arguments))), 0); 65 | } 66 | }]); 67 | 68 | function DDP(options) { 69 | (0, _classCallCheck3.default)(this, DDP); 70 | 71 | var _this = (0, _possibleConstructorReturn3.default)(this, Object.getPrototypeOf(DDP).call(this)); 72 | 73 | _this.status = "disconnected"; 74 | 75 | // Default `autoConnect` and `autoReconnect` to true 76 | _this.autoConnect = options.autoConnect !== false; 77 | _this.autoReconnect = options.autoReconnect !== false; 78 | _this.reconnectInterval = options.reconnectInterval || DEFAULT_RECONNECT_INTERVAL; 79 | 80 | _this.messageQueue = new _queue2.default(function (message) { 81 | if (_this.status === "connected") { 82 | _this.socket.send(message); 83 | return true; 84 | } else { 85 | return false; 86 | } 87 | }); 88 | 89 | _this.socket = new _socket2.default(options.SocketConstructor, options.endpoint); 90 | 91 | _this.socket.on("open", function () { 92 | // When the socket opens, send the `connect` message 93 | // to establish the DDP connection 94 | _this.socket.send({ 95 | msg: "connect", 96 | version: DDP_VERSION, 97 | support: [DDP_VERSION] 98 | }); 99 | }); 100 | 101 | _this.socket.on("close", function () { 102 | _this.status = "disconnected"; 103 | _this.messageQueue.empty(); 104 | _this.emit("disconnected"); 105 | if (_this.autoReconnect) { 106 | // Schedule a reconnection 107 | setTimeout(_this.socket.open.bind(_this.socket), _this.reconnectInterval); 108 | } 109 | }); 110 | 111 | _this.socket.on("message:in", function (message) { 112 | if (message.msg === "connected") { 113 | _this.status = "connected"; 114 | _this.messageQueue.process(); 115 | _this.emit("connected"); 116 | } else if (message.msg === "ping") { 117 | // Reply with a `pong` message to prevent the server from 118 | // closing the connection 119 | _this.socket.send({ msg: "pong", id: message.id }); 120 | } else if ((0, _utils.contains)(PUBLIC_EVENTS, message.msg)) { 121 | _this.emit(message.msg, message); 122 | } 123 | }); 124 | 125 | if (_this.autoConnect) { 126 | _this.connect(); 127 | } 128 | 129 | return _this; 130 | } 131 | 132 | (0, _createClass3.default)(DDP, [{ 133 | key: "connect", 134 | value: function connect() { 135 | this.socket.open(); 136 | } 137 | }, { 138 | key: "disconnect", 139 | value: function disconnect() { 140 | /* 141 | * If `disconnect` is called, the caller likely doesn't want the 142 | * the instance to try to auto-reconnect. Therefore we set the 143 | * `autoReconnect` flag to false. 144 | */ 145 | this.autoReconnect = false; 146 | this.socket.close(); 147 | } 148 | }, { 149 | key: "method", 150 | value: function method(name, params) { 151 | var id = (0, _utils.uniqueId)(); 152 | this.messageQueue.push({ 153 | msg: "method", 154 | id: id, 155 | method: name, 156 | params: params 157 | }); 158 | return id; 159 | } 160 | }, { 161 | key: "sub", 162 | value: function sub(name, params) { 163 | var id = (0, _utils.uniqueId)(); 164 | this.messageQueue.push({ 165 | msg: "sub", 166 | id: id, 167 | name: name, 168 | params: params 169 | }); 170 | return id; 171 | } 172 | }, { 173 | key: "unsub", 174 | value: function unsub(id) { 175 | this.messageQueue.push({ 176 | msg: "unsub", 177 | id: id 178 | }); 179 | return id; 180 | } 181 | }]); 182 | return DDP; 183 | }(_wolfy87Eventemitter2.default); 184 | 185 | exports.default = DDP; -------------------------------------------------------------------------------- /src/components/Mixin.js: -------------------------------------------------------------------------------- 1 | import Trackr from 'trackr'; 2 | import EJSON from 'ejson'; 3 | import Data from '../Data'; 4 | 5 | export default { 6 | componentWillMount() { 7 | 8 | Data.waitDdpReady(()=>{ 9 | if(this.getMeteorData) { 10 | this.data = {}; 11 | this._meteorDataManager = new MeteorDataManager(this); 12 | const newData = this._meteorDataManager.calculateData(); 13 | this._meteorDataManager.updateData(newData); 14 | } 15 | 16 | if(this.startMeteorSubscriptions) { 17 | console.warn('startMeteorSubscriptions is deprecated and will be removed soon. Please create your subscriptions in getMeteorData.'); 18 | this._meteorSubscriptionsManager = new MeteorSubscriptionsManager(this); 19 | this._meteorSubscriptionsManager.getMeteorSubscriptions(); 20 | } 21 | }); 22 | 23 | 24 | }, 25 | componentWillUpdate(nextProps, nextState) { 26 | 27 | if(this.startMeteorSubscriptions) { 28 | if(!EJSON.equals(this.state, nextState) || !EJSON.equals(this.props, nextProps)) { 29 | this._meteorSubscriptionsManager._meteorDataChangedCallback() 30 | } 31 | } 32 | 33 | if(this.getMeteorData) { 34 | const saveProps = this.props; 35 | const saveState = this.state; 36 | let newData; 37 | try { 38 | // Temporarily assign this.state and this.props, 39 | // so that they are seen by getMeteorData! 40 | // This is a simulation of how the proposed Observe API 41 | // for React will work, which calls observe() after 42 | // componentWillUpdate and after props and state are 43 | // updated, but before render() is called. 44 | // See https://github.com/facebook/react/issues/3398. 45 | this.props = nextProps; 46 | this.state = nextState; 47 | newData = this._meteorDataManager.calculateData(); 48 | } finally { 49 | this.props = saveProps; 50 | this.state = saveState; 51 | } 52 | 53 | this._meteorDataManager.updateData(newData); 54 | } 55 | 56 | }, 57 | componentWillUnmount() { 58 | if(this._meteorDataManager) { 59 | this._meteorDataManager.dispose(); 60 | } 61 | 62 | if(this._meteorSubscriptionsManager) { 63 | this._meteorSubscriptionsManager.dispose(); 64 | } 65 | 66 | } 67 | }; 68 | 69 | 70 | 71 | 72 | 73 | class MeteorSubscriptionsManager { 74 | constructor(component) { 75 | this.component = component; 76 | this.computation = null; 77 | 78 | this._meteorSubscriptionsDep = new Trackr.Dependency(); 79 | 80 | this._meteorDataChangedCallback = ()=>{this._meteorSubscriptionsDep.changed()}; 81 | 82 | Data.onChange(this._meteorDataChangedCallback); 83 | } 84 | dispose() { 85 | if (this.computation) { 86 | this.computation.stop(); 87 | this.computation = null; 88 | } 89 | 90 | Data.offChange(this._meteorDataChangedCallback); 91 | } 92 | stateOrPropsChanged() { 93 | 94 | } 95 | getMeteorSubscriptions() { 96 | this.computation = Trackr.nonreactive(() => { 97 | return Trackr.autorun((c) => { 98 | this._meteorSubscriptionsDep.depend(); 99 | 100 | this.component.startMeteorSubscriptions(); 101 | 102 | }); 103 | }); 104 | } 105 | 106 | } 107 | 108 | 109 | // A class to keep the state and utility methods needed to manage 110 | // the Meteor data for a component. 111 | class MeteorDataManager { 112 | constructor(component) { 113 | this.component = component; 114 | this.computation = null; 115 | this.oldData = null; 116 | this._meteorDataDep = new Trackr.Dependency(); 117 | 118 | this._meteorDataChangedCallback = ()=>{this._meteorDataDep.changed()}; 119 | 120 | Data.onChange(this._meteorDataChangedCallback); 121 | } 122 | 123 | dispose() { 124 | if (this.computation) { 125 | this.computation.stop(); 126 | this.computation = null; 127 | } 128 | 129 | Data.offChange(this._meteorDataChangedCallback); 130 | } 131 | 132 | calculateData() { 133 | const component = this.component; 134 | 135 | if (!component.getMeteorData) { 136 | return null; 137 | } 138 | 139 | if (this.computation) { 140 | this.computation.stop(); 141 | this.computation = null; 142 | } 143 | 144 | let data; 145 | // Use Tracker.nonreactive in case we are inside a Tracker Computation. 146 | // This can happen if someone calls `ReactDOM.render` inside a Computation. 147 | // In that case, we want to opt out of the normal behavior of nested 148 | // Computations, where if the outer one is invalidated or stopped, 149 | // it stops the inner one. 150 | 151 | this.computation = Trackr.nonreactive(() => { 152 | return Trackr.autorun((c) => { 153 | this._meteorDataDep.depend(); 154 | if (c.firstRun) { 155 | const savedSetState = component.setState; 156 | try { 157 | component.setState = () => { 158 | throw new Error( 159 | "Can't call `setState` inside `getMeteorData` as this could cause an endless" + 160 | " loop. To respond to Meteor data changing, consider making this component" + 161 | " a \"wrapper component\" that only fetches data and passes it in as props to" + 162 | " a child component. Then you can use `componentWillReceiveProps` in that" + 163 | " child component."); 164 | }; 165 | 166 | data = component.getMeteorData(); 167 | } finally { 168 | component.setState = savedSetState; 169 | } 170 | 171 | 172 | } else { 173 | // Stop this computation instead of using the re-run. 174 | // We use a brand-new autorun for each call to getMeteorData 175 | // to capture dependencies on any reactive data sources that 176 | // are accessed. The reason we can't use a single autorun 177 | // for the lifetime of the component is that Tracker only 178 | // re-runs autoruns at flush time, while we need to be able to 179 | // re-call getMeteorData synchronously whenever we want, e.g. 180 | // from componentWillUpdate. 181 | c.stop(); 182 | // Calling forceUpdate() triggers componentWillUpdate which 183 | // recalculates getMeteorData() and re-renders the component. 184 | component.forceUpdate(); 185 | } 186 | }); 187 | }); 188 | 189 | return data; 190 | } 191 | 192 | updateData(newData) { 193 | const component = this.component; 194 | const oldData = this.oldData; 195 | 196 | if (! (newData && (typeof newData) === 'object')) { 197 | throw new Error("Expected object returned from getMeteorData"); 198 | } 199 | // update componentData in place based on newData 200 | for (let key in newData) { 201 | component.data[key] = newData[key]; 202 | } 203 | // if there is oldData (which is every time this method is called 204 | // except the first), delete keys in newData that aren't in 205 | // oldData. don't interfere with other keys, in case we are 206 | // co-existing with something else that writes to a component's 207 | // this.data. 208 | if (oldData) { 209 | for (let key in oldData) { 210 | if (!(key in newData)) { 211 | delete component.data[key]; 212 | } 213 | } 214 | } 215 | this.oldData = newData; 216 | } 217 | } 218 | 219 | -------------------------------------------------------------------------------- /src/Meteor.js: -------------------------------------------------------------------------------- 1 | 2 | //import { NetInfo } from 'react-native'; 3 | 4 | import reactMixin from 'react-mixin'; 5 | import Trackr from 'trackr'; 6 | import EJSON from 'ejson'; 7 | import DDP from '../lib/ddp.js'; 8 | import Random from '../lib/Random'; 9 | 10 | import Data from './Data'; 11 | import collection from './Collection'; 12 | import call from './Call'; 13 | 14 | import Mixin from './components/Mixin'; 15 | import createContainer from './components/createContainer'; 16 | 17 | import FSCollection from './CollectionFS/FSCollection'; 18 | import FSCollectionImagesPreloader from './CollectionFS/FSCollectionImagesPreloader'; 19 | 20 | import User from './user/User'; 21 | import Accounts from './user/Accounts'; 22 | 23 | 24 | module.exports = { 25 | Accounts: Accounts, 26 | FSCollectionImagesPreloader: FSCollectionImagesPreloader, 27 | collection: collection, 28 | FSCollection: FSCollection, 29 | createContainer: createContainer, 30 | getData() { 31 | return Data; 32 | }, 33 | connectMeteor(reactClass) { 34 | return reactMixin.onClass(reactClass, Mixin); 35 | }, 36 | ...User, 37 | status() { 38 | return { 39 | connected: Data.ddp ? Data.ddp.status=="connected" : false, 40 | status: Data.ddp ? Data.ddp.status : "disconnected" 41 | //retryCount: 0 42 | //retryTime: 43 | //reason: 44 | } 45 | }, 46 | call: call, 47 | disconnect() { 48 | if(Data.ddp) { 49 | Data.ddp.disconnect(); 50 | } 51 | }, 52 | _subscriptionsRestart() { 53 | 54 | for(var i in Data.subscriptions) { 55 | const sub = Data.subscriptions[i]; 56 | Data.ddp.unsub(sub.subIdRemember); 57 | sub.subIdRemember = Data.ddp.sub(sub.name, sub.params); 58 | } 59 | 60 | }, 61 | waitDdpConnected(cb) { 62 | 63 | if(Data.ddp && Data.ddp.status == 'connected') { 64 | cb(); 65 | } else if(Data.ddp) { 66 | Data.ddp.once('connected', cb); 67 | } else { 68 | setTimeout(()=>{ this.waitDdpConnected(cb) }, 10); 69 | } 70 | 71 | }, 72 | reconnect() { 73 | Data.ddp && Data.ddp.connect(); 74 | }, 75 | connect(endpoint, options) { 76 | if(!endpoint) endpoint = Data._endpoint; 77 | if(!options) options = Data._options; 78 | 79 | Data._endpoint = endpoint; 80 | Data._options = options; 81 | 82 | 83 | this.ddp = Data.ddp = new DDP({ 84 | endpoint: endpoint, 85 | SocketConstructor: WebSocket, 86 | ...options 87 | }); 88 | 89 | this.subscribe('_roles'); 90 | //TODO 91 | /* 92 | NetInfo.isConnected.addEventListener('change', isConnected=>{ 93 | if(isConnected) { 94 | Data.ddp.connect(); 95 | } 96 | }); 97 | */ 98 | 99 | 100 | Data.ddp.on("connected", ()=>{ 101 | console.info("Connected to DDP server."); 102 | this._loadInitialUser(); 103 | 104 | this._subscriptionsRestart(); 105 | }); 106 | 107 | let lastDisconnect = null; 108 | Data.ddp.on("disconnected", ()=>{ 109 | console.info("Disconnected from DDP server."); 110 | 111 | if(!lastDisconnect || new Date() - lastDisconnect > 3000) { 112 | Data.ddp.connect(); 113 | } 114 | 115 | lastDisconnect = new Date(); 116 | 117 | }); 118 | 119 | Data.ddp.on("added", message => { 120 | if(!Data.db[message.collection]) { 121 | Data.db.addCollection(message.collection) 122 | } 123 | Data.db[message.collection].upsert({_id: message.id, ...message.fields}); 124 | }); 125 | 126 | Data.ddp.on("ready", message => { 127 | 128 | for(var i in Data.subscriptions) { 129 | const sub = Data.subscriptions[i]; 130 | sub.ready = true; 131 | sub.readyDeps.changed(); 132 | sub.readyCallback && sub.readyCallback(); 133 | } 134 | 135 | }); 136 | 137 | Data.ddp.on("changed", message => { 138 | Data.db[message.collection].upsert({_id: message.id, ...message.fields}); 139 | }); 140 | 141 | Data.ddp.on("removed", message => { 142 | Data.db[message.collection].del(message.id); 143 | }); 144 | Data.ddp.on("result", message => { 145 | const call = Data.calls.find(call=>call.id==message.id); 146 | if(typeof call.callback == 'function') call.callback(message.error, message.result); 147 | Data.calls.splice(Data.calls.findIndex(call=>call.id==message.id), 1); 148 | }); 149 | 150 | Data.ddp.on("nosub", message => { 151 | for(var i in Data.subscriptions) { 152 | const sub = Data.subscriptions[i]; 153 | if(sub.subIdRemember == message.id) { 154 | console.warn("No subscription existing for", sub.name); 155 | } 156 | } 157 | }); 158 | 159 | }, 160 | subscribe(name) { 161 | var params = Array.prototype.slice.call(arguments, 1); 162 | var callbacks = {}; 163 | if (params.length) { 164 | var lastParam = params[params.length - 1]; 165 | if (typeof lastParam == 'function') { 166 | callbacks.onReady = params.pop(); 167 | } else if (lastParam && (typeof lastParam.onReady == 'function' || typeof lastParam.onError == 'function' || typeof lastParam.onStop == 'function')) { 168 | callbacks = params.pop(); 169 | } 170 | } 171 | 172 | // Is there an existing sub with the same name and param, run in an 173 | // invalidated Computation? This will happen if we are rerunning an 174 | // existing computation. 175 | // 176 | // For example, consider a rerun of: 177 | // 178 | // Tracker.autorun(function () { 179 | // Meteor.subscribe("foo", Session.get("foo")); 180 | // Meteor.subscribe("bar", Session.get("bar")); 181 | // }); 182 | // 183 | // If "foo" has changed but "bar" has not, we will match the "bar" 184 | // subcribe to an existing inactive subscription in order to not 185 | // unsub and resub the subscription unnecessarily. 186 | // 187 | // We only look for one such sub; if there are N apparently-identical subs 188 | // being invalidated, we will require N matching subscribe calls to keep 189 | // them all active. 190 | 191 | 192 | 193 | let existing = false; 194 | for(var i in Data.subscriptions) { 195 | const sub = Data.subscriptions[i]; 196 | if(sub.inactive && sub.name === name && EJSON.equals(sub.params, params)) existing = sub; 197 | } 198 | 199 | let id; 200 | if (existing) { 201 | id = existing.id; 202 | existing.inactive = false; 203 | 204 | if (callbacks.onReady) { 205 | // If the sub is not already ready, replace any ready callback with the 206 | // one provided now. (It's not really clear what users would expect for 207 | // an onReady callback inside an autorun; the semantics we provide is 208 | // that at the time the sub first becomes ready, we call the last 209 | // onReady callback provided, if any.) 210 | if (!existing.ready) 211 | existing.readyCallback = callbacks.onReady; 212 | } 213 | if (callbacks.onStop) { 214 | existing.stopCallback = callbacks.onStop; 215 | } 216 | 217 | } else { 218 | 219 | // New sub! Generate an id, save it locally, and send message. 220 | 221 | id = Random.id(); 222 | const subIdRemember = Data.ddp.sub(name, params); 223 | 224 | Data.subscriptions[id] = { 225 | id: id, 226 | subIdRemember: subIdRemember, 227 | name: name, 228 | params: EJSON.clone(params), 229 | inactive: false, 230 | ready: false, 231 | readyDeps: new Trackr.Dependency, 232 | readyCallback: callbacks.onReady, 233 | stopCallback: callbacks.onStop, 234 | stop: function() { 235 | Data.ddp.unsub(this.subIdRemember); 236 | delete Data.subscriptions[this.id]; 237 | this.ready && this.readyDeps.changed(); 238 | 239 | if (callbacks.onStop) { 240 | callbacks.onStop(); 241 | } 242 | } 243 | }; 244 | 245 | } 246 | 247 | 248 | // return a handle to the application. 249 | var handle = { 250 | stop: function () { 251 | if(Data.subscriptions[id]) 252 | Data.subscriptions[id].stop(); 253 | }, 254 | ready: function () { 255 | if (!Data.subscriptions[id]) return false; 256 | 257 | var record = Data.subscriptions[id]; 258 | record.readyDeps.depend(); 259 | return record.ready; 260 | }, 261 | subscriptionId: id 262 | }; 263 | 264 | if (Trackr.active) { 265 | // We're in a reactive computation, so we'd like to unsubscribe when the 266 | // computation is invalidated... but not if the rerun just re-subscribes 267 | // to the same subscription! When a rerun happens, we use onInvalidate 268 | // as a change to mark the subscription "inactive" so that it can 269 | // be reused from the rerun. If it isn't reused, it's killed from 270 | // an afterFlush. 271 | Trackr.onInvalidate(function (c) { 272 | if(Data.subscriptions[id]) { 273 | Data.subscriptions[id].inactive = true; 274 | } 275 | 276 | Trackr.afterFlush(function () { 277 | if (Data.subscriptions[id] && Data.subscriptions[id].inactive) { 278 | handle.stop(); 279 | } 280 | }); 281 | }); 282 | } 283 | 284 | return handle; 285 | 286 | } 287 | } 288 | 289 | 290 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # electron-meteor [![electron-meteor](http://img.shields.io/npm/dm/electron-meteor.svg)](https://www.npmjs.org/package/electron-meteor) [![npm version](https://badge.fury.io/js/electron-meteor.svg)](http://badge.fury.io/js/electron-meteor) [![Dependency Status](https://david-dm.org/inProgress-team/electron-meteor.svg)](https://david-dm.org/inProgress-team/electron-meteor) 2 | 3 | Meteor-like methods for React Native. 4 | 5 | ## What is it for ? 6 | 7 | The purpose of this library is : 8 | * to set up and maintain a ddp connection with a ddp server, freeing the developer from having to do it on their own. 9 | * be fully compatible with react-native and help react-native developers. 10 | * **to match with [Meteor documentation](http://docs.meteor.com/) used with React.** 11 | 12 | ## Install 13 | 14 | npm i --save electron-meteor@latest 15 | 16 | [!! See detailed installation guide](https://github.com/inProgress-team/electron-meteor/blob/master/docs/Install.md) 17 | 18 | ## Example usage 19 | 20 | ```javascript 21 | 22 | import { View, Text, Component } from 'react-native'; 23 | import Meteor, { createContainer } from 'electron-meteor'; 24 | 25 | Meteor.connect('http://192.168.X.X:3000/websocket');//do this only once 26 | 27 | class App extends Component { 28 | renderRow(todo) { 29 | return ( 30 | {todo.title} 31 | ); 32 | } 33 | render() { 34 | const { settings, todosReady } = this.data; 35 | 36 | 37 | {settings.title} 38 | {!todosReady && Not ready} 39 | 40 | 46 | 47 | 48 | } 49 | } 50 | 51 | export default createContainer(params=>{ 52 | const handle = Meteor.subscribe('todos'); 53 | Meteor.subscribe('settings'); 54 | 55 | return { 56 | todosReady: handle.ready(), 57 | settings: Meteor.collection('settings').findOne() 58 | }; 59 | }) 60 | ``` 61 | 62 | # createContainer 63 | 64 | [Since Meteor 1.3, createContainer is the recommended way to go to populate your React Classes](http://guide.meteor.com/v1.3/react.html#using-createContainer). Very similar to getMeteorData but your separate container components from presentational components. 65 | 66 | ## Example 67 | 68 | ```javascript 69 | import Meteor, { createContainer } from 'electron-meteor'; 70 | 71 | 72 | class Orders extends Component { 73 | render() { 74 | const { pendingOrders } = this.props; 75 | 76 | //... 77 | ); 78 | } 79 | } 80 | 81 | export default createContainer(params=>{ 82 | return { 83 | pendingOrders: Meteor.collection('orders').find({status: "pending"}), 84 | }; 85 | }, Orders) 86 | ``` 87 | 88 | # connectMeteor && getMeteorData 89 | 90 | connectMeteor is a React Mixin which enables getMeteorData (the old way of populating meteor data into your components). 91 | 92 | ## Example 93 | 94 | ```javascript 95 | import Meteor, { connectMeteor } from 'electron-meteor'; 96 | 97 | /* 98 | * Uses decorators (see detailed installation to activate it) 99 | * Or use : 100 | 101 | class Todos extends Component { 102 | ... 103 | } 104 | connectMeteor(Todos); 105 | export default Todos; 106 | 107 | */ 108 | 109 | @connectMeteor 110 | class Orders extends Component { 111 | getMeteorData() { 112 | return { 113 | pendingOrders: Meteor.collection('orders').find({status: "pending"}), 114 | }; 115 | } 116 | render() { 117 | const { pendingOrders } = this.props; 118 | 119 | //... 120 | ); 121 | } 122 | } 123 | ``` 124 | 125 | # Reactive variables 126 | 127 | These variables can be used inside getMeteorData or createContainer. They will be populated into your component if they change. 128 | 129 | * [Meteor.subscribe](http://docs.meteor.com/#/full/meteor_subscribe) : returns an handle. !! If the component which called subscribe is unmounted, the subscription is automatically canceled. 130 | * Meteor.collection(collectionName) 131 | * [.find(selector, options)](http://docs.meteor.com/#/full/find) 132 | * [.findOne(selector, options)](http://docs.meteor.com/#/full/findone) 133 | * [Meteor.user()](http://docs.meteor.com/#/full/meteor_user) 134 | * [Meteor.userId()](http://docs.meteor.com/#/full/meteor_userid) 135 | * [Meteor.status()](http://docs.meteor.com/#/full/meteor_status) 136 | * [Meteor.loggingIn()](http://docs.meteor.com/#/full/meteor_loggingin) 137 | 138 | # Additionals collection methods 139 | 140 | These methods (except update) work offline. That means that elements are correctly updated offline, and when you reconnect to ddp, Meteor calls are taken care of. 141 | 142 | * Meteor.collection(collectionName) 143 | * [.insert(doc, callback)](http://docs.meteor.com/#/full/insert) 144 | * [.update(id, modifier, [options], [callback])](http://docs.meteor.com/#/full/update) 145 | * [.remove(id, callback(err, countRemoved))](http://docs.meteor.com/#/full/remove) 146 | 147 | 148 | # MeteorListView Component 149 | 150 | Same as [ListView](https://facebook.github.io/react-native/docs/listview.html) Component but does not need dataSource and accepts three arguments : 151 | 152 | - `collection` **string** *required* 153 | - `selector` [**string** / **object**] 154 | - `options` **object** 155 | - `listViewRef` [**string** / **function**] ref to ListView component. 156 | 157 | 158 | ### Example usage 159 | 160 | ```javascript 161 | 168 | ``` 169 | 170 | # MeteorComplexListView Component 171 | 172 | Same as [ListView](https://facebook.github.io/react-native/docs/listview.html) Component but does not need dataSource and accepts one argument. You may need it if you make complex requests combining multiples collections. 173 | 174 | - `elements` **function** *required* : a reactive function which returns an array of elements. 175 | - `listViewRef` [**string** / **function**] ref to ListView component. 176 | 177 | ### Example usage 178 | 179 | ```javascript 180 | {return Meteor.collection('todos').find()}} 182 | renderRow={this.renderItem} 183 | //...other listview props 184 | /> 185 | ``` 186 | 187 | # API 188 | 189 | ## Meteor DDP connection 190 | 191 | #### Meteor.connect(endpoint, options) 192 | 193 | Connect to a DDP server. You only have to do this once in your app. 194 | 195 | #### Arguments 196 | 197 | - `url` **string** *required* 198 | - `options` **object** Available options are : 199 | - autoConnect **boolean** [true] whether to establish the connection to the server upon instantiation. When false, one can manually establish the connection with the Meteor.ddp.connect method. 200 | - autoReconnect **boolean** [true] whether to try to reconnect to the server when the socket connection closes, unless the closing was initiated by a call to the disconnect method. 201 | - reconnectInterval **number** [10000] the interval in ms between reconnection attempts. 202 | 203 | #### Meteor.disconnect() 204 | 205 | Disconnect from the DDP server. 206 | 207 | ## Meteor methods 208 | 209 | * [Meteor.call](http://docs.meteor.com/#/full/meteor_call) 210 | * [Meteor.loginWithPassword](http://docs.meteor.com/#/full/meteor_loginwithpassword) (Please note that user is auto-resigned in - like in Meteor Web applications - thanks to React Native AsyncStorage.) 211 | * [Meteor.logout](http://docs.meteor.com/#/full/meteor_logout) 212 | * [Meteor.logoutOtherClients](http://docs.meteor.com/#/full/meteor_logoutotherclients) 213 | 214 | ## Meteor.Accounts 215 | 216 | * [Accounts.createUser](http://docs.meteor.com/#/full/accounts_createuser) 217 | * [Accounts.changePassword](http://docs.meteor.com/#/full/accounts_forgotpassword) 218 | * [Accounts.forgotPassword](http://docs.meteor.com/#/full/accounts_changepassword) 219 | * [Accounts.onLogin](http://docs.meteor.com/#/full/accounts_onlogin) 220 | * [Accounts.onLoginFailure](http://docs.meteor.com/#/full/accounts_onloginfailure) 221 | 222 | ## Meteor.ddp 223 | 224 | Once connected to the ddp server, you can access every method available in [ddp.js](https://github.com/mondora/ddp.js/). 225 | * Meteor.ddp.on('connected') 226 | * Meteor.ddp.on('added') 227 | * Meteor.ddp.on('changed') 228 | * ... 229 | 230 | ## CollectionFS 231 | 232 | * Meteor.FSCollection(collectionName) : Helper for [Meteor-CollectionFS](https://github.com/CollectionFS/Meteor-CollectionFS). Full documentation [here](https://github.com/inProgress-team/electron-meteor/blob/master/docs/FSCollection.md) 233 | * This plugin also exposes a FSCollectionImagesPreloader component which helps you preload every image you want in CollectionFS (only available on ios) 234 | 235 | ```javascript 236 | import { FSCollectionImagesPreloader } from 'electron-meteor'; 237 | 238 | 242 | ``` 243 | 244 | 245 | ## react-native-router-flux 246 | 247 | * [Github repository](https://github.com/inProgress-team/electron-meteor-router-flux) 248 | * npm i --save electron-meteor-router-flux@latest 249 | * [Custom scene renderer](https://github.com/aksonov/react-native-router-flux#switch-new-feature) which allows to select tab scene to show depending from app state. It could be useful for authentication, restricted scenes, etc. 250 | 251 | 252 | # Want to help ? 253 | 254 | Pull Requests and issues reported are welcome ! :) 255 | -------------------------------------------------------------------------------- /dist/src/components/Mixin.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | 7 | var _typeof2 = require('babel-runtime/helpers/typeof'); 8 | 9 | var _typeof3 = _interopRequireDefault(_typeof2); 10 | 11 | var _classCallCheck2 = require('babel-runtime/helpers/classCallCheck'); 12 | 13 | var _classCallCheck3 = _interopRequireDefault(_classCallCheck2); 14 | 15 | var _createClass2 = require('babel-runtime/helpers/createClass'); 16 | 17 | var _createClass3 = _interopRequireDefault(_createClass2); 18 | 19 | var _trackr = require('trackr'); 20 | 21 | var _trackr2 = _interopRequireDefault(_trackr); 22 | 23 | var _ejson = require('ejson'); 24 | 25 | var _ejson2 = _interopRequireDefault(_ejson); 26 | 27 | var _Data = require('../Data'); 28 | 29 | var _Data2 = _interopRequireDefault(_Data); 30 | 31 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } 32 | 33 | exports.default = { 34 | componentWillMount: function componentWillMount() { 35 | var _this = this; 36 | 37 | _Data2.default.waitDdpReady(function () { 38 | if (_this.getMeteorData) { 39 | _this.data = {}; 40 | _this._meteorDataManager = new MeteorDataManager(_this); 41 | var newData = _this._meteorDataManager.calculateData(); 42 | _this._meteorDataManager.updateData(newData); 43 | } 44 | 45 | if (_this.startMeteorSubscriptions) { 46 | console.warn('startMeteorSubscriptions is deprecated and will be removed soon. Please create your subscriptions in getMeteorData.'); 47 | _this._meteorSubscriptionsManager = new MeteorSubscriptionsManager(_this); 48 | _this._meteorSubscriptionsManager.getMeteorSubscriptions(); 49 | } 50 | }); 51 | }, 52 | componentWillUpdate: function componentWillUpdate(nextProps, nextState) { 53 | 54 | if (this.startMeteorSubscriptions) { 55 | if (!_ejson2.default.equals(this.state, nextState) || !_ejson2.default.equals(this.props, nextProps)) { 56 | this._meteorSubscriptionsManager._meteorDataChangedCallback(); 57 | } 58 | } 59 | 60 | if (this.getMeteorData) { 61 | var saveProps = this.props; 62 | var saveState = this.state; 63 | var newData = void 0; 64 | try { 65 | // Temporarily assign this.state and this.props, 66 | // so that they are seen by getMeteorData! 67 | // This is a simulation of how the proposed Observe API 68 | // for React will work, which calls observe() after 69 | // componentWillUpdate and after props and state are 70 | // updated, but before render() is called. 71 | // See https://github.com/facebook/react/issues/3398. 72 | this.props = nextProps; 73 | this.state = nextState; 74 | newData = this._meteorDataManager.calculateData(); 75 | } finally { 76 | this.props = saveProps; 77 | this.state = saveState; 78 | } 79 | 80 | this._meteorDataManager.updateData(newData); 81 | } 82 | }, 83 | componentWillUnmount: function componentWillUnmount() { 84 | if (this._meteorDataManager) { 85 | this._meteorDataManager.dispose(); 86 | } 87 | 88 | if (this._meteorSubscriptionsManager) { 89 | this._meteorSubscriptionsManager.dispose(); 90 | } 91 | } 92 | }; 93 | 94 | var MeteorSubscriptionsManager = function () { 95 | function MeteorSubscriptionsManager(component) { 96 | var _this2 = this; 97 | 98 | (0, _classCallCheck3.default)(this, MeteorSubscriptionsManager); 99 | 100 | this.component = component; 101 | this.computation = null; 102 | 103 | this._meteorSubscriptionsDep = new _trackr2.default.Dependency(); 104 | 105 | this._meteorDataChangedCallback = function () { 106 | _this2._meteorSubscriptionsDep.changed(); 107 | }; 108 | 109 | _Data2.default.onChange(this._meteorDataChangedCallback); 110 | } 111 | 112 | (0, _createClass3.default)(MeteorSubscriptionsManager, [{ 113 | key: 'dispose', 114 | value: function dispose() { 115 | if (this.computation) { 116 | this.computation.stop(); 117 | this.computation = null; 118 | } 119 | 120 | _Data2.default.offChange(this._meteorDataChangedCallback); 121 | } 122 | }, { 123 | key: 'stateOrPropsChanged', 124 | value: function stateOrPropsChanged() {} 125 | }, { 126 | key: 'getMeteorSubscriptions', 127 | value: function getMeteorSubscriptions() { 128 | var _this3 = this; 129 | 130 | this.computation = _trackr2.default.nonreactive(function () { 131 | return _trackr2.default.autorun(function (c) { 132 | _this3._meteorSubscriptionsDep.depend(); 133 | 134 | _this3.component.startMeteorSubscriptions(); 135 | }); 136 | }); 137 | } 138 | }]); 139 | return MeteorSubscriptionsManager; 140 | }(); 141 | 142 | // A class to keep the state and utility methods needed to manage 143 | // the Meteor data for a component. 144 | 145 | 146 | var MeteorDataManager = function () { 147 | function MeteorDataManager(component) { 148 | var _this4 = this; 149 | 150 | (0, _classCallCheck3.default)(this, MeteorDataManager); 151 | 152 | this.component = component; 153 | this.computation = null; 154 | this.oldData = null; 155 | this._meteorDataDep = new _trackr2.default.Dependency(); 156 | 157 | this._meteorDataChangedCallback = function () { 158 | _this4._meteorDataDep.changed(); 159 | }; 160 | 161 | _Data2.default.onChange(this._meteorDataChangedCallback); 162 | } 163 | 164 | (0, _createClass3.default)(MeteorDataManager, [{ 165 | key: 'dispose', 166 | value: function dispose() { 167 | if (this.computation) { 168 | this.computation.stop(); 169 | this.computation = null; 170 | } 171 | 172 | _Data2.default.offChange(this._meteorDataChangedCallback); 173 | } 174 | }, { 175 | key: 'calculateData', 176 | value: function calculateData() { 177 | var _this5 = this; 178 | 179 | var component = this.component; 180 | 181 | if (!component.getMeteorData) { 182 | return null; 183 | } 184 | 185 | if (this.computation) { 186 | this.computation.stop(); 187 | this.computation = null; 188 | } 189 | 190 | var data = void 0; 191 | // Use Tracker.nonreactive in case we are inside a Tracker Computation. 192 | // This can happen if someone calls `ReactDOM.render` inside a Computation. 193 | // In that case, we want to opt out of the normal behavior of nested 194 | // Computations, where if the outer one is invalidated or stopped, 195 | // it stops the inner one. 196 | 197 | this.computation = _trackr2.default.nonreactive(function () { 198 | return _trackr2.default.autorun(function (c) { 199 | _this5._meteorDataDep.depend(); 200 | if (c.firstRun) { 201 | var savedSetState = component.setState; 202 | try { 203 | component.setState = function () { 204 | throw new Error("Can't call `setState` inside `getMeteorData` as this could cause an endless" + " loop. To respond to Meteor data changing, consider making this component" + " a \"wrapper component\" that only fetches data and passes it in as props to" + " a child component. Then you can use `componentWillReceiveProps` in that" + " child component."); 205 | }; 206 | 207 | data = component.getMeteorData(); 208 | } finally { 209 | component.setState = savedSetState; 210 | } 211 | } else { 212 | // Stop this computation instead of using the re-run. 213 | // We use a brand-new autorun for each call to getMeteorData 214 | // to capture dependencies on any reactive data sources that 215 | // are accessed. The reason we can't use a single autorun 216 | // for the lifetime of the component is that Tracker only 217 | // re-runs autoruns at flush time, while we need to be able to 218 | // re-call getMeteorData synchronously whenever we want, e.g. 219 | // from componentWillUpdate. 220 | c.stop(); 221 | // Calling forceUpdate() triggers componentWillUpdate which 222 | // recalculates getMeteorData() and re-renders the component. 223 | component.forceUpdate(); 224 | } 225 | }); 226 | }); 227 | 228 | return data; 229 | } 230 | }, { 231 | key: 'updateData', 232 | value: function updateData(newData) { 233 | var component = this.component; 234 | var oldData = this.oldData; 235 | 236 | if (!(newData && (typeof newData === 'undefined' ? 'undefined' : (0, _typeof3.default)(newData)) === 'object')) { 237 | throw new Error("Expected object returned from getMeteorData"); 238 | } 239 | // update componentData in place based on newData 240 | for (var key in newData) { 241 | component.data[key] = newData[key]; 242 | } 243 | // if there is oldData (which is every time this method is called 244 | // except the first), delete keys in newData that aren't in 245 | // oldData. don't interfere with other keys, in case we are 246 | // co-existing with something else that writes to a component's 247 | // this.data. 248 | if (oldData) { 249 | for (var _key in oldData) { 250 | if (!(_key in newData)) { 251 | delete component.data[_key]; 252 | } 253 | } 254 | } 255 | this.oldData = newData; 256 | } 257 | }]); 258 | return MeteorDataManager; 259 | }(); -------------------------------------------------------------------------------- /dist/components/Mixin.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | 7 | var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { return typeof obj; } : function (obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol ? "symbol" : typeof obj; }; 8 | 9 | var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); 10 | 11 | var _trackr = require('trackr'); 12 | 13 | var _trackr2 = _interopRequireDefault(_trackr); 14 | 15 | var _ejson = require('ejson'); 16 | 17 | var _ejson2 = _interopRequireDefault(_ejson); 18 | 19 | var _Data = require('../Data'); 20 | 21 | var _Data2 = _interopRequireDefault(_Data); 22 | 23 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } 24 | 25 | function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } 26 | 27 | exports.default = { 28 | componentWillMount: function componentWillMount() { 29 | var _this = this; 30 | 31 | _Data2.default.waitDdpReady(function () { 32 | if (_this.getMeteorData) { 33 | _this.data = {}; 34 | _this._meteorDataManager = new MeteorDataManager(_this); 35 | var newData = _this._meteorDataManager.calculateData(); 36 | _this._meteorDataManager.updateData(newData); 37 | } 38 | 39 | if (_this.startMeteorSubscriptions) { 40 | console.warn('startMeteorSubscriptions is deprecated and will be removed soon. Please create your subscriptions in getMeteorData.'); 41 | _this._meteorSubscriptionsManager = new MeteorSubscriptionsManager(_this); 42 | _this._meteorSubscriptionsManager.getMeteorSubscriptions(); 43 | } 44 | }); 45 | }, 46 | componentWillUpdate: function componentWillUpdate(nextProps, nextState) { 47 | 48 | if (this.startMeteorSubscriptions) { 49 | if (!_ejson2.default.equals(this.state, nextState) || !_ejson2.default.equals(this.props, nextProps)) { 50 | this._meteorSubscriptionsManager._meteorDataChangedCallback(); 51 | } 52 | } 53 | 54 | if (this.getMeteorData) { 55 | var saveProps = this.props; 56 | var saveState = this.state; 57 | var newData = void 0; 58 | try { 59 | // Temporarily assign this.state and this.props, 60 | // so that they are seen by getMeteorData! 61 | // This is a simulation of how the proposed Observe API 62 | // for React will work, which calls observe() after 63 | // componentWillUpdate and after props and state are 64 | // updated, but before render() is called. 65 | // See https://github.com/facebook/react/issues/3398. 66 | this.props = nextProps; 67 | this.state = nextState; 68 | newData = this._meteorDataManager.calculateData(); 69 | } finally { 70 | this.props = saveProps; 71 | this.state = saveState; 72 | } 73 | 74 | this._meteorDataManager.updateData(newData); 75 | } 76 | }, 77 | componentWillUnmount: function componentWillUnmount() { 78 | if (this._meteorDataManager) { 79 | this._meteorDataManager.dispose(); 80 | } 81 | 82 | if (this._meteorSubscriptionsManager) { 83 | this._meteorSubscriptionsManager.dispose(); 84 | } 85 | } 86 | }; 87 | 88 | var MeteorSubscriptionsManager = function () { 89 | function MeteorSubscriptionsManager(component) { 90 | var _this2 = this; 91 | 92 | _classCallCheck(this, MeteorSubscriptionsManager); 93 | 94 | this.component = component; 95 | this.computation = null; 96 | 97 | this._meteorSubscriptionsDep = new _trackr2.default.Dependency(); 98 | 99 | this._meteorDataChangedCallback = function () { 100 | _this2._meteorSubscriptionsDep.changed(); 101 | }; 102 | 103 | _Data2.default.onChange(this._meteorDataChangedCallback); 104 | } 105 | 106 | _createClass(MeteorSubscriptionsManager, [{ 107 | key: 'dispose', 108 | value: function dispose() { 109 | if (this.computation) { 110 | this.computation.stop(); 111 | this.computation = null; 112 | } 113 | 114 | _Data2.default.offChange(this._meteorDataChangedCallback); 115 | } 116 | }, { 117 | key: 'stateOrPropsChanged', 118 | value: function stateOrPropsChanged() {} 119 | }, { 120 | key: 'getMeteorSubscriptions', 121 | value: function getMeteorSubscriptions() { 122 | var _this3 = this; 123 | 124 | this.computation = _trackr2.default.nonreactive(function () { 125 | return _trackr2.default.autorun(function (c) { 126 | _this3._meteorSubscriptionsDep.depend(); 127 | 128 | _this3.component.startMeteorSubscriptions(); 129 | }); 130 | }); 131 | } 132 | }]); 133 | 134 | return MeteorSubscriptionsManager; 135 | }(); 136 | 137 | // A class to keep the state and utility methods needed to manage 138 | // the Meteor data for a component. 139 | 140 | 141 | var MeteorDataManager = function () { 142 | function MeteorDataManager(component) { 143 | var _this4 = this; 144 | 145 | _classCallCheck(this, MeteorDataManager); 146 | 147 | this.component = component; 148 | this.computation = null; 149 | this.oldData = null; 150 | this._meteorDataDep = new _trackr2.default.Dependency(); 151 | 152 | this._meteorDataChangedCallback = function () { 153 | _this4._meteorDataDep.changed(); 154 | }; 155 | 156 | _Data2.default.onChange(this._meteorDataChangedCallback); 157 | } 158 | 159 | _createClass(MeteorDataManager, [{ 160 | key: 'dispose', 161 | value: function dispose() { 162 | if (this.computation) { 163 | this.computation.stop(); 164 | this.computation = null; 165 | } 166 | 167 | _Data2.default.offChange(this._meteorDataChangedCallback); 168 | } 169 | }, { 170 | key: 'calculateData', 171 | value: function calculateData() { 172 | var _this5 = this; 173 | 174 | var component = this.component; 175 | 176 | if (!component.getMeteorData) { 177 | return null; 178 | } 179 | 180 | if (this.computation) { 181 | this.computation.stop(); 182 | this.computation = null; 183 | } 184 | 185 | var data = void 0; 186 | // Use Tracker.nonreactive in case we are inside a Tracker Computation. 187 | // This can happen if someone calls `ReactDOM.render` inside a Computation. 188 | // In that case, we want to opt out of the normal behavior of nested 189 | // Computations, where if the outer one is invalidated or stopped, 190 | // it stops the inner one. 191 | 192 | this.computation = _trackr2.default.nonreactive(function () { 193 | return _trackr2.default.autorun(function (c) { 194 | _this5._meteorDataDep.depend(); 195 | if (c.firstRun) { 196 | var savedSetState = component.setState; 197 | try { 198 | component.setState = function () { 199 | throw new Error("Can't call `setState` inside `getMeteorData` as this could cause an endless" + " loop. To respond to Meteor data changing, consider making this component" + " a \"wrapper component\" that only fetches data and passes it in as props to" + " a child component. Then you can use `componentWillReceiveProps` in that" + " child component."); 200 | }; 201 | 202 | data = component.getMeteorData(); 203 | } finally { 204 | component.setState = savedSetState; 205 | } 206 | } else { 207 | // Stop this computation instead of using the re-run. 208 | // We use a brand-new autorun for each call to getMeteorData 209 | // to capture dependencies on any reactive data sources that 210 | // are accessed. The reason we can't use a single autorun 211 | // for the lifetime of the component is that Tracker only 212 | // re-runs autoruns at flush time, while we need to be able to 213 | // re-call getMeteorData synchronously whenever we want, e.g. 214 | // from componentWillUpdate. 215 | c.stop(); 216 | // Calling forceUpdate() triggers componentWillUpdate which 217 | // recalculates getMeteorData() and re-renders the component. 218 | component.forceUpdate(); 219 | } 220 | }); 221 | }); 222 | 223 | return data; 224 | } 225 | }, { 226 | key: 'updateData', 227 | value: function updateData(newData) { 228 | var component = this.component; 229 | var oldData = this.oldData; 230 | 231 | if (!(newData && (typeof newData === 'undefined' ? 'undefined' : _typeof(newData)) === 'object')) { 232 | throw new Error("Expected object returned from getMeteorData"); 233 | } 234 | // update componentData in place based on newData 235 | for (var key in newData) { 236 | component.data[key] = newData[key]; 237 | } 238 | // if there is oldData (which is every time this method is called 239 | // except the first), delete keys in newData that aren't in 240 | // oldData. don't interfere with other keys, in case we are 241 | // co-existing with something else that writes to a component's 242 | // this.data. 243 | if (oldData) { 244 | for (var _key in oldData) { 245 | if (!(_key in newData)) { 246 | delete component.data[_key]; 247 | } 248 | } 249 | } 250 | this.oldData = newData; 251 | } 252 | }]); 253 | 254 | return MeteorDataManager; 255 | }(); -------------------------------------------------------------------------------- /dist/Meteor.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; 4 | //import FSCollectionImagesPreloader from './CollectionFS/FSCollectionImagesPreloader'; 5 | 6 | var _reactNative = require('react-native'); 7 | 8 | var _reactMixin = require('react-mixin'); 9 | 10 | var _reactMixin2 = _interopRequireDefault(_reactMixin); 11 | 12 | var _trackr = require('trackr'); 13 | 14 | var _trackr2 = _interopRequireDefault(_trackr); 15 | 16 | var _ejson = require('ejson'); 17 | 18 | var _ejson2 = _interopRequireDefault(_ejson); 19 | 20 | var _ddp = require('../lib/ddp.js'); 21 | 22 | var _ddp2 = _interopRequireDefault(_ddp); 23 | 24 | var _Random = require('../lib/Random'); 25 | 26 | var _Random2 = _interopRequireDefault(_Random); 27 | 28 | var _Data = require('./Data'); 29 | 30 | var _Data2 = _interopRequireDefault(_Data); 31 | 32 | var _Collection = require('./Collection'); 33 | 34 | var _Collection2 = _interopRequireDefault(_Collection); 35 | 36 | var _Call = require('./Call'); 37 | 38 | var _Call2 = _interopRequireDefault(_Call); 39 | 40 | var _Mixin = require('./components/Mixin'); 41 | 42 | var _Mixin2 = _interopRequireDefault(_Mixin); 43 | 44 | var _createContainer = require('./components/createContainer'); 45 | 46 | var _createContainer2 = _interopRequireDefault(_createContainer); 47 | 48 | var _FSCollection = require('./CollectionFS/FSCollection'); 49 | 50 | var _FSCollection2 = _interopRequireDefault(_FSCollection); 51 | 52 | var _User = require('./user/User'); 53 | 54 | var _User2 = _interopRequireDefault(_User); 55 | 56 | var _Accounts = require('./user/Accounts'); 57 | 58 | var _Accounts2 = _interopRequireDefault(_Accounts); 59 | 60 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } 61 | 62 | module.exports = { 63 | Accounts: _Accounts2.default, 64 | //FSCollectionImagesPreloader: Platform.OS == 'android' ? View : FSCollectionImagesPreloader, 65 | collection: _Collection2.default, 66 | FSCollection: _FSCollection2.default, 67 | createContainer: _createContainer2.default, 68 | getData: function getData() { 69 | return _Data2.default; 70 | }, 71 | connectMeteor: function connectMeteor(reactClass) { 72 | return _reactMixin2.default.onClass(reactClass, _Mixin2.default); 73 | }, 74 | 75 | User: _User2.default, 76 | status: function status() { 77 | return { 78 | connected: _Data2.default.ddp ? _Data2.default.ddp.status == "connected" : false, 79 | status: _Data2.default.ddp ? _Data2.default.ddp.status : "disconnected" 80 | //retryCount: 0 81 | //retryTime: 82 | //reason: 83 | }; 84 | }, 85 | 86 | call: _Call2.default, 87 | disconnect: function disconnect() { 88 | if (_Data2.default.ddp) { 89 | _Data2.default.ddp.disconnect(); 90 | } 91 | }, 92 | _subscriptionsRestart: function _subscriptionsRestart() { 93 | 94 | for (var i in _Data2.default.subscriptions) { 95 | var sub = _Data2.default.subscriptions[i]; 96 | _Data2.default.ddp.unsub(sub.subIdRemember); 97 | sub.subIdRemember = _Data2.default.ddp.sub(sub.name, sub.params); 98 | } 99 | }, 100 | waitDdpConnected: function waitDdpConnected(cb) { 101 | var _this = this; 102 | 103 | if (_Data2.default.ddp && _Data2.default.ddp.status == 'connected') { 104 | cb(); 105 | } else if (_Data2.default.ddp) { 106 | _Data2.default.ddp.once('connected', cb); 107 | } else { 108 | setTimeout(function () { 109 | _this.waitDdpConnected(cb); 110 | }, 10); 111 | } 112 | }, 113 | reconnect: function reconnect() { 114 | _Data2.default.ddp && _Data2.default.ddp.connect(); 115 | }, 116 | connect: function connect(endpoint, options) { 117 | var _this2 = this; 118 | 119 | if (!endpoint) endpoint = _Data2.default._endpoint; 120 | if (!options) options = _Data2.default._options; 121 | 122 | _Data2.default._endpoint = endpoint; 123 | _Data2.default._options = options; 124 | 125 | this.ddp = _Data2.default.ddp = new _ddp2.default(_extends({ 126 | endpoint: endpoint, 127 | SocketConstructor: WebSocket 128 | }, options)); 129 | 130 | //TODO 131 | /* 132 | NetInfo.isConnected.addEventListener('change', isConnected=>{ 133 | if(isConnected) { 134 | Data.ddp.connect(); 135 | } 136 | }); 137 | */ 138 | 139 | _Data2.default.ddp.on("connected", function () { 140 | console.info("Connected to DDP server."); 141 | _this2._loadInitialUser(); 142 | 143 | _this2._subscriptionsRestart(); 144 | }); 145 | 146 | var lastDisconnect = null; 147 | _Data2.default.ddp.on("disconnected", function () { 148 | console.info("Disconnected from DDP server."); 149 | 150 | if (!lastDisconnect || new Date() - lastDisconnect > 3000) { 151 | _Data2.default.ddp.connect(); 152 | } 153 | 154 | lastDisconnect = new Date(); 155 | }); 156 | 157 | _Data2.default.ddp.on("added", function (message) { 158 | if (!_Data2.default.db[message.collection]) { 159 | _Data2.default.db.addCollection(message.collection); 160 | } 161 | _Data2.default.db[message.collection].upsert(_extends({ _id: message.id }, message.fields)); 162 | }); 163 | 164 | _Data2.default.ddp.on("ready", function (message) { 165 | 166 | for (var i in _Data2.default.subscriptions) { 167 | var sub = _Data2.default.subscriptions[i]; 168 | sub.ready = true; 169 | sub.readyDeps.changed(); 170 | sub.readyCallback && sub.readyCallback(); 171 | } 172 | }); 173 | 174 | _Data2.default.ddp.on("changed", function (message) { 175 | _Data2.default.db[message.collection].upsert(_extends({ _id: message.id }, message.fields)); 176 | }); 177 | 178 | _Data2.default.ddp.on("removed", function (message) { 179 | _Data2.default.db[message.collection].del(message.id); 180 | }); 181 | _Data2.default.ddp.on("result", function (message) { 182 | var call = _Data2.default.calls.find(function (call) { 183 | return call.id == message.id; 184 | }); 185 | if (typeof call.callback == 'function') call.callback(message.error, message.result); 186 | _Data2.default.calls.splice(_Data2.default.calls.findIndex(function (call) { 187 | return call.id == message.id; 188 | }), 1); 189 | }); 190 | 191 | _Data2.default.ddp.on("nosub", function (message) { 192 | for (var i in _Data2.default.subscriptions) { 193 | var sub = _Data2.default.subscriptions[i]; 194 | if (sub.subIdRemember == message.id) { 195 | console.warn("No subscription existing for", sub.name); 196 | } 197 | } 198 | }); 199 | }, 200 | subscribe: function subscribe(name) { 201 | var params = Array.prototype.slice.call(arguments, 1); 202 | var callbacks = {}; 203 | if (params.length) { 204 | var lastParam = params[params.length - 1]; 205 | if (typeof lastParam == 'function') { 206 | callbacks.onReady = params.pop(); 207 | } else if (lastParam && (typeof lastParam.onReady == 'function' || typeof lastParam.onError == 'function' || typeof lastParam.onStop == 'function')) { 208 | callbacks = params.pop(); 209 | } 210 | } 211 | 212 | // Is there an existing sub with the same name and param, run in an 213 | // invalidated Computation? This will happen if we are rerunning an 214 | // existing computation. 215 | // 216 | // For example, consider a rerun of: 217 | // 218 | // Tracker.autorun(function () { 219 | // Meteor.subscribe("foo", Session.get("foo")); 220 | // Meteor.subscribe("bar", Session.get("bar")); 221 | // }); 222 | // 223 | // If "foo" has changed but "bar" has not, we will match the "bar" 224 | // subcribe to an existing inactive subscription in order to not 225 | // unsub and resub the subscription unnecessarily. 226 | // 227 | // We only look for one such sub; if there are N apparently-identical subs 228 | // being invalidated, we will require N matching subscribe calls to keep 229 | // them all active. 230 | 231 | var existing = false; 232 | for (var i in _Data2.default.subscriptions) { 233 | var sub = _Data2.default.subscriptions[i]; 234 | if (sub.inactive && sub.name === name && _ejson2.default.equals(sub.params, params)) existing = sub; 235 | } 236 | 237 | var id = void 0; 238 | if (existing) { 239 | id = existing.id; 240 | existing.inactive = false; 241 | 242 | if (callbacks.onReady) { 243 | // If the sub is not already ready, replace any ready callback with the 244 | // one provided now. (It's not really clear what users would expect for 245 | // an onReady callback inside an autorun; the semantics we provide is 246 | // that at the time the sub first becomes ready, we call the last 247 | // onReady callback provided, if any.) 248 | if (!existing.ready) existing.readyCallback = callbacks.onReady; 249 | } 250 | if (callbacks.onStop) { 251 | existing.stopCallback = callbacks.onStop; 252 | } 253 | } else { 254 | 255 | // New sub! Generate an id, save it locally, and send message. 256 | 257 | id = _Random2.default.id(); 258 | var subIdRemember = _Data2.default.ddp.sub(name, params); 259 | 260 | _Data2.default.subscriptions[id] = { 261 | id: id, 262 | subIdRemember: subIdRemember, 263 | name: name, 264 | params: _ejson2.default.clone(params), 265 | inactive: false, 266 | ready: false, 267 | readyDeps: new _trackr2.default.Dependency(), 268 | readyCallback: callbacks.onReady, 269 | stopCallback: callbacks.onStop, 270 | stop: function stop() { 271 | _Data2.default.ddp.unsub(this.subIdRemember); 272 | delete _Data2.default.subscriptions[this.id]; 273 | this.ready && this.readyDeps.changed(); 274 | 275 | if (callbacks.onStop) { 276 | callbacks.onStop(); 277 | } 278 | } 279 | }; 280 | } 281 | 282 | // return a handle to the application. 283 | var handle = { 284 | stop: function stop() { 285 | if (_Data2.default.subscriptions[id]) _Data2.default.subscriptions[id].stop(); 286 | }, 287 | ready: function ready() { 288 | if (!_Data2.default.subscriptions[id]) return false; 289 | 290 | var record = _Data2.default.subscriptions[id]; 291 | record.readyDeps.depend(); 292 | return record.ready; 293 | }, 294 | subscriptionId: id 295 | }; 296 | 297 | if (_trackr2.default.active) { 298 | // We're in a reactive computation, so we'd like to unsubscribe when the 299 | // computation is invalidated... but not if the rerun just re-subscribes 300 | // to the same subscription! When a rerun happens, we use onInvalidate 301 | // as a change to mark the subscription "inactive" so that it can 302 | // be reused from the rerun. If it isn't reused, it's killed from 303 | // an afterFlush. 304 | _trackr2.default.onInvalidate(function (c) { 305 | if (_Data2.default.subscriptions[id]) { 306 | _Data2.default.subscriptions[id].inactive = true; 307 | } 308 | 309 | _trackr2.default.afterFlush(function () { 310 | if (_Data2.default.subscriptions[id] && _Data2.default.subscriptions[id].inactive) { 311 | handle.stop(); 312 | } 313 | }); 314 | }); 315 | } 316 | 317 | return handle; 318 | } 319 | }; -------------------------------------------------------------------------------- /dist/src/Meteor.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var _extends2 = require('babel-runtime/helpers/extends'); 4 | 5 | var _extends3 = _interopRequireDefault(_extends2); 6 | 7 | var _reactMixin = require('react-mixin'); 8 | 9 | var _reactMixin2 = _interopRequireDefault(_reactMixin); 10 | 11 | var _trackr = require('trackr'); 12 | 13 | var _trackr2 = _interopRequireDefault(_trackr); 14 | 15 | var _ejson = require('ejson'); 16 | 17 | var _ejson2 = _interopRequireDefault(_ejson); 18 | 19 | var _ddp = require('../lib/ddp.js'); 20 | 21 | var _ddp2 = _interopRequireDefault(_ddp); 22 | 23 | var _Random = require('../lib/Random'); 24 | 25 | var _Random2 = _interopRequireDefault(_Random); 26 | 27 | var _Data = require('./Data'); 28 | 29 | var _Data2 = _interopRequireDefault(_Data); 30 | 31 | var _Collection = require('./Collection'); 32 | 33 | var _Collection2 = _interopRequireDefault(_Collection); 34 | 35 | var _Call = require('./Call'); 36 | 37 | var _Call2 = _interopRequireDefault(_Call); 38 | 39 | var _Mixin = require('./components/Mixin'); 40 | 41 | var _Mixin2 = _interopRequireDefault(_Mixin); 42 | 43 | var _createContainer = require('./components/createContainer'); 44 | 45 | var _createContainer2 = _interopRequireDefault(_createContainer); 46 | 47 | var _FSCollection = require('./CollectionFS/FSCollection'); 48 | 49 | var _FSCollection2 = _interopRequireDefault(_FSCollection); 50 | 51 | var _FSCollectionImagesPreloader = require('./CollectionFS/FSCollectionImagesPreloader'); 52 | 53 | var _FSCollectionImagesPreloader2 = _interopRequireDefault(_FSCollectionImagesPreloader); 54 | 55 | var _User = require('./user/User'); 56 | 57 | var _User2 = _interopRequireDefault(_User); 58 | 59 | var _Accounts = require('./user/Accounts'); 60 | 61 | var _Accounts2 = _interopRequireDefault(_Accounts); 62 | 63 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } 64 | 65 | //import { NetInfo } from 'react-native'; 66 | 67 | module.exports = (0, _extends3.default)({ 68 | Accounts: _Accounts2.default, 69 | FSCollectionImagesPreloader: _FSCollectionImagesPreloader2.default, 70 | collection: _Collection2.default, 71 | FSCollection: _FSCollection2.default, 72 | createContainer: _createContainer2.default, 73 | getData: function getData() { 74 | return _Data2.default; 75 | }, 76 | connectMeteor: function connectMeteor(reactClass) { 77 | return _reactMixin2.default.onClass(reactClass, _Mixin2.default); 78 | } 79 | }, _User2.default, { 80 | status: function status() { 81 | return { 82 | connected: _Data2.default.ddp ? _Data2.default.ddp.status == "connected" : false, 83 | status: _Data2.default.ddp ? _Data2.default.ddp.status : "disconnected" 84 | //retryCount: 0 85 | //retryTime: 86 | //reason: 87 | }; 88 | }, 89 | 90 | call: _Call2.default, 91 | disconnect: function disconnect() { 92 | if (_Data2.default.ddp) { 93 | _Data2.default.ddp.disconnect(); 94 | } 95 | }, 96 | _subscriptionsRestart: function _subscriptionsRestart() { 97 | 98 | for (var i in _Data2.default.subscriptions) { 99 | var sub = _Data2.default.subscriptions[i]; 100 | _Data2.default.ddp.unsub(sub.subIdRemember); 101 | sub.subIdRemember = _Data2.default.ddp.sub(sub.name, sub.params); 102 | } 103 | }, 104 | waitDdpConnected: function waitDdpConnected(cb) { 105 | var _this = this; 106 | 107 | if (_Data2.default.ddp && _Data2.default.ddp.status == 'connected') { 108 | cb(); 109 | } else if (_Data2.default.ddp) { 110 | _Data2.default.ddp.once('connected', cb); 111 | } else { 112 | setTimeout(function () { 113 | _this.waitDdpConnected(cb); 114 | }, 10); 115 | } 116 | }, 117 | reconnect: function reconnect() { 118 | _Data2.default.ddp && _Data2.default.ddp.connect(); 119 | }, 120 | connect: function connect(endpoint, options) { 121 | var _this2 = this; 122 | 123 | if (!endpoint) endpoint = _Data2.default._endpoint; 124 | if (!options) options = _Data2.default._options; 125 | 126 | _Data2.default._endpoint = endpoint; 127 | _Data2.default._options = options; 128 | 129 | this.ddp = _Data2.default.ddp = new _ddp2.default((0, _extends3.default)({ 130 | endpoint: endpoint, 131 | SocketConstructor: WebSocket 132 | }, options)); 133 | 134 | this.subscribe('_roles'); 135 | //TODO 136 | /* 137 | NetInfo.isConnected.addEventListener('change', isConnected=>{ 138 | if(isConnected) { 139 | Data.ddp.connect(); 140 | } 141 | }); 142 | */ 143 | 144 | _Data2.default.ddp.on("connected", function () { 145 | console.info("Connected to DDP server."); 146 | _this2._loadInitialUser(); 147 | 148 | _this2._subscriptionsRestart(); 149 | }); 150 | 151 | var lastDisconnect = null; 152 | _Data2.default.ddp.on("disconnected", function () { 153 | console.info("Disconnected from DDP server."); 154 | 155 | if (!lastDisconnect || new Date() - lastDisconnect > 3000) { 156 | _Data2.default.ddp.connect(); 157 | } 158 | 159 | lastDisconnect = new Date(); 160 | }); 161 | 162 | _Data2.default.ddp.on("added", function (message) { 163 | if (!_Data2.default.db[message.collection]) { 164 | _Data2.default.db.addCollection(message.collection); 165 | } 166 | _Data2.default.db[message.collection].upsert((0, _extends3.default)({ _id: message.id }, message.fields)); 167 | }); 168 | 169 | _Data2.default.ddp.on("ready", function (message) { 170 | 171 | for (var i in _Data2.default.subscriptions) { 172 | var sub = _Data2.default.subscriptions[i]; 173 | sub.ready = true; 174 | sub.readyDeps.changed(); 175 | sub.readyCallback && sub.readyCallback(); 176 | } 177 | }); 178 | 179 | _Data2.default.ddp.on("changed", function (message) { 180 | _Data2.default.db[message.collection].upsert((0, _extends3.default)({ _id: message.id }, message.fields)); 181 | }); 182 | 183 | _Data2.default.ddp.on("removed", function (message) { 184 | _Data2.default.db[message.collection].del(message.id); 185 | }); 186 | _Data2.default.ddp.on("result", function (message) { 187 | var call = _Data2.default.calls.find(function (call) { 188 | return call.id == message.id; 189 | }); 190 | if (typeof call.callback == 'function') call.callback(message.error, message.result); 191 | _Data2.default.calls.splice(_Data2.default.calls.findIndex(function (call) { 192 | return call.id == message.id; 193 | }), 1); 194 | }); 195 | 196 | _Data2.default.ddp.on("nosub", function (message) { 197 | for (var i in _Data2.default.subscriptions) { 198 | var sub = _Data2.default.subscriptions[i]; 199 | if (sub.subIdRemember == message.id) { 200 | console.warn("No subscription existing for", sub.name); 201 | } 202 | } 203 | }); 204 | }, 205 | subscribe: function subscribe(name) { 206 | var params = Array.prototype.slice.call(arguments, 1); 207 | var callbacks = {}; 208 | if (params.length) { 209 | var lastParam = params[params.length - 1]; 210 | if (typeof lastParam == 'function') { 211 | callbacks.onReady = params.pop(); 212 | } else if (lastParam && (typeof lastParam.onReady == 'function' || typeof lastParam.onError == 'function' || typeof lastParam.onStop == 'function')) { 213 | callbacks = params.pop(); 214 | } 215 | } 216 | 217 | // Is there an existing sub with the same name and param, run in an 218 | // invalidated Computation? This will happen if we are rerunning an 219 | // existing computation. 220 | // 221 | // For example, consider a rerun of: 222 | // 223 | // Tracker.autorun(function () { 224 | // Meteor.subscribe("foo", Session.get("foo")); 225 | // Meteor.subscribe("bar", Session.get("bar")); 226 | // }); 227 | // 228 | // If "foo" has changed but "bar" has not, we will match the "bar" 229 | // subcribe to an existing inactive subscription in order to not 230 | // unsub and resub the subscription unnecessarily. 231 | // 232 | // We only look for one such sub; if there are N apparently-identical subs 233 | // being invalidated, we will require N matching subscribe calls to keep 234 | // them all active. 235 | 236 | var existing = false; 237 | for (var i in _Data2.default.subscriptions) { 238 | var sub = _Data2.default.subscriptions[i]; 239 | if (sub.inactive && sub.name === name && _ejson2.default.equals(sub.params, params)) existing = sub; 240 | } 241 | 242 | var id = void 0; 243 | if (existing) { 244 | id = existing.id; 245 | existing.inactive = false; 246 | 247 | if (callbacks.onReady) { 248 | // If the sub is not already ready, replace any ready callback with the 249 | // one provided now. (It's not really clear what users would expect for 250 | // an onReady callback inside an autorun; the semantics we provide is 251 | // that at the time the sub first becomes ready, we call the last 252 | // onReady callback provided, if any.) 253 | if (!existing.ready) existing.readyCallback = callbacks.onReady; 254 | } 255 | if (callbacks.onStop) { 256 | existing.stopCallback = callbacks.onStop; 257 | } 258 | } else { 259 | 260 | // New sub! Generate an id, save it locally, and send message. 261 | 262 | id = _Random2.default.id(); 263 | var subIdRemember = _Data2.default.ddp.sub(name, params); 264 | 265 | _Data2.default.subscriptions[id] = { 266 | id: id, 267 | subIdRemember: subIdRemember, 268 | name: name, 269 | params: _ejson2.default.clone(params), 270 | inactive: false, 271 | ready: false, 272 | readyDeps: new _trackr2.default.Dependency(), 273 | readyCallback: callbacks.onReady, 274 | stopCallback: callbacks.onStop, 275 | stop: function stop() { 276 | _Data2.default.ddp.unsub(this.subIdRemember); 277 | delete _Data2.default.subscriptions[this.id]; 278 | this.ready && this.readyDeps.changed(); 279 | 280 | if (callbacks.onStop) { 281 | callbacks.onStop(); 282 | } 283 | } 284 | }; 285 | } 286 | 287 | // return a handle to the application. 288 | var handle = { 289 | stop: function stop() { 290 | if (_Data2.default.subscriptions[id]) _Data2.default.subscriptions[id].stop(); 291 | }, 292 | ready: function ready() { 293 | if (!_Data2.default.subscriptions[id]) return false; 294 | 295 | var record = _Data2.default.subscriptions[id]; 296 | record.readyDeps.depend(); 297 | return record.ready; 298 | }, 299 | subscriptionId: id 300 | }; 301 | 302 | if (_trackr2.default.active) { 303 | // We're in a reactive computation, so we'd like to unsubscribe when the 304 | // computation is invalidated... but not if the rerun just re-subscribes 305 | // to the same subscription! When a rerun happens, we use onInvalidate 306 | // as a change to mark the subscription "inactive" so that it can 307 | // be reused from the rerun. If it isn't reused, it's killed from 308 | // an afterFlush. 309 | _trackr2.default.onInvalidate(function (c) { 310 | if (_Data2.default.subscriptions[id]) { 311 | _Data2.default.subscriptions[id].inactive = true; 312 | } 313 | 314 | _trackr2.default.afterFlush(function () { 315 | if (_Data2.default.subscriptions[id] && _Data2.default.subscriptions[id].inactive) { 316 | handle.stop(); 317 | } 318 | }); 319 | }); 320 | } 321 | 322 | return handle; 323 | } 324 | }); --------------------------------------------------------------------------------