react-native-meteor
Advanced tools
Comparing version 1.0.0-beta3 to 1.0.0-beta30
@@ -0,6 +1,16 @@ | ||
# Android | ||
Add this to your AndroidManifest.xml file to autoreconnect fastly to DDP server if your device reconnects to network | ||
```xml | ||
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" /> | ||
``` | ||
# Installing decorators | ||
## With RN >= 0.20.0 (Babel 6) | ||
## With RN >= 0.16.0 (Babel 6) | ||
- `npm i --save-dev babel-plugin-transform-decorators-legacy babel-preset-react-native` in your projet | ||
- `npm i --save-dev babel-plugin-transform-decorators-legacy babel-preset-react-native` in your project | ||
- Create a .babelrc file at the root of your project : | ||
@@ -15,10 +25,5 @@ | ||
## With RN >= 0.16.0 && <= 0.19.0 (Babel 6) | ||
Looking for your help. The RN 0.20.0 solution might be working, please let me know ;) | ||
## With RN <0.16.0 (Babel 5) | ||
Use a .babelrc file at the root of your projet that contains : | ||
Use a .babelrc file at the root of your project that contains : | ||
@@ -25,0 +30,0 @@ ```json |
{ | ||
"name": "react-native-meteor", | ||
"version": "1.0.0-beta3", | ||
"description": "DDP React-native Client", | ||
"version": "1.0.0-beta30", | ||
"description": "Full Meteor Client for React Native", | ||
"main": "src/Meteor.js", | ||
@@ -29,8 +29,10 @@ "scripts": { | ||
"dependencies": { | ||
"ddp.js": "1.1.0", | ||
"base-64": "^0.1.0", | ||
"crypto-js": "^3.1.6", | ||
"ejson": "^2.1.2", | ||
"minimongo-cache": "0.0.48", | ||
"react-mixin": "^3.0.3", | ||
"trackr": "^2.0.2" | ||
"trackr": "^2.0.2", | ||
"wolfy87-eventemitter": "^4.3.0" | ||
} | ||
} |
182
README.md
@@ -1,17 +0,5 @@ | ||
[](https://badge.fury.io/gh/inProgress-team%2Freact-native-meteor) | ||
[](http://badge.fury.io/js/react-native-meteor) | ||
[](https://david-dm.org/inProgress-team/react-native-meteor) | ||
[](https://david-dm.org/inProgress-team/react-native-meteor#info=devDependencies) | ||
[![MIT][license-badge]][license] | ||
[![bitHound Score][bithound-badge]][bithound] | ||
# react-native-meteor [](https://www.npmjs.org/package/react-native-meteor) [](http://badge.fury.io/js/react-native-meteor) [](https://david-dm.org/inProgress-team/react-native-meteor) | ||
[bithound-badge]: https://www.bithound.io/github/inProgress-Team/react-native-meteor/badges/score.svg | ||
[bithound]: https://www.bithound.io/github/inProgress-Team/react-native-meteor | ||
[license-badge]: https://img.shields.io/dub/l/vibe-d.svg | ||
[license]: https://github.com/inProgress-team/react-native-meteor/blob/master/LICENSE | ||
Meteor-like methods for React Native. ! For old docs, see [v0.6.2 documentation](https://github.com/inProgress-team/react-native-meteor/tree/0.6.2) (classic ddp interface). | ||
# react-native-meteor | ||
Meteor-like methods for React Native. **Currently in v1.0.0-beta3** ! For old docs, see [v0.6.2 documentation](https://github.com/inProgress-team/react-native-meteor/tree/0.6.2) (classic ddp interface). | ||
## What is it for ? | ||
@@ -26,3 +14,3 @@ | ||
npm i --save react-native-meteor | ||
npm i --save react-native-meteor@latest | ||
@@ -36,3 +24,3 @@ [!! See detailed installation guide](https://github.com/inProgress-team/react-native-meteor/blob/master/docs/Install.md) | ||
import { View, Text, Component } from 'react-native'; | ||
import Meteor, { connectMeteor } from 'react-native-meteor'; | ||
import Meteor, { connectMeteor, MeteorListView } from 'react-native-meteor'; | ||
@@ -57,21 +45,31 @@ /* | ||
} | ||
startMeteorSubscriptions() { | ||
Meteor.subscribe('todos'); | ||
} | ||
getMeteorData() { | ||
const handle = Meteor.subscribe('todos'); | ||
Meteor.subscribe('settings'); | ||
return { | ||
todos: Meteor.collection('todos').find() | ||
todosReady: handle.ready(), | ||
settings: Meteor.collection('settings').findOne() | ||
}; | ||
} | ||
renderRow(todo) { | ||
return ( | ||
<Text>{todo.title}</Text> | ||
); | ||
} | ||
render() { | ||
const { todos } = this.data; | ||
const { settings, todosReady } = this.data; | ||
{todos.map(todo=>{ | ||
return ( | ||
<View key={todo._id}> | ||
<Text>{todo.title}</Text> | ||
</View> | ||
) | ||
})} | ||
<View> | ||
<Text>{settings.title}</Text> | ||
{!todosReady && <Text>Not ready</Text>} | ||
<MeteorListView | ||
collection="todos" | ||
selector={{done: true}} | ||
options={{sort: {createdAt: -1}}} | ||
renderRow={this.renderRow} | ||
/> | ||
</View> | ||
} | ||
@@ -81,39 +79,18 @@ } | ||
# connectMeteor | ||
## startMeteorSubscriptions | ||
## getMeteorData | ||
Inside this method, you can create subscriptions (see below) when component is mounted. It will automatically unsubscribe if the component is unmounted. | ||
#### [Meteor.subscribe](http://docs.meteor.com/#/full/meteor_subscribe) | ||
##### Example usage | ||
* [Meteor.subscribe](http://docs.meteor.com/#/full/meteor_subscribe) : returns an handle | ||
your server side: | ||
```javascript | ||
Meteor.publish('todos', function(selector, options){ | ||
var selector = selector || {}; | ||
var options = options || {}; | ||
return Todos.find(selector, options); | ||
}); | ||
``` | ||
Inside getMeteorData, you can also access any Meteor reactive data source, which means : | ||
your react-native client code: | ||
```javascript | ||
//Meteor subscribe can be used like on meteor official site | ||
Meteor.subscribe('todos', {status: 'done'}, {limit: 10, sort: {createdAt: -1}}); | ||
``` | ||
##### NOTE | ||
- Meteor subscribe parameter still not supporting EJSON, so you can't pass param value like date, boolean etc. For now it's only support object of string (JSON) | ||
- Meteor subscribe parameter already support Publish Composite. If you are using this package, related published collections will be available too in subscriptions. | ||
## getMeteorData | ||
Inside getMeteorData, you can access any Meteor reactive data source, which means : | ||
* Meteor.subscribe handle | ||
* Meteor.collection(collectionName) | ||
* [.find(selector, options)](http://docs.meteor.com/#/full/find) | ||
* [.findOne(selector, options)](http://docs.meteor.com/#/full/findone) | ||
* [.findOne(id)](http://docs.meteor.com/#/full/findone) | ||
* [Meteor.user()](http://docs.meteor.com/#/full/meteor_user) | ||
@@ -124,6 +101,52 @@ * [Meteor.userId()](http://docs.meteor.com/#/full/meteor_userid) | ||
# Additionals collection methods | ||
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. | ||
* Meteor.collection(collectionName) | ||
* [.insert(doc, callback)](http://docs.meteor.com/#/full/insert) | ||
* [.update(id, modifier, [options], [callback])](http://docs.meteor.com/#/full/update) | ||
* [.remove(id, callback(err, countRemoved))](http://docs.meteor.com/#/full/remove) | ||
# MeteorListView Component | ||
Same as [ListView](https://facebook.github.io/react-native/docs/listview.html) Component but does not need dataSource and accepts three arguments : | ||
- `collection` **string** *required* | ||
- `selector` [**string** / **object**] | ||
- `options` **object** | ||
### Example usage | ||
```javascript | ||
<MeteorListView | ||
collection="todos" | ||
selector={{done: true}} | ||
options={{sort: {createdAt: -1}}} | ||
renderRow={this.renderItem} | ||
/> | ||
``` | ||
# MeteorComplexListView Component | ||
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. | ||
- `elements` **function** *required* : a reactive function which returns an array of elements. | ||
### Example usage | ||
```javascript | ||
<MeteorComplexListView | ||
elements={()=>{return Meteor.collection('todos').find()}} | ||
renderRow={this.renderItem} | ||
/> | ||
``` | ||
# API | ||
## Meteor.connect(endpoint) | ||
## Meteor DDP connection | ||
#### Meteor.connect(endpoint, options) | ||
Connect to a DDP server. You only have to do this once in your app. | ||
@@ -134,17 +157,58 @@ | ||
- `url` **string** *required* | ||
- `options` **object** Available options are : | ||
- 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. | ||
- 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. | ||
- reconnectInterval **number** [10000] the interval in ms between reconnection attempts. | ||
#### Meteor.disconnect() | ||
Disconnect from the DDP server. | ||
## Meteor methods | ||
* [Meteor.call](http://docs.meteor.com/#/full/meteor_call) | ||
* [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.) | ||
* [Meteor.logout](http://docs.meteor.com/#/full/meteor_logout) | ||
* [Meteor.call](http://docs.meteor.com/#/full/meteor_call) | ||
* [Meteor.logoutOtherClients](http://docs.meteor.com/#/full/meteor_logoutotherclients) | ||
##### NOTE | ||
Meteor call parameter still not supporting EJSON, so you can't pass param value like date, boolean etc. For now it's only support object of string (JSON) | ||
## Meteor.Accounts | ||
* [Accounts.createUser](http://docs.meteor.com/#/full/accounts_createuser) | ||
* [Accounts.changePassword](http://docs.meteor.com/#/full/accounts_forgotpassword) | ||
* [Accounts.forgotPassword](http://docs.meteor.com/#/full/accounts_changepassword) | ||
* [Accounts.onLogin](http://docs.meteor.com/#/full/accounts_onlogin) | ||
* [Accounts.onLoginFailure](http://docs.meteor.com/#/full/accounts_onloginfailure) | ||
## Meteor.ddp | ||
Once connected to the ddp server, you can access every method available in [ddp.js](https://github.com/mondora/ddp.js/). | ||
* Meteor.ddp.on('connected') | ||
* Meteor.ddp.on('added') | ||
* Meteor.ddp.on('changed') | ||
* ... | ||
## CollectionFS | ||
* Meteor.FSCollection(collectionName) : Helper for [Meteor-CollectionFS](https://github.com/CollectionFS/Meteor-CollectionFS). Full documentation [here](https://github.com/inProgress-team/react-native-meteor/blob/master/docs/FSCollection.md) | ||
* This plugin also exposes a FSCollectionImagesPreloader component which helps you preload every image you want in CollectionFS (only available on ios) | ||
```javascript | ||
import { FSCollectionImagesPreloader } from 'react-native-meteor'; | ||
<FSCollectionImagesPreloader | ||
collection="imagesFiles" | ||
selector={{metadata.owner: XXX}} | ||
/> | ||
``` | ||
## react-native-router-flux | ||
* [Github repository](https://github.com/inProgress-team/react-native-meteor-router-flux) | ||
* npm i --save react-native-meteor-router-flux@latest | ||
* [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. | ||
# Want to help ? | ||
Pull Requests and issues reported are welcome ! :) |
@@ -0,10 +1,22 @@ | ||
import React from 'react-native'; | ||
import minimongo from 'minimongo-cache'; | ||
process.nextTick = setImmediate; | ||
const db = new minimongo(); | ||
db.debug = false; | ||
db.batchedUpdates = React.addons.batchedUpdates; | ||
export default { | ||
_endpoint: null, | ||
_options: null, | ||
ddp: null, | ||
subscriptions: {}, | ||
db: new minimongo(), | ||
db: db, | ||
calls: [], | ||
hasBeenConnected: false, | ||
getUrl() { | ||
return this._endpoint.substring(0, this._endpoint.indexOf('/websocket')); | ||
}, | ||
waitDdpReady(cb) { | ||
@@ -20,14 +32,31 @@ if(this.ddp) { | ||
_cbsLoggingIn: [], | ||
_subscribeLoggingIn(cb) { | ||
this._cbsLoggingIn.push(cb); | ||
_cbs: [], | ||
onChange(cb) { | ||
this.db.on('change', cb); | ||
this.ddp.on('connected', cb); | ||
this.ddp.on('disconnected', cb); | ||
this.on('loggingIn', cb); | ||
}, | ||
_unsubscribeLoggingIn(cb) { | ||
this._cbsLoggingIn.splice(this._cbsLoggingIn.indexOf(cb)); | ||
offChange(cb) { | ||
this.db.off('change', cb); | ||
this.ddp.off('connected', cb); | ||
this.ddp.off('disconnected', cb); | ||
this.off('loggingIn', cb); | ||
}, | ||
_notifyLoggingIn() { | ||
for(var i in this._cbsLoggingIn) { | ||
this._cbsLoggingIn[i](); | ||
} | ||
on(eventName, cb) { | ||
this._cbs.push({ | ||
eventName: eventName, | ||
callback: cb | ||
}); | ||
}, | ||
off(eventName, cb) { | ||
this._cbs.splice(this._cbs.findIndex(_cb=>_cb.callback == cb && _cb.eventName == eventName), 1); | ||
}, | ||
notify(eventName) { | ||
this._cbs.map(cb=>{ | ||
if(cb.eventName == eventName && typeof cb.callback == 'function') { | ||
cb.callback(); | ||
} | ||
}); | ||
} | ||
} |
@@ -0,29 +1,38 @@ | ||
import { NetInfo, Platform, View } from 'react-native'; | ||
import reactMixin from 'react-mixin'; | ||
import Trackr from 'trackr'; | ||
import DDP from 'ddp.js'; | ||
import EJSON from 'ejson'; | ||
import DDP from '../lib/ddp.js'; | ||
import Random from '../lib/Random'; | ||
import Data from './Data'; | ||
import Mixin from './Mixin'; | ||
import User from './User'; | ||
import collection from './Collection'; | ||
import call from './Call'; | ||
import Mixin from './components/Mixin'; | ||
import ListView from './components/ListView'; | ||
import MeteorComplexListView from './components/ComplexListView'; | ||
import FSCollection from './CollectionFS/FSCollection'; | ||
import FSCollectionImagesPreloader from './CollectionFS/FSCollectionImagesPreloader'; | ||
import User from './user/User'; | ||
import Accounts from './user/Accounts'; | ||
module.exports = { | ||
Accounts: Accounts, | ||
MeteorListView: ListView, | ||
MeteorComplexListView: MeteorComplexListView, | ||
FSCollectionImagesPreloader: Platform.OS == 'android' ? View : FSCollectionImagesPreloader, | ||
collection: collection, | ||
FSCollection: FSCollection, | ||
getData() { | ||
return Data | ||
}, | ||
connectMeteor(reactClass) { | ||
return reactMixin.onClass(reactClass, Mixin); | ||
}, | ||
collection(name) { | ||
return { | ||
find(selector, options) { | ||
if(!Data.db || !Data.db[name]) return []; | ||
if(typeof selector == 'string') return this.find({_id: selector}, options); | ||
return Data.db[name].find(selector, options) | ||
}, | ||
findOne(selector, options) { | ||
if(!Data.db || !Data.db[name]) return null; | ||
if(typeof selector == 'string') return this.findOne({_id: selector}, options); | ||
return Data.db[name] && Data.db[name].findOne(selector, options) | ||
} | ||
}; | ||
}, | ||
...User, | ||
@@ -39,26 +48,66 @@ status() { | ||
}, | ||
call(eventName) { | ||
var args = Array.prototype.slice.call(arguments, 1); | ||
if (args.length && typeof args[args.length - 1] === "function") { | ||
var callback = args.pop(); | ||
call: call, | ||
disconnect() { | ||
if(Data.ddp) { | ||
Data.ddp.disconnect(); | ||
} | ||
}, | ||
_subscriptionsRestart() { | ||
for(var i in Data.subscriptions) { | ||
const sub = Data.subscriptions[i]; | ||
Data.ddp.unsub(sub.subIdRemember); | ||
sub.subIdRemember = Data.ddp.sub(sub.name, sub.params); | ||
} | ||
const id = Data.ddp.method(eventName, args); | ||
Data.calls.push({ | ||
id: id, | ||
callback: callback | ||
}); | ||
}, | ||
connect(endpoint) { | ||
waitDdpConnected(cb) { | ||
if(Data.ddp && Data.ddp.status == 'connected') { | ||
cb(); | ||
} else if(Data.ddp) { | ||
Data.ddp.once('connected', cb); | ||
} else { | ||
setTimeout(()=>{ this.waitDdpConnected(cb) }, 500); | ||
} | ||
}, | ||
connect(endpoint, options) { | ||
if(!endpoint) endpoint = Data._endpoint; | ||
if(!options) options = Data._options; | ||
Data._endpoint = endpoint; | ||
Data._options = options; | ||
this.ddp = Data.ddp = new DDP({ | ||
endpoint: endpoint, | ||
SocketConstructor: WebSocket | ||
SocketConstructor: WebSocket, | ||
...options | ||
}); | ||
NetInfo.isConnected.addEventListener('change', isConnected=>{ | ||
if(isConnected) { | ||
Data.ddp.connect(); | ||
} | ||
}); | ||
Data.ddp.on("connected", ()=>{ | ||
console.info("connected"); | ||
console.info("Connected to DDP server."); | ||
this._loadInitialUser(); | ||
if(Data.hasBeenConnected) { | ||
this._subscriptionsRestart(); | ||
} else { | ||
Data.hasBeenConnected = true; | ||
} | ||
}); | ||
Data.ddp.on("disconnected", ()=>{ | ||
console.info("Disconnected from DDP server."); | ||
this.handleLogout(); | ||
this.connect(); | ||
}); | ||
Data.ddp.on("added", message => { | ||
@@ -72,3 +121,10 @@ if(!Data.db[message.collection]) { | ||
Data.ddp.on("ready", message => { | ||
//console.info('READY', message.subs); | ||
for(var i in Data.subscriptions) { | ||
const sub = Data.subscriptions[i]; | ||
sub.ready = true; | ||
sub.readyDeps.changed(); | ||
sub.readyCallback && sub.readyCallback(); | ||
} | ||
}); | ||
@@ -93,7 +149,8 @@ | ||
const sub = Data.subscriptions[i]; | ||
if(sub.id == message.id) { | ||
console.log("No sub for", sub.name); | ||
if(sub.subIdRemember == message.id) { | ||
console.warn("No subscription existing for", sub.name); | ||
} | ||
} | ||
}); | ||
}, | ||
@@ -112,53 +169,117 @@ subscribe(name) { | ||
const stringKey = name+JSON.stringify(params); | ||
// Is there an existing sub with the same name and param, run in an | ||
// invalidated Computation? This will happen if we are rerunning an | ||
// existing computation. | ||
// | ||
// For example, consider a rerun of: | ||
// | ||
// Tracker.autorun(function () { | ||
// Meteor.subscribe("foo", Session.get("foo")); | ||
// Meteor.subscribe("bar", Session.get("bar")); | ||
// }); | ||
// | ||
// If "foo" has changed but "bar" has not, we will match the "bar" | ||
// subcribe to an existing inactive subscription in order to not | ||
// unsub and resub the subscription unnecessarily. | ||
// | ||
// We only look for one such sub; if there are N apparently-identical subs | ||
// being invalidated, we will require N matching subscribe calls to keep | ||
// them all active. | ||
if (!Data.subscriptions[stringKey]) { | ||
// We're not currently subscribed to this. Go start a subscription. | ||
//console.log("Subscribe to", name, "with params", params); | ||
Data.subscriptions[stringKey] = { | ||
references: 1, | ||
let existing = false; | ||
for(var i in Data.subscriptions) { | ||
const sub = Data.subscriptions[i]; | ||
if(sub.inactive && sub.name === name && EJSON.equals(sub.params, params)) existing = sub; | ||
} | ||
let id; | ||
if (existing) { | ||
id = existing.id; | ||
existing.inactive = false; | ||
if (callbacks.onReady) { | ||
// If the sub is not already ready, replace any ready callback with the | ||
// one provided now. (It's not really clear what users would expect for | ||
// an onReady callback inside an autorun; the semantics we provide is | ||
// that at the time the sub first becomes ready, we call the last | ||
// onReady callback provided, if any.) | ||
if (!existing.ready) | ||
existing.readyCallback = callbacks.onReady; | ||
} | ||
if (callbacks.onStop) { | ||
existing.stopCallback = callbacks.onStop; | ||
} | ||
} else { | ||
// New sub! Generate an id, save it locally, and send message. | ||
id = Random.id(); | ||
const subIdRemember = Data.ddp.sub(name, params); | ||
Data.subscriptions[id] = { | ||
id: id, | ||
subIdRemember: subIdRemember, | ||
name: name, | ||
params: params, | ||
id: Data.ddp.sub(name, params), | ||
stop: function () { | ||
// Wait for all autoruns to finish rerunning before processing the unsubscription | ||
// request. It's possible that stop() was called because an autorun is rerunning, | ||
// so before doing anything drastic, we should wait to see if the autorun | ||
// recreates the subscription when it is rerun. | ||
this.references--; | ||
Trackr.afterFlush(() => { | ||
if (this.references === 0) { | ||
// Nope, either the autorun did not recreate the subscription, or there | ||
// were multiple calls to subscribeToMagazine() in the first place and not all | ||
// of them have been stopped. Go ahead and cancel. | ||
//console.log("Canceling our subscription to", name, "with params", params); | ||
Data.ddp.unsub(Data.subscriptions[stringKey].id); | ||
delete Data.subscriptions[stringKey]; | ||
} | ||
}); | ||
params: EJSON.clone(params), | ||
inactive: false, | ||
ready: false, | ||
readyDeps: new Trackr.Dependency, | ||
readyCallback: callbacks.onReady, | ||
stopCallback: callbacks.onStop, | ||
stop: function() { | ||
Data.ddp.unsub(this.subIdRemember); | ||
delete Data.subscriptions[this.id]; | ||
this.ready && this.readyDeps.changed(); | ||
if (callbacks.onStop) { | ||
callbacks.onStop(); | ||
} | ||
} | ||
}; | ||
} else { | ||
// We already have a subscription to this magazine running. Increment the reference | ||
// count to stop it from going away. | ||
Data.subscriptions[stringKey].references++; | ||
} | ||
// return a handle to the application. | ||
var handle = { | ||
stop: function () { | ||
if(Data.subscriptions[id]) | ||
Data.subscriptions[id].stop(); | ||
}, | ||
ready: function () { | ||
if (!Data.subscriptions[id]) return false; | ||
var record = Data.subscriptions[id]; | ||
record.readyDeps.depend(); | ||
return record.ready; | ||
}, | ||
subscriptionId: id | ||
}; | ||
if (Trackr.active) { | ||
Trackr.onInvalidate((c) => { | ||
// subscribeToMagazine was called from inside an autorun, and the autorun is | ||
// about to rerun itself. Tentatively plan to cancel the subscription. If the | ||
// autorun resubscribes to that same magazine when it is rerun, the logic in | ||
// in stop() is smart enough to leave the subscription alone rather than | ||
// canceling and immediately recreating it. | ||
// | ||
// (Tracker.onInvalidate is a shortcut for Tracker.currentComputation.onInvalidate.) | ||
if (Data.subscriptions[stringKey]) { | ||
Data.subscriptions[stringKey].stop(); | ||
// We're in a reactive computation, so we'd like to unsubscribe when the | ||
// computation is invalidated... but not if the rerun just re-subscribes | ||
// to the same subscription! When a rerun happens, we use onInvalidate | ||
// as a change to mark the subscription "inactive" so that it can | ||
// be reused from the rerun. If it isn't reused, it's killed from | ||
// an afterFlush. | ||
Trackr.onInvalidate(function (c) { | ||
if(Data.subscriptions[id]) { | ||
Data.subscriptions[id].inactive = true; | ||
} | ||
Trackr.afterFlush(function () { | ||
if (Data.subscriptions[id] && Data.subscriptions[id].inactive) { | ||
handle.stop(); | ||
} | ||
}); | ||
}); | ||
} | ||
return Data.subscriptions[stringKey]; | ||
return handle; | ||
} | ||
} |
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
49050
24
1200
209
7
1
+ Addedbase-64@^0.1.0
+ Addedcrypto-js@^3.1.6
+ Addedwolfy87-eventemitter@^4.3.0
+ Addedbase-64@0.1.0(transitive)
+ Addedcrypto-js@3.3.0(transitive)
- Removedddp.js@1.1.0
- Removedddp.js@1.1.0(transitive)