diff --git a/integration/test/ParseUserTest.js b/integration/test/ParseUserTest.js index d05a477fa..75a216ca0 100644 --- a/integration/test/ParseUserTest.js +++ b/integration/test/ParseUserTest.js @@ -113,7 +113,7 @@ describe('Parse User', () => { it('can login users with installationId', async () => { Parse.User.enableUnsafeCurrentUser(); - const currentInstallation = await Parse.CoreManager.getInstallationController().currentInstallationId(); + const currentInstallationId = await Parse.CoreManager.getInstallationController().currentInstallationId(); const installationId = '12345678'; const user = new Parse.User(); user.set('username', 'parse'); @@ -132,7 +132,7 @@ describe('Parse User', () => { let sessions = await sessionQuery.find({ useMasterKey: true }); expect(sessions.length).toBe(2); expect(sessions[0].get('installationId')).toBe(installationId); - expect(sessions[1].get('installationId')).toBe(currentInstallation); + expect(sessions[1].get('installationId')).toBe(currentInstallationId); expect(sessions[0].get('sessionToken')).toBe(user.getSessionToken()); expect(sessions[1].get('sessionToken')).toBe(loggedUser.getSessionToken()); @@ -142,12 +142,43 @@ describe('Parse User', () => { }); sessions = await sessionQuery.find({ useMasterKey: true }); expect(sessions.length).toBe(2); - expect(sessions[0].get('installationId')).toBe(currentInstallation); + expect(sessions[0].get('installationId')).toBe(currentInstallationId); expect(sessions[1].get('installationId')).toBe(installationId); expect(sessions[0].get('sessionToken')).toBe(loggedUser.getSessionToken()); expect(sessions[1].get('sessionToken')).toBe(installationUser.getSessionToken()); }); + it('can get current installation', async () => { + const currentInstallationId = await Parse.CoreManager.getInstallationController().currentInstallationId(); + const installation = await Parse.Installation.currentInstallation(); + expect(installation.installationId).toBe(currentInstallationId); + expect(installation.deviceType).toBe(Parse.Installation.DEVICE_TYPES.WEB); + await installation.save(); + expect(installation.id).toBeDefined(); + expect(installation.createdAt).toBeDefined(); + expect(installation.updatedAt).toBeDefined(); + const data = { + deviceToken: '1234', + badge: 1, + appIdentifier: 'com.parse.server', + appName: 'Parse JS SDK', + appVersion: '1.0.0', + parseVersion: '1.0.0', + localeIdentifier: 'en-US', + timeZone: 'GMT', + channels: ['test'], + GCMSenderId: '1234', + pushType: 'test', + }; + installation.set(data); + await installation.save(); + const query = new Parse.Query(Parse.Installation); + const result = await query.get(installation.id, { useMasterKey: true }); + Object.keys(data).forEach(key => { + expect(result[key]).toEqual(data[key]); + }); + }); + it('can login with userId', async () => { Parse.User.enableUnsafeCurrentUser(); diff --git a/src/CoreManager.ts b/src/CoreManager.ts index db639b55d..49ed62e91 100644 --- a/src/CoreManager.ts +++ b/src/CoreManager.ts @@ -14,6 +14,7 @@ import type { HookDeclaration, HookDeleteArg } from './ParseHooks'; import type ParseConfig from './ParseConfig'; import type LiveQueryClient from './LiveQueryClient'; import type ParseSchema from './ParseSchema'; +import type ParseInstallation from './ParseInstallation'; type AnalyticsController = { track: (name: string, dimensions: { [key: string]: string }) => Promise, @@ -41,6 +42,8 @@ type FileController = { }; type InstallationController = { currentInstallationId: () => Promise, + currentInstallation: () => Promise, + updateInstallationOnDisk: (installation: ParseInstallation) => Promise, }; type ObjectController = { fetch: ( @@ -376,7 +379,11 @@ const CoreManager = { }, setInstallationController(controller: InstallationController) { - requireMethods('InstallationController', ['currentInstallationId'], controller); + requireMethods( + 'InstallationController', + ['currentInstallationId', 'currentInstallation', 'updateInstallationOnDisk'], + controller + ); config['InstallationController'] = controller; }, diff --git a/src/InstallationController.js b/src/InstallationController.js deleted file mode 100644 index 41d865d10..000000000 --- a/src/InstallationController.js +++ /dev/null @@ -1,38 +0,0 @@ -/** - * @flow - */ - -import Storage from './Storage'; -const uuidv4 = require('./uuid'); - -let iidCache = null; - -const InstallationController = { - currentInstallationId(): Promise { - if (typeof iidCache === 'string') { - return Promise.resolve(iidCache); - } - const path = Storage.generatePath('installationId'); - return Storage.getItemAsync(path).then(iid => { - if (!iid) { - iid = uuidv4(); - return Storage.setItemAsync(path, iid).then(() => { - iidCache = iid; - return iid; - }); - } - iidCache = iid; - return iid; - }); - }, - - _clearCache() { - iidCache = null; - }, - - _setInstallationIdCache(iid: string) { - iidCache = iid; - }, -}; - -module.exports = InstallationController; diff --git a/src/InstallationController.ts b/src/InstallationController.ts new file mode 100644 index 000000000..bc14e8337 --- /dev/null +++ b/src/InstallationController.ts @@ -0,0 +1,81 @@ +import CoreManager from './CoreManager'; +import Storage from './Storage'; +import ParseInstallation from './ParseInstallation'; +import uuidv4 from './uuid'; + +const CURRENT_INSTALLATION_KEY = 'currentInstallation'; +const CURRENT_INSTALLATION_ID_KEY = 'currentInstallationId'; + +let iidCache: string | null = null; +let currentInstallationCache = null; +let currentInstallationCacheMatchesDisk = false; + +const InstallationController = { + async updateInstallationOnDisk(installation: ParseInstallation): Promise { + const path = Storage.generatePath(CURRENT_INSTALLATION_KEY); + await Storage.setItemAsync(path, JSON.stringify(installation.toJSON())); + this._setCurrentInstallationCache(installation); + }, + + async currentInstallationId(): Promise { + if (typeof iidCache === 'string') { + return iidCache; + } + const path = Storage.generatePath(CURRENT_INSTALLATION_ID_KEY); + let iid = await Storage.getItemAsync(path); + if (!iid) { + iid = uuidv4(); + return Storage.setItemAsync(path, iid).then(() => { + iidCache = iid; + return iid; + }); + } + iidCache = iid; + return iid; + }, + + async currentInstallation(): Promise { + if (currentInstallationCache) { + return currentInstallationCache; + } + if (currentInstallationCacheMatchesDisk) { + return null; + } + const path = Storage.generatePath(CURRENT_INSTALLATION_KEY); + let installationData = await Storage.getItemAsync(path); + currentInstallationCacheMatchesDisk = true; + if (installationData) { + installationData = JSON.parse(installationData); + installationData.className = '_Installation'; + const current = ParseInstallation.fromJSON(installationData); + currentInstallationCache = current; + return current; + } + const installationId = await this.currentInstallationId(); + const installation = new ParseInstallation(); + installation.set('deviceType', ParseInstallation.DEVICE_TYPES.WEB); + installation.set('installationId', installationId); + installation.set('parseVersion', CoreManager.get('VERSION')); + currentInstallationCache = installation; + await Storage.setItemAsync(path, JSON.stringify(installation.toJSON())) + return installation; + }, + + _clearCache() { + iidCache = null; + currentInstallationCache = null; + currentInstallationCacheMatchesDisk = false; + }, + + _setInstallationIdCache(iid: string) { + iidCache = iid; + }, + + _setCurrentInstallationCache(installation: ParseInstallation, matchesDisk: boolean = true) { + currentInstallationCache = installation; + currentInstallationCacheMatchesDisk = matchesDisk; + }, +}; + +module.exports = InstallationController; +export default InstallationController; diff --git a/src/Parse.ts b/src/Parse.ts index 6692df3e3..b6bc8a169 100644 --- a/src/Parse.ts +++ b/src/Parse.ts @@ -199,12 +199,12 @@ const Parse: ParseType = { CoreManager.setIfNeeded('EventEmitter', EventEmitter); CoreManager.setIfNeeded('LiveQuery', new ParseLiveQuery()); CoreManager.setIfNeeded('CryptoController', CryptoController); + CoreManager.setIfNeeded('EventuallyQueue', EventuallyQueue); + CoreManager.setIfNeeded('InstallationController', InstallationController); CoreManager.setIfNeeded('LocalDatastoreController', LocalDatastoreController); CoreManager.setIfNeeded('StorageController', StorageController); CoreManager.setIfNeeded('WebSocketController', WebSocketController); - CoreManager.setIfNeeded('EventuallyQueue', EventuallyQueue); - if (process.env.PARSE_BUILD === 'browser') { Parse.IndexedDB = CoreManager.setIfNeeded('IndexedDBStorageController', IndexedDBStorageController); } @@ -464,7 +464,6 @@ const Parse: ParseType = { }, }; -CoreManager.setInstallationController(InstallationController); CoreManager.setRESTController(RESTController); if (process.env.PARSE_BUILD === 'node') { diff --git a/src/ParseInstallation.js b/src/ParseInstallation.js deleted file mode 100644 index 380b5c8a9..000000000 --- a/src/ParseInstallation.js +++ /dev/null @@ -1,20 +0,0 @@ -/** - * @flow - */ - -import ParseObject from './ParseObject'; - -import type { AttributeMap } from './ObjectStateMutations'; - -export default class Installation extends ParseObject { - constructor(attributes: ?AttributeMap) { - super('_Installation'); - if (attributes && typeof attributes === 'object') { - if (!this.set(attributes || {})) { - throw new Error("Can't create an invalid Installation"); - } - } - } -} - -ParseObject.registerSubclass('_Installation', Installation); diff --git a/src/ParseInstallation.ts b/src/ParseInstallation.ts new file mode 100644 index 000000000..10f95bbea --- /dev/null +++ b/src/ParseInstallation.ts @@ -0,0 +1,230 @@ +import CoreManager from './CoreManager'; +import ParseObject from './ParseObject'; + +import type { AttributeMap } from './ObjectStateMutations'; + +type DeviceInterface = { + IOS: string; + MACOS: string; + TVOS: string; + FCM: string; + ANDROID: string; + WEB: string; +} + +const DEVICE_TYPES: DeviceInterface = { + IOS: 'ios', + MACOS: 'macos', + TVOS: 'tvos', + FCM: 'fcm', + ANDROID: 'android', + WEB: 'web', +}; + +/** + * Parse.Installation is a local representation of installation data that can be saved and retrieved from the Parse cloud. + * This class is a subclass of a Parse.Object, and retains the same functionality of a Parse.Object, but also extends it with installation-specific features. + * + *

A valid Parse.Installation can only be instantiated via Parse.Installation.currentInstallation() + * + * Parse.Installation objects which have a valid deviceToken and are saved to the Parse cloud can be used to target push notifications. + *

+ * + * @alias Parse.Installation + */ +class ParseInstallation extends ParseObject { + /** + * @param {object} attributes The initial set of data to store in the object. + */ + constructor(attributes?: AttributeMap) { + super('_Installation'); + if (attributes && typeof attributes === 'object') { + if (!this.set(attributes)) { + throw new Error("Can't create an invalid Installation"); + } + } + } + + /** + * A unique identifier for this installation’s client application. In iOS, this is the Bundle Identifier. + * + * @property {string} appIdentifier + * @static + */ + get appIdentifier() { + return this.get('appIdentifier'); + } + + /** + * The version string of the client application to which this installation belongs. + * + * @property {string} appVersion + * @static + */ + get appVersion() { + return this.get('appVersion'); + } + + /** + * The display name of the client application to which this installation belongs. + * + * @property {string} appName + * @static + */ + get appName() { + return this.get('appName'); + } + + /** + * The current value of the icon badge for iOS apps. + * Changes to this value on the server will be used + * for future badge-increment push notifications. + * + * @property {number} badge + * @static + */ + get badge() { + return this.get('badge'); + } + + /** + * An array of the channels to which a device is currently subscribed. + * + * @property {string[]} channels + * @static + */ + get channels() { + return this.get('channels'); + } + + /** + * Token used to deliver push notifications to the device. + * + * @property {string} deviceToken + * @static + */ + get deviceToken() { + return this.get('deviceToken'); + } + + /** + * The type of device, “ios”, “android”, “web”, etc. + * + * @property {string} deviceType + * @static + */ + get deviceType() { + return this.get('deviceType'); + } + + /** + * Gets the GCM sender identifier for this installation + * + * @property {string} GCMSenderId + * @static + */ + get GCMSenderId() { + return this.get('GCMSenderId'); + } + + /** + * Universally Unique Identifier (UUID) for the device used by Parse. It must be unique across all of an app’s installations. + * + * @property {string} installationId + * @static + */ + get installationId() { + return this.get('installationId'); + } + + /** + * Gets the local identifier for this installation + * + * @property {string} localeIdentifier + * @static + */ + get localeIdentifier() { + return this.get('localeIdentifier'); + } + + /** + * Gets the parse server version for this installation + * + * @property {string} parseVersion + * @static + */ + get parseVersion() { + return this.get('parseVersion'); + } + + /** + * This field is reserved for directing Parse to the push delivery network to be used. + * + * @property {string} pushType + * @static + */ + get pushType() { + return this.get('pushType'); + } + + /** + * Gets the time zone for this installation + * + * @property {string} timeZone + * @static + */ + get timeZone() { + return this.get('timeZone'); + } + + /** + * Returns the device types for used for Push Notifications. + * + *
+   * Parse.Installation.DEVICE_TYPES.IOS
+   * Parse.Installation.DEVICE_TYPES.MACOS
+   * Parse.Installation.DEVICE_TYPES.TVOS
+   * Parse.Installation.DEVICE_TYPES.FCM
+   * Parse.Installation.DEVICE_TYPES.ANDROID
+   * Parse.Installation.DEVICE_TYPES.WEB
+   * 
): Promise { + await super.save.apply(this, args); + await CoreManager.getInstallationController().updateInstallationOnDisk(this); + return this; + } + + /** + * Get the current Parse.Installation from disk. If doesn't exists, create an new installation. + * + *
+   * const installation = await Parse.Installation.currentInstallation();
+   * installation.set('deviceToken', '123');
+   * await installation.save();
+   * 
+ * + * @returns {Promise} A promise that resolves to the local installation object. + */ + static currentInstallation(): Promise { + return CoreManager.getInstallationController().currentInstallation(); + } +} + +ParseObject.registerSubclass('_Installation', ParseInstallation); + +module.exports = ParseInstallation; +export default ParseInstallation; diff --git a/src/__tests__/CoreManager-test.js b/src/__tests__/CoreManager-test.js index b08076bbe..d4744ae1e 100644 --- a/src/__tests__/CoreManager-test.js +++ b/src/__tests__/CoreManager-test.js @@ -136,9 +136,25 @@ describe('CoreManager', () => { 'InstallationController must implement currentInstallationId()' ); + expect(CoreManager.setInstallationController.bind(null, { + currentInstallationId: function () {}, + currentInstallation: function () {}, + })).toThrow( + 'InstallationController must implement updateInstallationOnDisk()' + ); + + expect(CoreManager.setInstallationController.bind(null, { + currentInstallationId: function () {}, + updateInstallationOnDisk: function () {}, + })).toThrow( + 'InstallationController must implement currentInstallation()' + ); + expect( CoreManager.setInstallationController.bind(null, { currentInstallationId: function () {}, + currentInstallation: function () {}, + updateInstallationOnDisk: function () {}, }) ).not.toThrow(); }); @@ -146,6 +162,8 @@ describe('CoreManager', () => { it('can set and get InstallationController', () => { const controller = { currentInstallationId: function () {}, + currentInstallation: function () {}, + updateInstallationOnDisk: function () {}, }; CoreManager.setInstallationController(controller); diff --git a/src/__tests__/EventuallyQueue-test.js b/src/__tests__/EventuallyQueue-test.js index 3b98d5022..da4486c42 100644 --- a/src/__tests__/EventuallyQueue-test.js +++ b/src/__tests__/EventuallyQueue-test.js @@ -57,6 +57,8 @@ CoreManager.setInstallationController({ currentInstallationId() { return Promise.resolve('iid'); }, + currentInstallation() {}, + updateInstallationOnDisk() {}, }); describe('EventuallyQueue', () => { diff --git a/src/__tests__/InstallationController-test.js b/src/__tests__/InstallationController-test.js index dcfad0a45..8c6422a2d 100644 --- a/src/__tests__/InstallationController-test.js +++ b/src/__tests__/InstallationController-test.js @@ -1,13 +1,22 @@ jest.dontMock('../CoreManager'); +jest.dontMock('../decode'); +jest.dontMock('../encode'); jest.dontMock('../InstallationController'); +jest.dontMock('../ObjectStateMutations'); +jest.dontMock('../ParseInstallation'); +jest.dontMock('../ParseObject'); +jest.dontMock('../ParseOp'); jest.dontMock('../Storage'); jest.dontMock('../StorageController.default'); +jest.dontMock('../SingleInstanceStateController'); +jest.dontMock('../UniqueInstanceStateController'); jest.mock('../uuid', () => { let value = 0; return () => value++ + ''; }); const CoreManager = require('../CoreManager'); +const ParseInstallation = require('../ParseInstallation'); const InstallationController = require('../InstallationController'); const Storage = require('../Storage'); @@ -21,48 +30,69 @@ describe('InstallationController', () => { InstallationController._clearCache(); }); - it('generates a new installation id when there is none', done => { - InstallationController.currentInstallationId().then(iid => { - expect(typeof iid).toBe('string'); - expect(iid.length).toBeGreaterThan(0); - done(); - }); + it('generates a new installation id when there is none', async () => { + const iid = await InstallationController.currentInstallationId(); + expect(typeof iid).toBe('string'); + expect(iid.length).toBeGreaterThan(0); }); - it('caches the installation id', done => { - let iid = null; - InstallationController.currentInstallationId() - .then(i => { - iid = i; - Storage._clear(); - return InstallationController.currentInstallationId(); - }) - .then(i => { - expect(i).toBe(iid); - done(); - }); + it('caches the installation id', async () => { + const iid = await InstallationController.currentInstallationId(); + Storage._clear(); + const i = await InstallationController.currentInstallationId(); + expect(i).toBe(iid); }); - it('permanently stores the installation id', done => { - let iid = null; - InstallationController.currentInstallationId() - .then(i => { - iid = i; - InstallationController._clearCache(); - return InstallationController.currentInstallationId(); - }) - .then(i => { - expect(i).toBe(iid); - done(); - }); + it('permanently stores the installation id', async () => { + const iid = await InstallationController.currentInstallationId(); + InstallationController._clearCache(); + const i = await InstallationController.currentInstallationId(); + expect(i).toBe(iid); }); - it('can set installation id', done => { + it('can set installation id', async () => { const iid = '12345678'; InstallationController._setInstallationIdCache(iid); - InstallationController.currentInstallationId().then(i => { - expect(i).toBe(iid); - done(); - }); + const i = await InstallationController.currentInstallationId(); + expect(i).toBe(iid); + }); + + it('generates a new installation when there is none', async () => { + const installation = await InstallationController.currentInstallation(); + expect(installation instanceof ParseInstallation).toBe(true); + expect(installation.deviceType).toBe('web'); + expect(installation.installationId).toBeDefined(); + }); + + it('caches the current installation', async () => { + const iid = 'cached-installation-id'; + InstallationController._setInstallationIdCache(iid); + const installation = await InstallationController.currentInstallation(); + Storage._clear(); + const i = await InstallationController.currentInstallation(); + expect(i.installationId).toEqual(installation.installationId); + }); + + it('permanently stores the current installation', async () => { + const iid = 'stored-installation-id'; + InstallationController._setInstallationIdCache(iid); + const installation = await InstallationController.currentInstallation(); + InstallationController._clearCache(); + const i = await InstallationController.currentInstallation(); + expect(i.installationId).toEqual(installation.installationId); + }); + + it('can update installation on disk', async () => { + const installationId = 'new-installation-id'; + const installation = new ParseInstallation({ installationId }); + InstallationController.updateInstallationOnDisk(installation); + const i = await InstallationController.currentInstallation(); + expect(i.installationId).toBe(installationId); + }); + + it('can handle cache not matching disk', async () => { + InstallationController._setCurrentInstallationCache(null, true); + const i = await InstallationController.currentInstallation(); + expect(i).toBeNull(); }); }); diff --git a/src/__tests__/Parse-test.js b/src/__tests__/Parse-test.js index d74b76373..119484592 100644 --- a/src/__tests__/Parse-test.js +++ b/src/__tests__/Parse-test.js @@ -240,6 +240,8 @@ describe('Parse module', () => { it('_getInstallationId', () => { const controller = { currentInstallationId: () => '1234', + currentInstallation: () => {}, + updateInstallationOnDisk: () => {}, }; CoreManager.setInstallationController(controller); expect(Parse._getInstallationId()).toBe('1234'); diff --git a/src/__tests__/ParseInstallation-test.js b/src/__tests__/ParseInstallation-test.js index 20c01d007..3bdc11fce 100644 --- a/src/__tests__/ParseInstallation-test.js +++ b/src/__tests__/ParseInstallation-test.js @@ -1,14 +1,20 @@ jest.dontMock('../CoreManager'); jest.dontMock('../decode'); +jest.dontMock('../LocalDatastore'); jest.dontMock('../ObjectStateMutations'); jest.dontMock('../ParseError'); jest.dontMock('../ParseObject'); jest.dontMock('../ParseOp'); jest.dontMock('../ParseInstallation'); +jest.dontMock('../promiseUtils'); +jest.dontMock('../RESTController'); +jest.dontMock('../TaskQueue'); jest.dontMock('../SingleInstanceStateController'); jest.dontMock('../UniqueInstanceStateController'); -const ParseInstallation = require('../ParseInstallation').default; +const LocalDatastore = require('../LocalDatastore'); +const ParseInstallation = require('../ParseInstallation'); +const CoreManager = require('../CoreManager'); describe('ParseInstallation', () => { it('can create ParseInstallation', () => { @@ -25,4 +31,73 @@ describe('ParseInstallation', () => { new ParseInstallation({ 'invalid#name': 'foo' }); }).toThrow("Can't create an invalid Installation"); }); + + it('can get device types', () => { + expect(ParseInstallation.DEVICE_TYPES.WEB).toEqual('web'); + expect(ParseInstallation.DEVICE_TYPES.IOS).toEqual('ios'); + expect(ParseInstallation.DEVICE_TYPES.MACOS).toEqual('macos'); + expect(ParseInstallation.DEVICE_TYPES.TVOS).toEqual('tvos'); + expect(ParseInstallation.DEVICE_TYPES.FCM).toEqual('fcm'); + expect(ParseInstallation.DEVICE_TYPES.ANDROID).toEqual('android'); + }); + + it('can retrieve getters', () => { + const data = { + deviceType: 'web', + installationId: '1234', + deviceToken: '1234', + badge: 1, + appIdentifier: 'com.parse.server', + appName: 'Parse JS SDK', + appVersion: '1.0.0', + parseVersion: '1.0.0', + localeIdentifier: 'en-US', + timeZone: 'GMT', + channels: ['test'], + GCMSenderId: '1234', + pushType: 'test', + }; + const installation = new ParseInstallation(data); + Object.keys(data).forEach(key => { + expect(installation[key]).toEqual(data[key]); + }); + }); + + it('can save to disk', async () => { + const InstallationController = { + async updateInstallationOnDisk() {}, + async currentInstallationId() {}, + async currentInstallation() {}, + }; + CoreManager.setInstallationController(InstallationController); + CoreManager.setRESTController({ + request() { + return Promise.resolve({}, 200); + }, + ajax() {}, + }); + CoreManager.setLocalDatastore(LocalDatastore); + jest.spyOn(InstallationController, 'updateInstallationOnDisk').mockImplementationOnce(() => {}); + const installation = new ParseInstallation(); + installation.set('deviceToken', '1234'); + await installation.save(); + expect(InstallationController.updateInstallationOnDisk).toHaveBeenCalledTimes(1); + }); + + it('can get current installation', async () => { + const InstallationController = { + async updateInstallationOnDisk() {}, + async currentInstallationId() {}, + async currentInstallation() {}, + }; + CoreManager.setInstallationController(InstallationController); + jest.spyOn(InstallationController, 'currentInstallation').mockImplementationOnce(() => { + const installation = new ParseInstallation({ deviceType: 'web', installationId: '1234' }); + return installation; + }); + const installation = await ParseInstallation.currentInstallation(); + expect(InstallationController.currentInstallation).toHaveBeenCalledTimes(1); + expect(installation.deviceType).toEqual('web'); + expect(installation.installationId).toEqual('1234'); + }); }); diff --git a/src/__tests__/ParseObject-test.js b/src/__tests__/ParseObject-test.js index 890d77ff4..a9474fef5 100644 --- a/src/__tests__/ParseObject-test.js +++ b/src/__tests__/ParseObject-test.js @@ -165,6 +165,8 @@ CoreManager.setInstallationController({ currentInstallationId() { return Promise.resolve('iid'); }, + currentInstallation() {}, + updateInstallationOnDisk() {}, }); CoreManager.set('APPLICATION_ID', 'A'); CoreManager.set('JAVASCRIPT_KEY', 'B'); diff --git a/src/__tests__/RESTController-test.js b/src/__tests__/RESTController-test.js index 640d83bc5..4c1890379 100644 --- a/src/__tests__/RESTController-test.js +++ b/src/__tests__/RESTController-test.js @@ -17,6 +17,8 @@ CoreManager.setInstallationController({ currentInstallationId() { return Promise.resolve('iid'); }, + currentInstallation() {}, + updateInstallationOnDisk() {}, }); CoreManager.set('APPLICATION_ID', 'A'); CoreManager.set('JAVASCRIPT_KEY', 'B'); diff --git a/types/CoreManager.d.ts b/types/CoreManager.d.ts index 2fa05b5d3..4c1f949a2 100644 --- a/types/CoreManager.d.ts +++ b/types/CoreManager.d.ts @@ -14,6 +14,7 @@ import type { HookDeclaration, HookDeleteArg } from './ParseHooks'; import type ParseConfig from './ParseConfig'; import type LiveQueryClient from './LiveQueryClient'; import type ParseSchema from './ParseSchema'; +import type ParseInstallation from './ParseInstallation'; type AnalyticsController = { track: (name: string, dimensions: { [key: string]: string; @@ -54,6 +55,8 @@ type FileController = { }; type InstallationController = { currentInstallationId: () => Promise; + currentInstallation: () => Promise; + updateInstallationOnDisk: (installation: ParseInstallation) => Promise; }; type ObjectController = { fetch: (object: ParseObject | Array, forceFetch: boolean, options: RequestOptions) => Promise; diff --git a/types/InstallationController.d.ts b/types/InstallationController.d.ts index cb0ff5c3b..1409c39db 100644 --- a/types/InstallationController.d.ts +++ b/types/InstallationController.d.ts @@ -1 +1,10 @@ -export {}; +import ParseInstallation from './ParseInstallation'; +declare const InstallationController: { + updateInstallationOnDisk(installation: ParseInstallation): Promise; + currentInstallationId(): Promise; + currentInstallation(): Promise; + _clearCache(): void; + _setInstallationIdCache(iid: string): void; + _setCurrentInstallationCache(installation: ParseInstallation, matchesDisk?: boolean): void; +}; +export default InstallationController; diff --git a/types/ParseInstallation.d.ts b/types/ParseInstallation.d.ts index 2a512fc6b..c74a47ceb 100644 --- a/types/ParseInstallation.d.ts +++ b/types/ParseInstallation.d.ts @@ -1,6 +1,156 @@ -// @ts-nocheck -export default class Installation extends ParseObject { - constructor(attributes: AttributeMap | null); -} import ParseObject from './ParseObject'; -import { AttributeMap } from './ObjectStateMutations'; +import type { AttributeMap } from './ObjectStateMutations'; +type DeviceInterface = { + IOS: string; + MACOS: string; + TVOS: string; + FCM: string; + ANDROID: string; + WEB: string; +}; +/** + * Parse.Installation is a local representation of installation data that can be saved and retrieved from the Parse cloud. + * This class is a subclass of a Parse.Object, and retains the same functionality of a Parse.Object, but also extends it with installation-specific features. + * + *

A valid Parse.Installation can only be instantiated via Parse.Installation.currentInstallation() + * + * Parse.Installation objects which have a valid deviceToken and are saved to the Parse cloud can be used to target push notifications. + *

+ * + * @alias Parse.Installation + */ +declare class ParseInstallation extends ParseObject { + /** + * @param {object} attributes The initial set of data to store in the object. + */ + constructor(attributes?: AttributeMap); + /** + * A unique identifier for this installation’s client application. In iOS, this is the Bundle Identifier. + * + * @property {string} appIdentifier + * @static + */ + get appIdentifier(): any; + /** + * The version string of the client application to which this installation belongs. + * + * @property {string} appVersion + * @static + */ + get appVersion(): any; + /** + * The display name of the client application to which this installation belongs. + * + * @property {string} appName + * @static + */ + get appName(): any; + /** + * The current value of the icon badge for iOS apps. + * Changes to this value on the server will be used + * for future badge-increment push notifications. + * + * @property {number} badge + * @static + */ + get badge(): any; + /** + * An array of the channels to which a device is currently subscribed. + * + * @property {string[]} channels + * @static + */ + get channels(): any; + /** + * Token used to deliver push notifications to the device. + * + * @property {string} deviceToken + * @static + */ + get deviceToken(): any; + /** + * The type of device, “ios”, “android”, “web”, etc. + * + * @property {string} deviceType + * @static + */ + get deviceType(): any; + /** + * Gets the GCM sender identifier for this installation + * + * @property {string} GCMSenderId + * @static + */ + get GCMSenderId(): any; + /** + * Universally Unique Identifier (UUID) for the device used by Parse. It must be unique across all of an app’s installations. + * + * @property {string} installationId + * @static + */ + get installationId(): any; + /** + * Gets the local identifier for this installation + * + * @property {string} localeIdentifier + * @static + */ + get localeIdentifier(): any; + /** + * Gets the parse server version for this installation + * + * @property {string} parseVersion + * @static + */ + get parseVersion(): any; + /** + * This field is reserved for directing Parse to the push delivery network to be used. + * + * @property {string} pushType + * @static + */ + get pushType(): any; + /** + * Gets the time zone for this installation + * + * @property {string} timeZone + * @static + */ + get timeZone(): any; + /** + * Returns the device types for used for Push Notifications. + * + *
+     * Parse.Installation.DEVICE_TYPES.IOS
+     * Parse.Installation.DEVICE_TYPES.MACOS
+     * Parse.Installation.DEVICE_TYPES.TVOS
+     * Parse.Installation.DEVICE_TYPES.FCM
+     * Parse.Installation.DEVICE_TYPES.ANDROID
+     * Parse.Installation.DEVICE_TYPES.WEB
+     * 
): Promise; + /** + * Get the current Parse.Installation from disk. If doesn't exists, create an new installation. + * + *
+     * const installation = await Parse.Installation.currentInstallation();
+     * installation.set('deviceToken', '123');
+     * await installation.save();
+     * 
+ * + * @returns {Promise} A promise that resolves to the local installation object. + */ + static currentInstallation(): Promise; +} +export default ParseInstallation;