├── .gitignore ├── .npmignore ├── .vscode └── tasks.json ├── README.md ├── gulpfile.js ├── package-lock.json ├── package.json ├── src ├── RxParse.ts ├── internal │ ├── ParseClientPlugins.ts │ ├── cloud │ │ ├── controller │ │ │ ├── IParseCloudController.ts │ │ │ └── ParseCloudController.ts │ │ └── encoding │ │ │ ├── IParseCloudDecoder.ts │ │ │ └── ParseCloudDecoder.ts │ ├── command │ │ ├── IParseCommandRunner.ts │ │ ├── ParseCommand.ts │ │ ├── ParseCommandResponse.ts │ │ └── ParseCommandRunner.ts │ ├── encoding │ │ ├── IParseDecoder.ts │ │ ├── IParseEncoder.ts │ │ ├── IParseObjectDecoder.ts │ │ ├── ParseDecoder.ts │ │ ├── ParseEncoder.ts │ │ └── ParseObjectDecoder.ts │ ├── httpClient │ │ ├── AxiosRxHttpClient.ts │ │ ├── HttpRequest.ts │ │ ├── HttpResponse.ts │ │ ├── IRxHttpClient.ts │ │ └── RxHttpClient.ts │ ├── object │ │ ├── controller │ │ │ ├── IParseObjectController.ts │ │ │ └── ParseObjectController.ts │ │ └── state │ │ │ ├── IObjectState.ts │ │ │ └── MutableObjectState.ts │ ├── operation │ │ ├── IParseFieldOperation.ts │ │ ├── ParseAddOperation.ts │ │ ├── ParseDeleteOperation.ts │ │ ├── ParseRemoveOperation.ts │ │ └── ParseSetOperation.ts │ ├── query │ │ └── controller │ │ │ ├── IQueryController.ts │ │ │ └── QueryController.ts │ ├── storage │ │ ├── IStorage.ts │ │ └── controller │ │ │ ├── IStorageController.ts │ │ │ └── StorageController.ts │ ├── tool │ │ └── controller │ │ │ ├── IToolController.ts │ │ │ └── ToolController.ts │ ├── user │ │ └── controller │ │ │ ├── IUserController.ts │ │ │ └── UserController.ts │ └── websocket │ │ ├── IRxWebSocketClient.ts │ │ ├── IWebSocketClient.ts │ │ └── controller │ │ ├── IRxWebSocketController.ts │ │ └── RxWebSocketController.ts └── public │ ├── RxParseACL.ts │ ├── RxParseClient.ts │ ├── RxParseCloud.ts │ ├── RxParseInstallation.ts │ ├── RxParseObject.ts │ ├── RxParsePush.ts │ ├── RxParseQuery.ts │ ├── RxParseRole.ts │ └── RxParseUser.ts ├── test ├── client │ └── mutipleApp.ts ├── cloud │ ├── cloud.ts │ └── functionTest.ts ├── mocha.opts ├── object │ ├── deleteTest.ts │ ├── saveTest.ts │ └── updateTest.ts ├── push │ ├── installationTest.ts │ └── pushTest.ts ├── query │ ├── arrayTest.ts │ ├── include.ts │ ├── orTest.ts │ └── whereTest.ts ├── role │ └── roleTest.ts ├── rx │ └── timer.ts ├── user │ ├── logInTest.ts │ └── signUpTest.ts ├── utils │ ├── config.ts │ ├── init.ts │ └── random.ts └── websocket │ └── NodeJSWebSocketClient.ts ├── tsconfig.json └── typings.json /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | **/.vs 3 | **/.vscode 4 | !**/.vscode/tasks.json 5 | .idea 6 | typings/ 7 | release/ 8 | test/*.js 9 | dist/ 10 | out/ 11 | **/.leancloud 12 | .bin/ -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | **/.vs 3 | **/.vscode 4 | .idea 5 | release/ 6 | test/ 7 | out/ 8 | **/.leancloud 9 | .travis.yml 10 | build_notification.ps1 -------------------------------------------------------------------------------- /.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | // See https://go.microsoft.com/fwlink/?LinkId=733558 3 | // for the documentation about the tasks.json format 4 | "version": "2.0.0", 5 | "label": "typescript", 6 | "tasks": [{ 7 | "label": "typescript", 8 | "type": "shell", 9 | "command": "tsc", 10 | "problemMatcher": "$tsc", 11 | "args": [ 12 | "-p", 13 | "\"${workspaceFolder}/tsconfig.json\"" 14 | ], 15 | "group": { 16 | "kind": "build", 17 | "isDefault": true 18 | } 19 | }, 20 | { 21 | "label": "build dev", 22 | "type": "shell", 23 | "command": "tsc" 24 | }, 25 | { 26 | "label": "gulp dev", 27 | "type": "shell", 28 | "command": "gulp devCopy" 29 | }, 30 | { 31 | "label": "pretest", 32 | "dependsOn": ["build dev", "gulp dev"] 33 | }, 34 | { 35 | "label": "test init", 36 | "type": "shell", 37 | "command": "node out/test/utils/init.js" 38 | }, 39 | { 40 | "label": "mocha run", 41 | "type": "shell", 42 | "command": "mocha --timeout 30000 $(find out/test -name '*.js')" 43 | }, 44 | { 45 | "label": "test", 46 | "dependsOn": ["test init", "mocha run"] 47 | } 48 | ] 49 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # TypeScript SDK for Parse Server wrapped by RxJS 2 | -------------------------------------------------------------------------------- /gulpfile.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | var gulp = require("gulp"); 4 | var jsdoc = require('gulp-jsdoc3'); 5 | var ts = require("gulp-typescript"); 6 | var tsProject = ts.createProject("tsconfig.json", {}); 7 | 8 | gulp.task("source", function () { 9 | return gulp.src("src/**/*") 10 | .pipe(tsProject()) 11 | .pipe(gulp.dest("dist")); 12 | }); 13 | 14 | gulp.task('releaseCopy', function () { 15 | return gulp.src('package.json') 16 | .pipe(gulp.dest('dist')); 17 | }); 18 | 19 | gulp.task('devCopy', function () { 20 | return gulp.src('package.json') 21 | .pipe(gulp.dest('.bin/src')); 22 | }); 23 | 24 | gulp.task('doc', gulp.series('source', function (done) { 25 | gulp.src(['README.md', './dist/**/*.js'], { 26 | read: false 27 | }).pipe(jsdoc(done)); 28 | })); 29 | 30 | gulp.task('default', gulp.series(gulp.parallel('source', 'releaseCopy'), function (done) { 31 | done(); 32 | })); 33 | 34 | gulp.task('dev', gulp.series(gulp.parallel('source', 'devCopy'), function (done) { 35 | done(); 36 | })); 37 | 38 | gulp.task('docs', gulp.series(gulp.parallel('source', 'doc'), function (done) { 39 | done(); 40 | })); -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@rxparse/rx-parse", 3 | "version": "0.1.6", 4 | "repository": { 5 | "type": "git", 6 | "url": "https://github.com/RxParse/Parse-SDK-ts" 7 | }, 8 | "description": "Parse server TypeScript SDK", 9 | "scripts": { 10 | "ready": "tsc && node .bin/test/utils/init.js && mocha --timeout 30000 $(find .bin/test -name '*.js') && gulp", 11 | "pretest": "tsc && gulp devCopy", 12 | "test": "node .bin/test/utils/init.js && mocha --timeout 30000 $(find .bin/test -name '*.js')", 13 | "prepublish": "tsc && gulp && ls" 14 | }, 15 | "author": "Wu Jun", 16 | "license": "MIT", 17 | "devDependencies": { 18 | "@types/chai": "^4.1.5", 19 | "@types/mocha": "^5.2.5", 20 | "@types/superagent": "^3.8.4", 21 | "@types/ws": "6.0.1", 22 | "chai": "^4.2.0", 23 | "gulp": "^4.0.0", 24 | "gulp-jsdoc3": "^2.0.0", 25 | "gulp-typescript": "^5.0.0-alpha.3", 26 | "mocha": "^5.2.0", 27 | "tsconfig-paths": "^3.6.0", 28 | "typescript": "^3.0.3", 29 | "typings": "^2.0.0", 30 | "ws": "^6.0.0" 31 | }, 32 | "dependencies": { 33 | "axios": "^0.18.0", 34 | "jstz": "^2.0.0", 35 | "rxjs": "^6.3.3", 36 | "superagent": "^4.0.0-beta.5" 37 | }, 38 | "main": "./dist/RxParse.js", 39 | "typings": "./dist/RxParse.d.ts", 40 | "types": "./dist/RxParse.d.ts", 41 | "engines": { 42 | "node": "6.x" 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/RxParse.ts: -------------------------------------------------------------------------------- 1 | export { IStorage } from './internal/storage/IStorage'; 2 | export { IWebSocketClient } from './internal/websocket/IWebSocketClient'; 3 | export { RxParseObject, RxParseObject as Object, ICanSaved, StorageObject } from './public/RxParseObject'; 4 | export { ParseClient, ParseClientConfig, ParseApp, ParseAppConfig } from './public/RxParseClient'; 5 | export { RxParseQuery, RxParseQuery as Query, RxParseLiveQuery } from './public/RxParseQuery'; 6 | export { RxParseUser, RxParseUser as User } from './public/RxParseUser'; 7 | export { RxParseACL, RxParseACL as ACL } from './public/RxParseACL'; 8 | export { RxParseRole } from './public/RxParseRole'; 9 | export { RxParseCloud } from './public/RxParseCloud'; 10 | export { RxParseInstallation } from './public/RxParseInstallation'; 11 | export { RxParsePush } from './public/RxParsePush'; 12 | -------------------------------------------------------------------------------- /src/internal/ParseClientPlugins.ts: -------------------------------------------------------------------------------- 1 | import { HttpRequest } from './httpClient/HttpRequest'; 2 | import { AxiosRxHttpClient } from './httpClient/AxiosRxHttpClient'; 3 | import { IRxHttpClient } from './httpClient/IRxHttpClient'; 4 | import { RxHttpClient } from './httpClient/RxHttpClient'; 5 | import { ParseCommand } from './command/ParseCommand'; 6 | import { IParseCommandRunner } from './command/IParseCommandRunner'; 7 | import { ParseCommandRunner } from './command/ParseCommandRunner'; 8 | import { IObjectController } from './object/controller/IParseObjectController'; 9 | import { ObjectController } from './object/controller/ParseObjectController'; 10 | import { IUserController } from './user/controller/IUserController'; 11 | import { UserController } from './user/controller/UserController'; 12 | import { IQueryController } from './query/controller/IQueryController'; 13 | import { QueryController } from './query/controller/QueryController'; 14 | import { IParseCloudController } from './cloud/controller/IParseCloudController'; 15 | import { ParseCloudController } from './cloud/controller/ParseCloudController'; 16 | 17 | import { IToolController } from './tool/controller/IToolController'; 18 | import { ToolController } from './tool/controller/ToolController'; 19 | 20 | import { IParseEncoder } from './encoding/IParseEncoder'; 21 | import { ParseEncoder } from './encoding/ParseEncoder'; 22 | import { IParseDecoder } from './encoding/IParseDecoder'; 23 | import { ParseDecoder } from './encoding/ParseDecoder'; 24 | import { IParseObjectDecoder } from './encoding/IParseObjectDecoder'; 25 | import { ParseObjectDecoder } from './encoding/ParseObjectDecoder'; 26 | import { IParseCloudDecoder } from './cloud/encoding/IParseCloudDecoder'; 27 | import { ParseCloudDecoder } from './cloud/encoding/ParseCloudDecoder'; 28 | 29 | import { IStorage } from './storage/IStorage'; 30 | import { IStorageController } from './storage/controller/IStorageController'; 31 | import { StorageController } from './storage/controller/StorageController'; 32 | 33 | import { IRxWebSocketClient } from './websocket/IRxWebSocketClient'; 34 | import { IWebSocketClient } from './websocket/IWebSocketClient'; 35 | import { RxWebSocketController } from './websocket/controller/RxWebSocketController'; 36 | import { IRxWebSocketController } from './websocket/controller/IRxWebSocketController'; 37 | 38 | import { ParseClient } from 'public/RxParseClient'; 39 | 40 | export /** 41 | * SDKPlugins 42 | */ 43 | class SDKPlugins { 44 | private _version = 1; 45 | private _httpClient: IRxHttpClient; 46 | private _commandRunner: IParseCommandRunner; 47 | private _objectController: IObjectController; 48 | private _queryController: IQueryController; 49 | private _userController: IUserController; 50 | private _cloudController: IParseCloudController; 51 | private _encoder: IParseEncoder; 52 | private _decoder: IParseDecoder; 53 | private _objectDecoder: IParseObjectDecoder; 54 | private _cloudDecoder: IParseCloudDecoder; 55 | private _toolController: IToolController; 56 | private _storageController: IStorageController; 57 | private _storageProvider: IStorage; 58 | private _webSocketProvider: IWebSocketClient; 59 | private _rxWebSocketController: IRxWebSocketController; 60 | private static _sdkPluginsInstance: SDKPlugins; 61 | 62 | constructor(version?: number) { 63 | this._version = version; 64 | } 65 | 66 | get httpClient() { 67 | if (this._httpClient == null) { 68 | this._httpClient = new AxiosRxHttpClient(); 69 | } 70 | return this._httpClient; 71 | } 72 | 73 | get commandRunner() { 74 | if (this._commandRunner == null) { 75 | this._commandRunner = new ParseCommandRunner(this.httpClient); 76 | } 77 | return this._commandRunner; 78 | } 79 | 80 | get objectController() { 81 | if (this._objectController == null) { 82 | this._objectController = new ObjectController(this.commandRunner); 83 | } 84 | return this._objectController; 85 | } 86 | 87 | get userController() { 88 | if (this._userController == null) { 89 | this._userController = new UserController(this.commandRunner); 90 | } 91 | return this._userController; 92 | } 93 | 94 | get queryController() { 95 | if (this._queryController == null) { 96 | this._queryController = new QueryController(this.commandRunner); 97 | } 98 | return this._queryController; 99 | } 100 | 101 | get cloudController() { 102 | if (this._cloudController == null) { 103 | this._cloudController = new ParseCloudController(this.CloudDecoder); 104 | } 105 | return this._cloudController; 106 | } 107 | 108 | get ToolControllerInstance() { 109 | if (this._toolController == null) { 110 | this._toolController = new ToolController(); 111 | } 112 | return this._toolController; 113 | } 114 | 115 | get LocalStorageControllerInstance() { 116 | if (this._storageController == null) { 117 | if (this.StorageProvider != null) 118 | this._storageController = new StorageController(this.StorageProvider); 119 | } 120 | return this._storageController; 121 | } 122 | 123 | get hasStorage() { 124 | return this.StorageProvider != null; 125 | } 126 | get StorageProvider() { 127 | return this._storageProvider; 128 | } 129 | 130 | set StorageProvider(provider: IStorage) { 131 | this._storageProvider = provider; 132 | } 133 | 134 | set LocalStorageControllerInstance(controller: IStorageController) { 135 | this._storageController = controller; 136 | } 137 | 138 | get WebSocketProvider() { 139 | return this._webSocketProvider; 140 | } 141 | set WebSocketProvider(provider: IWebSocketClient) { 142 | this._webSocketProvider = provider; 143 | } 144 | 145 | get WebSocketController() { 146 | if (this._rxWebSocketController == null) { 147 | if (this._webSocketProvider != null) { 148 | this._rxWebSocketController = new RxWebSocketController(this._webSocketProvider); 149 | } else { 150 | throw new Error(`you must set the websocket when invoke RxParseClient.init{ 151 | ... 152 | plugins?: { 153 | ... 154 | websocket?: IWebSocketClient 155 | ... 156 | } 157 | ... 158 | }`); 159 | } 160 | } 161 | return this._rxWebSocketController; 162 | } 163 | 164 | set WebSocketController(provider: IRxWebSocketController) { 165 | this._rxWebSocketController = provider; 166 | } 167 | 168 | get Encoder() { 169 | if (this._encoder == null) { 170 | this._encoder = new ParseEncoder(); 171 | } 172 | return this._encoder; 173 | } 174 | 175 | get Decoder() { 176 | if (this._decoder == null) { 177 | this._decoder = new ParseDecoder(); 178 | } 179 | return this._decoder; 180 | } 181 | 182 | get ObjectDecoder() { 183 | if (this._objectDecoder == null) { 184 | this._objectDecoder = new ParseObjectDecoder(); 185 | } 186 | return this._objectDecoder; 187 | } 188 | 189 | get CloudDecoder() { 190 | if (this._cloudDecoder == null) { 191 | this._cloudDecoder = new ParseCloudDecoder(this.Decoder, this.ObjectDecoder); 192 | } 193 | return this._cloudDecoder; 194 | } 195 | 196 | static get instance(): SDKPlugins { 197 | if (SDKPlugins._sdkPluginsInstance == null) 198 | SDKPlugins._sdkPluginsInstance = new SDKPlugins(1); 199 | return SDKPlugins._sdkPluginsInstance; 200 | } 201 | 202 | static set version(version: number) { 203 | SDKPlugins._sdkPluginsInstance = new SDKPlugins(version); 204 | } 205 | } 206 | -------------------------------------------------------------------------------- /src/internal/cloud/controller/IParseCloudController.ts: -------------------------------------------------------------------------------- 1 | import { Observable } from 'rxjs'; 2 | 3 | export interface IParseCloudController { 4 | callFunction(name: string, 5 | parameters?: { [key: string]: any }, 6 | sessionToken?: string): Observable<{ [key: string]: any }>; 7 | } -------------------------------------------------------------------------------- /src/internal/cloud/controller/ParseCloudController.ts: -------------------------------------------------------------------------------- 1 | import { map } from 'rxjs/operators'; 2 | import { IParseCloudController } from './IParseCloudController' 3 | import { Observable } from 'rxjs'; 4 | import { ParseCommand } from '../../command/ParseCommand'; 5 | import { SDKPlugins } from '../../ParseClientPlugins'; 6 | import { IParseCloudDecoder } from '../encoding/IParseCloudDecoder'; 7 | 8 | export class ParseCloudController implements IParseCloudController { 9 | private _LeanEngineDecoder: IParseCloudDecoder; 10 | 11 | constructor(LeanEngineDecoder: IParseCloudDecoder) { 12 | this._LeanEngineDecoder = LeanEngineDecoder; 13 | } 14 | 15 | callFunction(name: string, 16 | parameters?: { [key: string]: any }, 17 | sessionToken?: string): Observable<{ [key: string]: any }> { 18 | 19 | let cmd = new ParseCommand({ 20 | relativeUrl: `/functions/${name}`, 21 | method: 'POST', 22 | data: parameters, 23 | sessionToken: sessionToken 24 | }); 25 | 26 | return SDKPlugins.instance.commandRunner.runRxCommand(cmd).pipe(map(res => { 27 | let result = this._LeanEngineDecoder.decodeDictionary(res.body.result); 28 | return result; 29 | })); 30 | } 31 | } -------------------------------------------------------------------------------- /src/internal/cloud/encoding/IParseCloudDecoder.ts: -------------------------------------------------------------------------------- 1 | import { IObjectState } from '../../object/state/IObjectState'; 2 | 3 | export interface IParseCloudDecoder { 4 | decodeParseObject(serverResponse: { [key: string]: any }): IObjectState; 5 | decodeDictionary(serverResponse: { [key: string]: any }): { [key: string]: any }; 6 | } -------------------------------------------------------------------------------- /src/internal/cloud/encoding/ParseCloudDecoder.ts: -------------------------------------------------------------------------------- 1 | import { IParseCloudDecoder } from './IParseCloudDecoder'; 2 | import { IParseDecoder } from '../../encoding/IParseDecoder'; 3 | import { IParseObjectDecoder } from '../../encoding/IParseObjectDecoder'; 4 | import { IObjectState } from '../../object/state/IObjectState'; 5 | 6 | export class ParseCloudDecoder implements IParseCloudDecoder { 7 | 8 | protected _decoder: IParseDecoder; 9 | protected _objectDecoder: IParseObjectDecoder; 10 | 11 | constructor(decoder: IParseDecoder, objectDecoder: IParseObjectDecoder) { 12 | this._decoder = decoder; 13 | this._objectDecoder = objectDecoder; 14 | } 15 | 16 | decodeParseObject(serverResponse: { [key: string]: any }): IObjectState { 17 | return this._objectDecoder.decode(serverResponse, this._decoder); 18 | } 19 | 20 | decodeDictionary(serverResponse: { [key: string]: any }): { [key: string]: any } { 21 | return this._decoder.decode(serverResponse); 22 | } 23 | } -------------------------------------------------------------------------------- /src/internal/command/IParseCommandRunner.ts: -------------------------------------------------------------------------------- 1 | import { Observable } from 'rxjs'; 2 | import { ParseCommand } from './ParseCommand'; 3 | import { ParseCommandResponse } from './ParseCommandResponse'; 4 | 5 | export interface IParseCommandRunner { 6 | runRxCommand(command: ParseCommand): Observable; 7 | } -------------------------------------------------------------------------------- /src/internal/command/ParseCommand.ts: -------------------------------------------------------------------------------- 1 | import { HttpRequest } from '../httpClient/HttpRequest'; 2 | import { ParseClient } from '../../RxParse'; 3 | 4 | export class ParseCommand extends HttpRequest { 5 | relativeUrl: string; 6 | sessionToken: string; 7 | contentType: string; 8 | 9 | constructor(options?: any) { 10 | super(); 11 | this.data = {}; 12 | if (options != null) { 13 | this.relativeUrl = options.relativeUrl; 14 | if (this.relativeUrl == null || typeof this.relativeUrl == 'undefined') throw new Error('command must have a relative url.'); 15 | let app = ParseClient.instance.currentApp; 16 | 17 | if (options.app != null) { 18 | app = options.app; 19 | } 20 | this.url = `${app.pureServerURL}/${this.clearHeadSlashes(this.relativeUrl)}`; 21 | this.url = encodeURI(this.url); 22 | this.method = options.method; 23 | this.data = options.data; 24 | this.headers = app.httpHeaders; 25 | if (options.headers != null) { 26 | for (let key in options.headers) { 27 | this.headers[key] = options.headers[key]; 28 | } 29 | } 30 | if (options.sessionToken != null) { 31 | this.sessionToken = options.sessionToken; 32 | this.headers['X-Parse-Session-Token'] = options.sessionToken; 33 | } 34 | if (options.contentType != null) { 35 | this.contentType = options.contentType; 36 | this.headers['Content-Type'] = options.contentType; 37 | } 38 | } 39 | } 40 | 41 | attribute(key: string, value: any) { 42 | this.data[key] = value; 43 | return this; 44 | } 45 | 46 | clearHeadSlashes(url: string): string { 47 | if (url.startsWith('/')) { 48 | url = url.substring(1, url.length); 49 | return this.clearHeadSlashes(url); 50 | } 51 | else 52 | return url; 53 | } 54 | } -------------------------------------------------------------------------------- /src/internal/command/ParseCommandResponse.ts: -------------------------------------------------------------------------------- 1 | import { HttpResponse } from '../httpClient/HttpResponse'; 2 | 3 | /** 4 | * 5 | * 6 | * @export 7 | * @class ParseCommandResponse 8 | * @extends {HttpResponse} 9 | */ 10 | export class ParseCommandResponse extends HttpResponse { 11 | constructor(base: HttpResponse) { 12 | super(); 13 | this.body = base.body; 14 | this.statusCode = base.statusCode; 15 | } 16 | } -------------------------------------------------------------------------------- /src/internal/command/ParseCommandRunner.ts: -------------------------------------------------------------------------------- 1 | import { map, catchError } from 'rxjs/operators'; 2 | import { Observable } from 'rxjs'; 3 | import { ParseCommand } from './ParseCommand'; 4 | import { ParseCommandResponse } from './ParseCommandResponse'; 5 | import { IParseCommandRunner } from './IParseCommandRunner'; 6 | import { IRxHttpClient } from '../httpClient/IRxHttpClient'; 7 | import { ParseClient } from '../../public/RxParseClient'; 8 | 9 | export class ParseCommandRunner implements IParseCommandRunner { 10 | 11 | private _IRxHttpClient: IRxHttpClient; 12 | 13 | constructor(rxHttpClient: IRxHttpClient) { 14 | this._IRxHttpClient = rxHttpClient; 15 | } 16 | 17 | runRxCommand(command: ParseCommand): Observable { 18 | ParseClient.printHttpLog(command); 19 | return this._IRxHttpClient.execute(command).pipe(map(res => { 20 | ParseClient.printHttpLog(null, res); 21 | return new ParseCommandResponse(res); 22 | })).pipe(catchError((errorRes) => { 23 | return Observable.throw(errorRes); 24 | })); 25 | } 26 | } -------------------------------------------------------------------------------- /src/internal/encoding/IParseDecoder.ts: -------------------------------------------------------------------------------- 1 | 2 | export interface IParseDecoder { 3 | decode(data: { [key: string]: any }): { [key: string]: any }; 4 | decodeItem(data: any): any; 5 | } -------------------------------------------------------------------------------- /src/internal/encoding/IParseEncoder.ts: -------------------------------------------------------------------------------- 1 | export interface IParseEncoder { 2 | encode(value: any): any; 3 | isValidType(value: any): boolean; 4 | } -------------------------------------------------------------------------------- /src/internal/encoding/IParseObjectDecoder.ts: -------------------------------------------------------------------------------- 1 | import { IObjectState } from '../object/state/IObjectState'; 2 | import { IParseDecoder } from './IParseDecoder'; 3 | 4 | export interface IParseObjectDecoder { 5 | decode(serverResult: any, decoder: IParseDecoder): IObjectState; 6 | } -------------------------------------------------------------------------------- /src/internal/encoding/ParseDecoder.ts: -------------------------------------------------------------------------------- 1 | import { IParseDecoder } from './IParseDecoder'; 2 | import { RxParseObject, RxParseACL, RxParseUser } from '../../RxParse'; 3 | 4 | export class ParseDecoder implements IParseDecoder { 5 | 6 | decode(data: { [key: string]: any }): { [key: string]: any } { 7 | let mutableData = data; 8 | let result: { [key: string]: any } = {}; 9 | for (let key in mutableData) { 10 | result[key] = this.extractFromDictionary(mutableData, key, (v) => { 11 | if (Object.prototype.hasOwnProperty.call(v, '__type') || Object.prototype.hasOwnProperty.call(v, 'className')) { 12 | return this.decodeItem(v); 13 | } else { 14 | return v; 15 | } 16 | }); 17 | } 18 | return result; 19 | } 20 | 21 | decodeItem(data: any): any { 22 | 23 | if (data == null) { 24 | return null; 25 | } 26 | 27 | if (this.isBuiltInType(data)) 28 | return data; 29 | 30 | if (Array.isArray(data)) { 31 | 32 | return data.map(item => { 33 | return this.decodeItem(item); 34 | }); 35 | } 36 | 37 | let dict = data as { [key: string]: any }; 38 | 39 | if (!Object.prototype.hasOwnProperty.call(dict, '__type')) { 40 | let newDict: { [key: string]: any } = {}; 41 | for (let key in dict) { 42 | let value = dict[key]; 43 | newDict[key] = this.decodeItem(value); 44 | } 45 | return newDict; 46 | } else { 47 | let typeString = dict['__type']; 48 | if (typeString == 'Date') { 49 | let dt: Date = new Date(dict["iso"]); 50 | return dt; 51 | } else if (typeString == 'Pointer') { 52 | return this.decodePointer(dict['className'], dict['objectId']); 53 | } 54 | } 55 | 56 | return data; 57 | } 58 | protected decodePointer(className: string, objectId: string) { 59 | if (className == '_User') { 60 | return RxParseUser.createWithoutData(objectId); 61 | } 62 | return RxParseObject.createWithoutData(className, objectId); 63 | } 64 | 65 | protected extractFromDictionary(data: { [key: string]: any }, key: string, convertor: (value: any) => any) { 66 | if (Object.prototype.hasOwnProperty.call(data, key)) { 67 | let v = data[key]; 68 | let result = convertor(v); 69 | return v; 70 | } 71 | return null; 72 | } 73 | private isValidType(value: any): boolean { 74 | return value == null || 75 | value instanceof String || 76 | value instanceof RxParseObject || 77 | value instanceof RxParseACL || 78 | value instanceof Date; 79 | } 80 | 81 | private isBuiltInType(value: any): boolean { 82 | return typeof value == 'number' || 83 | typeof value == 'string' || 84 | typeof value == 'boolean'; 85 | } 86 | } -------------------------------------------------------------------------------- /src/internal/encoding/ParseEncoder.ts: -------------------------------------------------------------------------------- 1 | import { RxParseObject, RxParseACL } from '../../RxParse'; 2 | import { IParseEncoder } from './IParseEncoder'; 3 | 4 | /** 5 | * 6 | * 7 | * @export 8 | * @class ParseEncoder 9 | * @implements {IParseEncoder} 10 | */ 11 | export class ParseEncoder implements IParseEncoder { 12 | 13 | constructor() { 14 | } 15 | 16 | encode(value: any): any { 17 | if (value instanceof Map) { 18 | let encodedDictionary = {}; 19 | value.forEach((v, k, m) => { 20 | let encodedV = this.encode(v); 21 | encodedDictionary[k] = encodedV; 22 | }); 23 | return encodedDictionary; 24 | } else if (Array.isArray(value)) { 25 | return this.encodeList(value); 26 | } else if (value instanceof Date) { 27 | return { '__type': 'Date', 'iso': value.toJSON() }; 28 | } else if (value instanceof RxParseObject) { 29 | return { 30 | __type: "Pointer", 31 | className: value.className, 32 | objectId: value.objectId 33 | }; 34 | } else if (typeof value.encode === 'function') { 35 | return value.encode(); 36 | } 37 | 38 | return value; 39 | } 40 | 41 | encodeList(list: Array): Array { 42 | return list.map(item => { 43 | return this.encode(item); 44 | }); 45 | } 46 | 47 | isValidType(value: any): boolean { 48 | return value != null || 49 | value != undefined || 50 | value instanceof String || 51 | value instanceof RxParseObject || 52 | value instanceof RxParseACL || 53 | value instanceof Date || 54 | value instanceof Map || 55 | Array.isArray(value); 56 | } 57 | } -------------------------------------------------------------------------------- /src/internal/encoding/ParseObjectDecoder.ts: -------------------------------------------------------------------------------- 1 | import { IObjectState } from '../object/state/IObjectState'; 2 | import { MutableObjectState } from '../object/state/MutableObjectState'; 3 | import { IParseObjectDecoder } from './IParseObjectDecoder'; 4 | import { IParseDecoder } from './IParseDecoder'; 5 | import { RxParseObject } from '../../RxParse'; 6 | 7 | /** 8 | * 9 | * 10 | * @export 11 | * @class ParseObjectDecoder 12 | * @implements {IParseObjectDecoder} 13 | */ 14 | export class ParseObjectDecoder implements IParseObjectDecoder { 15 | constructor() { 16 | } 17 | 18 | decode(serverResult: any, decoder: IParseDecoder): IObjectState { 19 | let state = new MutableObjectState(); 20 | this.handlerServerResult(state, serverResult, decoder); 21 | return state; 22 | } 23 | 24 | handlerServerResult(state: MutableObjectState, serverResult: any, decoder: IParseDecoder): IObjectState { 25 | let mutableData = new Map(); 26 | if (serverResult.createdAt) { 27 | state.createdAt = serverResult.createdAt; 28 | state.updatedAt = serverResult.createdAt; 29 | delete serverResult.createdAt; 30 | } 31 | if (serverResult.updatedAt) { 32 | state.updatedAt = serverResult.updatedAt; 33 | delete serverResult.updatedAt; 34 | } 35 | if (serverResult.objectId) { 36 | state.objectId = serverResult.objectId; 37 | delete serverResult.objectId; 38 | } 39 | 40 | for (let key in serverResult) { 41 | 42 | var value = serverResult[key]; 43 | 44 | if (value != null) { 45 | if (Object.prototype.hasOwnProperty.call(value, '__type') || Object.prototype.hasOwnProperty.call(value, 'className')) { 46 | if (value['__type'] == 'Pointer') { 47 | let rxObject: RxParseObject = decoder.decodeItem(value); 48 | delete value.__type; 49 | let serverState = this.decode(value, decoder); 50 | rxObject.handleFetchResult(serverState); 51 | mutableData[key] = rxObject; 52 | } else { 53 | mutableData[key] = decoder.decodeItem(value); 54 | } 55 | } else if (Array.isArray(value)) { 56 | mutableData[key] = decoder.decodeItem(value); 57 | } else { 58 | mutableData[key] = value; 59 | } 60 | } 61 | } 62 | state.serverData = mutableData; 63 | state.isNew = true; 64 | return state; 65 | } 66 | } -------------------------------------------------------------------------------- /src/internal/httpClient/AxiosRxHttpClient.ts: -------------------------------------------------------------------------------- 1 | import { map, catchError } from 'rxjs/operators'; 2 | import { Observable, from } from 'rxjs'; 3 | import { HttpRequest } from './HttpRequest'; 4 | import { HttpResponse } from './HttpResponse'; 5 | import { IRxHttpClient } from './IRxHttpClient'; 6 | import axios, { AxiosRequestConfig, AxiosPromise, AxiosResponse } from 'axios'; 7 | import { ParseClient } from '../../public/RxParseClient'; 8 | 9 | export class AxiosRxHttpClient implements IRxHttpClient { 10 | 11 | execute(httpRequest: HttpRequest): Observable { 12 | let tuple: [number, any] = [200, '']; 13 | 14 | return from(this.RxExecuteAxios(httpRequest)).pipe(map(res => { 15 | 16 | tuple[0] = res.status; 17 | tuple[1] = res.data; 18 | let response = new HttpResponse(tuple); 19 | return response; 20 | }), catchError((err: any) => { 21 | ParseClient.printLog('Meta Error:', err.response.data.message); 22 | return Observable.throw(err); 23 | })); 24 | } 25 | 26 | RxExecuteAxios(httpRequest: HttpRequest): Promise { 27 | let method = httpRequest.method.toUpperCase(); 28 | let useData = false; 29 | if (method == 'PUT' || 'POST') { 30 | useData = true; 31 | } 32 | return new Promise((resolve, reject) => { 33 | axios({ 34 | method: method, 35 | url: httpRequest.url, 36 | data: useData ? httpRequest.data : null, 37 | headers: httpRequest.headers 38 | }).then(response => { 39 | resolve(response); 40 | }).catch(error => { 41 | reject(error); 42 | }); 43 | }); 44 | 45 | } 46 | 47 | } -------------------------------------------------------------------------------- /src/internal/httpClient/HttpRequest.ts: -------------------------------------------------------------------------------- 1 | 2 | export class HttpRequest { 3 | public url: string; 4 | public headers: { [key: string]: string }; 5 | public data: { [key: string]: any }; 6 | public method: string; 7 | 8 | constructor() { }; 9 | } 10 | -------------------------------------------------------------------------------- /src/internal/httpClient/HttpResponse.ts: -------------------------------------------------------------------------------- 1 | export /** 2 | * HttpResponse 3 | */ 4 | class HttpResponse { 5 | statusCode: number; 6 | body: any; 7 | constructor(option?: [number, any]) { 8 | if (option != null) { 9 | this.statusCode = option[0]; 10 | this.body = option[1]; 11 | } 12 | } 13 | get jsonBody() { 14 | return JSON.parse(this.body); 15 | } 16 | } -------------------------------------------------------------------------------- /src/internal/httpClient/IRxHttpClient.ts: -------------------------------------------------------------------------------- 1 | import { Observable } from 'rxjs'; 2 | import { HttpRequest } from './HttpRequest'; 3 | import { HttpResponse } from './HttpResponse'; 4 | 5 | export interface IRxHttpClient { 6 | execute(httpRequest: HttpRequest): Observable; 7 | } 8 | -------------------------------------------------------------------------------- /src/internal/httpClient/RxHttpClient.ts: -------------------------------------------------------------------------------- 1 | import { map, catchError } from 'rxjs/operators'; 2 | import { Observable, from } from 'rxjs'; 3 | import { HttpRequest } from './HttpRequest'; 4 | import { HttpResponse } from './HttpResponse'; 5 | import { IRxHttpClient } from './IRxHttpClient'; 6 | import axios, { AxiosRequestConfig, AxiosResponse } from 'axios'; 7 | import * as superagent from 'superagent'; 8 | import { ParseClient } from 'public/RxParseClient'; 9 | 10 | export class RxHttpClient implements IRxHttpClient { 11 | version: number; 12 | constructor(version?: number) { 13 | this.version = version; 14 | } 15 | 16 | execute(httpRequest: HttpRequest): Observable { 17 | let tuple: [number, any] = [200, '']; 18 | let errMsg = { 19 | statusCode: -1, 20 | error: { code: 0, error: 'Server error' } 21 | }; 22 | 23 | let response = new HttpResponse(tuple); 24 | ParseClient.printLog('Request:', JSON.stringify(httpRequest)); 25 | if (ParseClient.instance.currentConfiguration.isNode && this.version == 1) { 26 | ParseClient.printLog('http client:axios'); 27 | return from(this.RxExecuteAxios(httpRequest)).pipe(map(res => { 28 | 29 | tuple[0] = res.status; 30 | tuple[1] = res.data; 31 | response = new HttpResponse(tuple); 32 | ParseClient.printLog('Response:', JSON.stringify(response)); 33 | return response; 34 | }), catchError((err: any) => { 35 | ParseClient.printLog('Meta Error:', err); 36 | if (err) { 37 | errMsg.statusCode = err.response.status; 38 | errMsg.error = err.response.data; 39 | } 40 | ParseClient.printLog('Error:', JSON.stringify(errMsg)); 41 | return Observable.throw(errMsg); 42 | })); 43 | } 44 | 45 | else { 46 | ParseClient.printLog('http client:superagent'); 47 | return from(this.RxExecuteSuperagent(httpRequest)).pipe(map(res => { 48 | tuple[0] = res.status; 49 | tuple[1] = res.body; 50 | let response = new HttpResponse(tuple); 51 | ParseClient.printLog('Response:', JSON.stringify(response)); 52 | return response; 53 | }), catchError((err: any) => { 54 | ParseClient.printLog('Meta Error:', err); 55 | if (err) { 56 | errMsg.statusCode = err.status; 57 | errMsg.error = JSON.parse(err.response.text); 58 | } 59 | ParseClient.printLog('Error:', errMsg); 60 | return Observable.throw(errMsg); 61 | })); 62 | } 63 | } 64 | 65 | RxExecuteAxios(httpRequest: HttpRequest): Promise { 66 | let method = httpRequest.method.toUpperCase(); 67 | let useData = false; 68 | if (method == 'PUT' || 'POST') { 69 | useData = true; 70 | } 71 | return new Promise((resolve, reject) => { 72 | axios({ 73 | method: method, 74 | url: httpRequest.url, 75 | data: useData ? httpRequest.data : null, 76 | headers: httpRequest.headers 77 | }).then(response => { 78 | resolve(response); 79 | }).catch(error => { 80 | reject(error); 81 | }); 82 | }); 83 | } 84 | 85 | RxExecuteSuperagent(httpRequest: HttpRequest): Promise { 86 | let method = httpRequest.method.toUpperCase(); 87 | if (method == 'POST') 88 | return new Promise((resolve, reject) => { 89 | superagent 90 | .post(httpRequest.url) 91 | .send(httpRequest.data) 92 | .set(httpRequest.headers) 93 | .end((error, res) => { 94 | error ? reject(error) : resolve(res); 95 | }); 96 | }); 97 | else if (method == 'PUT') 98 | return new Promise((resolve, reject) => { 99 | superagent 100 | .put(httpRequest.url) 101 | .send(httpRequest.data) 102 | .set(httpRequest.headers) 103 | .end((error, res) => { 104 | error ? reject(error) : resolve(res); 105 | }); 106 | }); 107 | else if (method == 'GET') 108 | return new Promise((resolve, reject) => { 109 | superagent 110 | .get(httpRequest.url) 111 | .set(httpRequest.headers) 112 | .end((error, res) => { 113 | error ? reject(error) : resolve(res); 114 | }); 115 | }); 116 | else if (method == 'DELETE') 117 | return new Promise((resolve, reject) => { 118 | superagent 119 | .del(httpRequest.url) 120 | .set(httpRequest.headers) 121 | .end((error, res) => { 122 | error ? reject(error) : resolve(res); 123 | }); 124 | }); 125 | } 126 | } 127 | -------------------------------------------------------------------------------- /src/internal/object/controller/IParseObjectController.ts: -------------------------------------------------------------------------------- 1 | import { IObjectState } from '../state/IObjectState'; 2 | import { Observable } from 'rxjs'; 3 | import { IParseFieldOperation } from '../../operation/IParseFieldOperation'; 4 | 5 | export interface IObjectController { 6 | fetch(state: IObjectState, sessionToken: string): Observable; 7 | save(state: IObjectState, operations: Map, sessionToken: string): Observable; 8 | delete(state: IObjectState, sessionToken: string): Observable; 9 | batchSave(states: Array, operations: Array>, sessionToken: string): Observable>; 10 | batchDelete(states: Array, sessionToken: string): Observable>; 11 | } -------------------------------------------------------------------------------- /src/internal/object/controller/ParseObjectController.ts: -------------------------------------------------------------------------------- 1 | import { ParseClient } from '../../../RxParse'; 2 | import { Observable } from 'rxjs'; 3 | import { map, catchError } from 'rxjs/operators'; 4 | import { IObjectState } from '../state/IObjectState'; 5 | import { IObjectController } from './IParseObjectController'; 6 | import { ParseCommand } from '../../command/ParseCommand'; 7 | import { IParseCommandRunner } from '../../command/IParseCommandRunner'; 8 | import { SDKPlugins } from '../../ParseClientPlugins'; 9 | import { IParseFieldOperation } from '../../../internal/operation/IParseFieldOperation'; 10 | 11 | export class ObjectController implements IObjectController { 12 | private readonly _commandRunner: IParseCommandRunner; 13 | 14 | constructor(commandRunner: IParseCommandRunner) { 15 | this._commandRunner = commandRunner; 16 | } 17 | 18 | fetch(state: IObjectState, sessionToken: string): Observable { 19 | let cmd = new ParseCommand({ 20 | app: state.app, 21 | relativeUrl: `/classes/${state.className}/${state.objectId}`, 22 | method: 'GET', 23 | data: null, 24 | sessionToken: sessionToken 25 | }); 26 | return this._commandRunner.runRxCommand(cmd).pipe(map(res => { 27 | let serverState = SDKPlugins.instance.ObjectDecoder.decode(res.body, SDKPlugins.instance.Decoder); 28 | return serverState; 29 | })); 30 | } 31 | delete(state: IObjectState, sessionToken: string): Observable { 32 | let cmd = new ParseCommand({ 33 | app: state.app, 34 | relativeUrl: `/classes/${state.className}/${state.objectId}`, 35 | method: 'DELETE', 36 | data: null, 37 | sessionToken: sessionToken 38 | }); 39 | return this._commandRunner.runRxCommand(cmd).pipe(map(res => { 40 | return res.statusCode == 200; 41 | })); 42 | } 43 | 44 | batchDelete(states: Array, sessionToken: string) { 45 | let cmdArray = states.map(state => { 46 | return new ParseCommand({ 47 | app: state.app, 48 | relativeUrl: `/classes/${state.className}/${state.objectId}`, 49 | method: 'DELETE', 50 | data: null, 51 | sessionToken: sessionToken 52 | }) 53 | }); 54 | return this.executeBatchCommands(cmdArray, sessionToken).pipe(map(batchRes => { 55 | return batchRes.map(res => { 56 | return res.statusCode == 200; 57 | }); 58 | })); 59 | } 60 | 61 | clearReadonlyFields(state: IObjectState, dictionary: { [key: string]: any }) { 62 | 63 | if (Object.prototype.hasOwnProperty.call(dictionary, 'objectId')) { 64 | delete dictionary['objectId']; 65 | } 66 | if (Object.prototype.hasOwnProperty.call(dictionary, 'createdAt')) { 67 | delete dictionary['createdAt']; 68 | } 69 | if (Object.prototype.hasOwnProperty.call(dictionary, 'updatedAt')) { 70 | delete dictionary['updatedAt']; 71 | } 72 | if (Object.prototype.hasOwnProperty.call(dictionary, 'className')) { 73 | delete dictionary['className']; 74 | } 75 | if (state.className == '_User') { 76 | if (Object.prototype.hasOwnProperty.call(dictionary, 'sessionToken')) { 77 | delete dictionary['sessionToken']; 78 | } 79 | if (Object.prototype.hasOwnProperty.call(dictionary, 'username')) { 80 | delete dictionary['username']; 81 | } 82 | if (Object.prototype.hasOwnProperty.call(dictionary, 'emailVerified')) { 83 | delete dictionary['emailVerified']; 84 | } 85 | if (Object.prototype.hasOwnProperty.call(dictionary, 'mobilePhoneVerified')) { 86 | delete dictionary['mobilePhoneVerified']; 87 | } 88 | if (Object.prototype.hasOwnProperty.call(dictionary, 'email')) { 89 | delete dictionary['email']; 90 | } 91 | } 92 | } 93 | 94 | clearRelationFields(state: IObjectState, dictionary: { [key: string]: any }) { 95 | for (let key in dictionary) { 96 | let v = dictionary[key]; 97 | if (Object.prototype.hasOwnProperty.call(v, '__type')) { 98 | if (v['__type'] == 'Relation') { 99 | delete dictionary[key]; 100 | } 101 | } 102 | } 103 | } 104 | 105 | 106 | save(state: IObjectState, operations: Map, sessionToken: string): Observable { 107 | let encoded = {}; 108 | operations.forEach((v, k, m) => { 109 | encoded[k] = SDKPlugins.instance.Encoder.encode(v); 110 | }); 111 | let cmd = new ParseCommand({ 112 | app: state.app, 113 | relativeUrl: state.objectId == null ? `/classes/${state.className}` : `/classes/${state.className}/${state.objectId}`, 114 | method: state.objectId == null ? 'POST' : 'PUT', 115 | data: encoded, 116 | sessionToken: sessionToken 117 | }); 118 | 119 | return this._commandRunner.runRxCommand(cmd).pipe(map(res => { 120 | let serverState = SDKPlugins.instance.ObjectDecoder.decode(res.body, SDKPlugins.instance.Decoder); 121 | state = state.mutatedClone(s => { 122 | s.isNew = res.statusCode == 201; 123 | if (serverState.updatedAt) { 124 | s.updatedAt = serverState.updatedAt; 125 | } 126 | if (serverState.objectId) { 127 | s.objectId = serverState.objectId; 128 | } 129 | if (serverState.createdAt) { 130 | s.createdAt = serverState.createdAt; 131 | } 132 | }); 133 | return state; 134 | })); 135 | } 136 | 137 | batchSave(states: Array, operations: Array>, sessionToken: string): Observable> { 138 | 139 | let cmdArray: Array = []; 140 | 141 | states.map((state, i, a) => { 142 | let encoded = {}; 143 | operations[i].forEach((v, k, m) => { 144 | encoded[k] = SDKPlugins.instance.Encoder.encode(v); 145 | }); 146 | let cmd = new ParseCommand({ 147 | app: state.app, 148 | relativeUrl: state.objectId == null ? `/${state.app.mountPath}/classes/${state.className}` : `/${state.app.mountPath}/classes/${state.className}/${state.objectId}`, 149 | method: state.objectId == null ? 'POST' : 'PUT', 150 | data: encoded, 151 | sessionToken: sessionToken 152 | }); 153 | 154 | cmdArray.push(cmd); 155 | }); 156 | 157 | return this.executeBatchCommands(cmdArray, sessionToken).pipe(map(batchRes => { 158 | return batchRes.map(res => { 159 | let serverState = SDKPlugins.instance.ObjectDecoder.decode(res, SDKPlugins.instance.Decoder); 160 | serverState = serverState.mutatedClone((s: IObjectState) => { 161 | s.isNew = res['status'] == 201; 162 | }); 163 | return serverState; 164 | }); 165 | })); 166 | 167 | } 168 | 169 | executeBatchCommands(requests: Array, sessionToken: string): Observable> { 170 | let batchSize = requests.length; 171 | let encodedRequests = requests.map((cmd, i, a) => { 172 | let r: { [key: string]: any } = { 173 | method: cmd.method, 174 | path: cmd.relativeUrl 175 | }; 176 | if (cmd.data != null) { 177 | r['body'] = cmd.data; 178 | } 179 | return r; 180 | }); 181 | 182 | let batchRequest = new ParseCommand({ 183 | relativeUrl: '/batch', 184 | method: 'POST', 185 | data: { requests: encodedRequests }, 186 | sessionToken: sessionToken 187 | }); 188 | return this._commandRunner.runRxCommand(batchRequest).pipe(map(res => { 189 | let rtn: Array<{ [key: string]: any }> = []; 190 | let resultsArray = res.body; 191 | let resultLength = resultsArray.length; 192 | if (resultLength != batchSize) { 193 | throw new Error(`Batch command result count expected: " + ${batchSize} + " but was: " + ${resultLength} + ".`) 194 | } 195 | for (let i = 0; i < batchSize; i++) { 196 | let result = resultsArray[i]; 197 | if (Object.prototype.hasOwnProperty.call(result, 'success')) { 198 | let subBody = result.success; 199 | rtn.push(subBody); 200 | } 201 | } 202 | return rtn; 203 | })); 204 | } 205 | } -------------------------------------------------------------------------------- /src/internal/object/state/IObjectState.ts: -------------------------------------------------------------------------------- 1 | import { ParseApp } from '../../../RxParse'; 2 | 3 | export interface IObjectState { 4 | isNew: boolean; 5 | className: string; 6 | objectId: string; 7 | updatedAt: Date; 8 | createdAt: Date; 9 | app: ParseApp; 10 | serverData: Map; 11 | containsKey(key: string): boolean; 12 | mutatedClone(func: (source: IObjectState) => void): IObjectState; 13 | } -------------------------------------------------------------------------------- /src/internal/object/state/MutableObjectState.ts: -------------------------------------------------------------------------------- 1 | import { IObjectState } from './IObjectState'; 2 | import { ParseApp } from 'public/RxParseClient'; 3 | import { IParseFieldOperation } from '../../operation/IParseFieldOperation'; 4 | import { ParseAddOperation } from '../../operation/ParseAddOperation'; 5 | import { ParseDeleteOperation } from '../../operation/ParseDeleteOperation'; 6 | const _hasOwnProperty = Object.prototype.hasOwnProperty; 7 | export const has = function (obj: any, prop: any) { 8 | return _hasOwnProperty.call(obj, prop); 9 | }; 10 | export /** 11 | * MutableObjectState 12 | */ 13 | class MutableObjectState implements IObjectState { 14 | isNew: boolean; 15 | className: string; 16 | objectId: string; 17 | updatedAt: Date; 18 | createdAt: Date; 19 | app: ParseApp; 20 | serverData: Map; 21 | containsKey(key: string): boolean { 22 | if (this.serverData == null) return false; 23 | return has(this.serverData, key); 24 | } 25 | apply(source: IObjectState) { 26 | this.isNew = source.isNew; 27 | this.objectId = source.objectId; 28 | this.createdAt = source.createdAt; 29 | this.updatedAt = source.updatedAt; 30 | this.serverData = source.serverData; 31 | } 32 | merge(source: IObjectState) { 33 | this.isNew = source.isNew; 34 | this.updatedAt = source.updatedAt; 35 | this.objectId = source.objectId; 36 | this.createdAt = source.createdAt; 37 | } 38 | mutatedClone(func: (source: IObjectState) => void): IObjectState { 39 | let clone = this.mutableClone(); 40 | func(clone); 41 | return clone; 42 | } 43 | protected mutableClone() { 44 | let state = new MutableObjectState({ 45 | data: this.serverData, 46 | className: this.className, 47 | objectId: this.objectId, 48 | createdAt: this.createdAt, 49 | updatedAt: this.updatedAt 50 | }); 51 | return state; 52 | } 53 | 54 | constructor(options?: any) { 55 | if (options != null) { 56 | if (options.className != null) { 57 | this.className = options.className; 58 | } 59 | if (options.data != null) { 60 | this.serverData = options.data; 61 | } 62 | if (options.objectId != null) { 63 | this.objectId = options.objectId; 64 | } 65 | if (options.createdAt != null) { 66 | this.createdAt = options.createdAt; 67 | } 68 | if (options.updatedAt != null) { 69 | this.updatedAt = options.updatedAt; 70 | } 71 | if (options.app != null) { 72 | this.app = options.app; 73 | } 74 | } 75 | } 76 | } -------------------------------------------------------------------------------- /src/internal/operation/IParseFieldOperation.ts: -------------------------------------------------------------------------------- 1 | export interface IParseFieldOperation { 2 | 3 | encode(): object; 4 | 5 | mergeWithPrevious(previous: IParseFieldOperation): IParseFieldOperation; 6 | 7 | apply(oldValue: object, key: string): object; 8 | } -------------------------------------------------------------------------------- /src/internal/operation/ParseAddOperation.ts: -------------------------------------------------------------------------------- 1 | import { IParseFieldOperation } from './IParseFieldOperation'; 2 | import { SDKPlugins } from '../ParseClientPlugins'; 3 | import { ParseDeleteOperation } from './ParseDeleteOperation'; 4 | import { ParseSetOperation } from './ParseSetOperation'; 5 | 6 | export class ParseAddOperation implements IParseFieldOperation { 7 | encode(): any { 8 | return { 9 | __op: 'Add', 10 | objects: SDKPlugins.instance.Encoder.encode(this.objects) 11 | }; 12 | } 13 | mergeWithPrevious(previous: IParseFieldOperation): IParseFieldOperation { 14 | throw new Error("Method not implemented."); 15 | } 16 | apply(oldValue: object, key: string): object { 17 | throw new Error("Method not implemented."); 18 | } 19 | objects: Array; 20 | constructor(objects: Array) { 21 | this.objects = objects; 22 | } 23 | } 24 | 25 | export class ParseAddUniqueOperation implements IParseFieldOperation { 26 | encode(): any { 27 | return { 28 | __op: 'AddUnique', 29 | objects: SDKPlugins.instance.Encoder.encode(this.objects) 30 | }; 31 | } 32 | mergeWithPrevious(previous: any): IParseFieldOperation { 33 | if (previous == null || previous == undefined) { 34 | return this; 35 | } 36 | if (previous instanceof ParseDeleteOperation) { 37 | return new ParseSetOperation(this.objects); 38 | } 39 | if (previous instanceof ParseSetOperation) { 40 | 41 | } 42 | 43 | return this; 44 | } 45 | apply(oldValue: object, key: string): object { 46 | throw new Error("Method not implemented."); 47 | } 48 | objects: Array; 49 | constructor(objects: Array) { 50 | this.objects = objects; 51 | } 52 | } 53 | 54 | -------------------------------------------------------------------------------- /src/internal/operation/ParseDeleteOperation.ts: -------------------------------------------------------------------------------- 1 | import { IParseFieldOperation } from './IParseFieldOperation'; 2 | import { SDKPlugins } from '../ParseClientPlugins'; 3 | 4 | export class ParseDeleteToken { 5 | public static sharedInstance = new ParseDeleteToken(); 6 | } 7 | 8 | 9 | export class ParseDeleteOperation implements IParseFieldOperation { 10 | public static deleteToken = ParseDeleteToken.sharedInstance; 11 | public static sharedInstance = new ParseDeleteOperation(); 12 | encode(): any { 13 | return { 14 | __op: 'Delete' 15 | }; 16 | } 17 | mergeWithPrevious(previous: IParseFieldOperation): IParseFieldOperation { 18 | return this; 19 | } 20 | apply(oldValue: object, key: string): object { 21 | return ParseDeleteOperation.deleteToken; 22 | } 23 | 24 | constructor() { 25 | 26 | } 27 | } 28 | 29 | -------------------------------------------------------------------------------- /src/internal/operation/ParseRemoveOperation.ts: -------------------------------------------------------------------------------- 1 | import { IParseFieldOperation } from './IParseFieldOperation'; 2 | import { SDKPlugins } from '../ParseClientPlugins'; 3 | 4 | export class ParseRemoveOperation implements IParseFieldOperation { 5 | encode(): any { 6 | return { 7 | __op: 'Remove', 8 | objects: SDKPlugins.instance.Encoder.encode(this.objects) 9 | }; 10 | } 11 | mergeWithPrevious(previous: IParseFieldOperation): IParseFieldOperation { 12 | throw new Error("Method not implemented."); 13 | } 14 | apply(oldValue: object, key: string): object { 15 | throw new Error("Method not implemented."); 16 | } 17 | 18 | objects: Array; 19 | constructor(objects: Array) { 20 | this.objects = objects; 21 | } 22 | } 23 | 24 | -------------------------------------------------------------------------------- /src/internal/operation/ParseSetOperation.ts: -------------------------------------------------------------------------------- 1 | import { IParseFieldOperation } from './IParseFieldOperation'; 2 | import { SDKPlugins } from '../ParseClientPlugins'; 3 | 4 | export class ParseSetOperation implements IParseFieldOperation { 5 | private value: any; 6 | constructor(value: any) { 7 | this.value = value; 8 | } 9 | encode(): object { 10 | return SDKPlugins.instance.Encoder.encode(this.value); 11 | } 12 | mergeWithPrevious(previous: IParseFieldOperation): IParseFieldOperation { 13 | return this; 14 | } 15 | apply(oldValue: object, key: string): object { 16 | return this.value; 17 | } 18 | 19 | } -------------------------------------------------------------------------------- /src/internal/query/controller/IQueryController.ts: -------------------------------------------------------------------------------- 1 | import { IObjectState } from '../../object/state/IObjectState'; 2 | import { RxParseQuery } from 'public/RxParseQuery'; 3 | import { Observable } from 'rxjs'; 4 | 5 | export /** 6 | * IQueryController 7 | */ 8 | interface IQueryController { 9 | find(query: RxParseQuery, sessionToken: string): Observable>; 10 | count(query: RxParseQuery, sessionToken: string): Observable; 11 | first(query: RxParseQuery, sessionToken: string): Observable>; 12 | 13 | //find(): Observable>; 14 | 15 | } -------------------------------------------------------------------------------- /src/internal/query/controller/QueryController.ts: -------------------------------------------------------------------------------- 1 | import { IObjectState } from '../../object/state/IObjectState'; 2 | import { IQueryController } from './IQueryController'; 3 | import { ParseCommand } from '../../command/ParseCommand'; 4 | import { IParseCommandRunner } from '../../command/IParseCommandRunner'; 5 | import { RxParseQuery } from 'public/RxParseQuery'; 6 | import { SDKPlugins } from '../../ParseClientPlugins'; 7 | import { Observable, from } from 'rxjs'; 8 | import { map, catchError } from 'rxjs/operators'; 9 | 10 | export class QueryController implements IQueryController { 11 | private readonly _commandRunner: IParseCommandRunner; 12 | 13 | constructor(commandRunner: IParseCommandRunner) { 14 | this._commandRunner = commandRunner; 15 | } 16 | 17 | find(query: RxParseQuery, sessionToken: string): Observable> { 18 | let qu = this.buildQueryString(query); 19 | let cmd = new ParseCommand({ 20 | app: query.app, 21 | relativeUrl: qu, 22 | method: 'GET', 23 | sessionToken: sessionToken 24 | }); 25 | return this._commandRunner.runRxCommand(cmd).pipe(map(res => { 26 | let items = res.body["results"] as Array; 27 | let x = items.map((item, i, a) => { 28 | let y = SDKPlugins.instance.ObjectDecoder.decode(item, SDKPlugins.instance.Decoder); 29 | return y; 30 | }); 31 | return x; 32 | })); 33 | } 34 | 35 | count(query: RxParseQuery, sessionToken: string): Observable { 36 | return from([0]); 37 | } 38 | 39 | first(query: RxParseQuery, sessionToken: string): Observable> { 40 | return null; 41 | } 42 | 43 | buildQueryString(query: RxParseQuery) { 44 | let queryJson = query.buildParameters(); 45 | let queryArray = []; 46 | let queryUrl = ''; 47 | for (let key in queryJson) { 48 | let qs = `${key}=${queryJson[key]}`; 49 | queryArray.push(qs); 50 | } 51 | queryUrl = queryArray.join('&'); 52 | 53 | return `/classes/${query.className}?${queryUrl}`; 54 | } 55 | } -------------------------------------------------------------------------------- /src/internal/storage/IStorage.ts: -------------------------------------------------------------------------------- 1 | 2 | export interface IStorage { 3 | add(key: string, value: any): Promise 4 | remove(key: string): Promise; 5 | get(key: string): Promise; 6 | } 7 | 8 | export interface RealtimeStorage{ 9 | 10 | } -------------------------------------------------------------------------------- /src/internal/storage/controller/IStorageController.ts: -------------------------------------------------------------------------------- 1 | import { Observable } from 'rxjs'; 2 | import { IStorage } from '../IStorage'; 3 | 4 | export interface IStorageController { 5 | isDirty: boolean; 6 | dictionary: { [key: string]: any }; 7 | provider: IStorage; 8 | load(): Observable; 9 | save(contents: { [key: string]: any }): Observable; 10 | get(key: string): Observable; 11 | set(key: string, value: any): Observable; 12 | remove(key: string): Observable; 13 | } -------------------------------------------------------------------------------- /src/internal/storage/controller/StorageController.ts: -------------------------------------------------------------------------------- 1 | import { IStorageController } from './IStorageController'; 2 | import { Observable, from } from 'rxjs'; 3 | import { map, catchError, flatMap } from 'rxjs/operators'; 4 | import { IStorage } from '../IStorage'; 5 | 6 | 7 | export class StorageController implements IStorageController { 8 | private storageFileName: string = 'RxApplicationSettings'; 9 | provider: IStorage; 10 | isDirty: boolean; 11 | hasLoaded: boolean = false; 12 | dictionary: { [key: string]: any } = null; 13 | 14 | constructor(storageProvider?: IStorage) { 15 | if (storageProvider) 16 | this.provider = storageProvider; 17 | } 18 | 19 | load(): Observable { 20 | if (!this.provider) { 21 | console.warn('can not find a Storage Provider.'); 22 | return from([null]); 23 | } 24 | let obs = from(this.provider.get(this.storageFileName)); 25 | return obs.pipe(map(data => { 26 | if (data) { 27 | let firstJson = JSON.parse(data); 28 | if (typeof firstJson == 'string') { 29 | this.dictionary = JSON.parse(firstJson); 30 | } else { 31 | this.dictionary = firstJson; 32 | } 33 | } 34 | else this.dictionary = {}; 35 | this.isDirty = false; 36 | return this.provider; 37 | })); 38 | } 39 | 40 | save(contents: { [key: string]: any }): Observable { 41 | this.dictionary = contents; 42 | if (!this.provider) { 43 | console.warn('can not find a Storage Provider.'); 44 | return from([null]); 45 | } 46 | if (this.dictionary && this.dictionary != null) { 47 | let jsonString = JSON.stringify(this.dictionary); 48 | let save = this.provider.add(this.storageFileName, jsonString); 49 | return from(save).pipe(map(success => { 50 | return this.provider; 51 | })); 52 | } else { 53 | return from(this.provider.remove(this.storageFileName)).pipe(map(removed => { 54 | console.log('empty cache with key ' + this.storageFileName); 55 | return this.provider; 56 | })); 57 | } 58 | } 59 | 60 | get(key: string): Observable { 61 | if (this.dictionary != null) 62 | return from([this.dictionary[key]]); 63 | else { 64 | return this.load().pipe(map(provider => { 65 | if (this.dictionary != null) 66 | return this.dictionary[key]; 67 | else return null; 68 | })); 69 | } 70 | } 71 | 72 | set(key: string, value: any): Observable { 73 | let loaded = from([this.provider]); 74 | if (!this.hasLoaded) { 75 | loaded = this.load(); 76 | } 77 | return loaded.pipe(flatMap(provider => { 78 | this.isDirty = true; 79 | this.dictionary[key] = value; 80 | return this.save(this.dictionary); 81 | })); 82 | } 83 | remove(key: string): Observable { 84 | if (this.dictionary[key]) { 85 | delete this.dictionary[key]; 86 | } 87 | return this.save(this.dictionary); 88 | } 89 | } -------------------------------------------------------------------------------- /src/internal/tool/controller/IToolController.ts: -------------------------------------------------------------------------------- 1 | export interface IToolController { 2 | newObjectId():string; 3 | getTimestamp(unit:string):number; 4 | } -------------------------------------------------------------------------------- /src/internal/tool/controller/ToolController.ts: -------------------------------------------------------------------------------- 1 | import { IToolController } from './IToolController'; 2 | import { randomBytes, createHash } from 'crypto'; 3 | 4 | export class ToolController implements IToolController { 5 | // Returns a new random hex string of the given even size. 6 | randomHexString(size: number): string { 7 | if (size === 0) { 8 | throw new Error('Zero-length randomHexString is useless.'); 9 | } 10 | if (size % 2 !== 0) { 11 | throw new Error('randomHexString size must be divisible by 2.') 12 | } 13 | return randomBytes(size / 2).toString('hex'); 14 | } 15 | 16 | randomHexStringWithPrefix(prefix: string, size: number): string { 17 | return prefix + this.randomHexString(size); 18 | } 19 | 20 | // Returns a new random alphanumeric string of the given size. 21 | // 22 | // Note: to simplify implementation, the result has slight modulo bias, 23 | // because chars length of 62 doesn't divide the number of all bytes 24 | // (256) evenly. Such bias is acceptable for most cases when the output 25 | // length is long enough and doesn't need to be uniform. 26 | randomString(size: number): string { 27 | if (size === 0) { 28 | throw new Error('Zero-length randomString is useless.'); 29 | } 30 | let chars = ('ABCDEFGHIJKLMNOPQRSTUVWXYZ' + 31 | 'abcdefghijklmnopqrstuvwxyz' + 32 | '0123456789'); 33 | let objectId = ''; 34 | let bytes = randomBytes(size); 35 | for (let i = 0; i < bytes.length; ++i) { 36 | objectId += chars[bytes.readUInt8(i) % chars.length]; 37 | } 38 | return objectId; 39 | } 40 | 41 | // Returns a new random alphanumeric string suitable for object ID. 42 | newObjectId(): string { 43 | //TODO: increase length to better protect against collisions. 44 | return this.randomString(10); 45 | } 46 | getTimestamp(unit: string): number { 47 | let unitLower = unit.toLowerCase(); 48 | if (unitLower == 'seconds' || unitLower == 'second' || unitLower == 's') { 49 | return Math.floor(Date.now()); 50 | } else if(unitLower == 'milliseconds' || unitLower == 'millisecond' || unitLower == 'ms'){ 51 | return new Date().getTime(); 52 | //return Math.floor(Date.now() / 1000); 53 | } 54 | } 55 | 56 | // Returns a new random hex string suitable for secure tokens. 57 | newToken(): string { 58 | return this.randomHexString(32); 59 | } 60 | 61 | md5Hash(string: string): string { 62 | return createHash('md5').update(string).digest('hex'); 63 | } 64 | 65 | newMobilePhoneNumber(): string { 66 | let prefix = ['138', '139', '188', '186', '189', '171', '170']; 67 | let chars = ('0123456789'); 68 | let mobile = prefix[Math.floor(Math.random() * prefix.length)]; 69 | let bytes = randomBytes(8); 70 | for (let i = 0; i < bytes.length; ++i) { 71 | mobile += chars[bytes.readUInt8(i) % chars.length]; 72 | } 73 | return mobile; 74 | } 75 | } -------------------------------------------------------------------------------- /src/internal/user/controller/IUserController.ts: -------------------------------------------------------------------------------- 1 | import { Observable } from 'rxjs'; 2 | import { IObjectState } from '../../object/state/IObjectState'; 3 | 4 | export /** 5 | * IUserController 6 | */ 7 | interface IUserController { 8 | signUp(state:IObjectState,dictionary:{ [key: string]: any }): Observable; 9 | logIn(username: string, password: string): Observable; 10 | logInWithParameters(relativeUrl: string, data: { [key: string]: any }): Observable; 11 | getUser(sessionToken:string): Observable; 12 | } -------------------------------------------------------------------------------- /src/internal/user/controller/UserController.ts: -------------------------------------------------------------------------------- 1 | import { HttpResponse } from '../../httpClient/HttpResponse'; 2 | import { ParseCommand } from '../../command/ParseCommand'; 3 | import { ParseClient } from 'public/RxParseClient'; 4 | import { Observable, from } from 'rxjs'; 5 | import { map, catchError } from 'rxjs/operators'; 6 | import { IObjectState } from '../../object/state/IObjectState'; 7 | import { IUserController } from './IUserController'; 8 | import { SDKPlugins } from '../../ParseClientPlugins'; 9 | import { IParseCommandRunner } from '../../command/IParseCommandRunner'; 10 | 11 | export /** 12 | * UserController 13 | */ 14 | class UserController implements IUserController { 15 | 16 | private readonly _commandRunner: IParseCommandRunner; 17 | constructor(commandRunner: IParseCommandRunner) { 18 | this._commandRunner = commandRunner; 19 | } 20 | signUp(state: IObjectState, dictionary: { [key: string]: any }): Observable { 21 | let encoded = SDKPlugins.instance.Encoder.encode(dictionary); 22 | let cmd = new ParseCommand({ 23 | app: state.app, 24 | relativeUrl: "/users", 25 | method: 'POST', 26 | data: encoded 27 | }); 28 | return this._commandRunner.runRxCommand(cmd).pipe(map(res => { 29 | let serverState = SDKPlugins.instance.ObjectDecoder.decode(res.body, SDKPlugins.instance.Decoder); 30 | serverState = serverState.mutatedClone((s: IObjectState) => { 31 | s.isNew = true; 32 | }); 33 | return serverState; 34 | })); 35 | } 36 | logIn(username: string, password: string): Observable { 37 | let data = { 38 | "username": username, 39 | "password": password 40 | }; 41 | let cmd = new ParseCommand({ 42 | relativeUrl: "/login", 43 | method: 'POST', 44 | data: data 45 | }); 46 | 47 | return this._commandRunner.runRxCommand(cmd).pipe(map(res => { 48 | let serverState = SDKPlugins.instance.ObjectDecoder.decode(res.body, SDKPlugins.instance.Decoder); 49 | return serverState; 50 | })); 51 | } 52 | 53 | logInWithParameters(relativeUrl: string, data: { [key: string]: any }): Observable { 54 | let encoded = SDKPlugins.instance.Encoder.encode(data); 55 | 56 | let cmd = new ParseCommand({ 57 | relativeUrl: relativeUrl, 58 | method: 'POST', 59 | data: data 60 | }); 61 | 62 | return this._commandRunner.runRxCommand(cmd).pipe(map(res => { 63 | let serverState = SDKPlugins.instance.ObjectDecoder.decode(res.body, SDKPlugins.instance.Decoder); 64 | serverState = serverState.mutatedClone((s: IObjectState) => { 65 | s.isNew = res.statusCode == 201; 66 | }); 67 | return serverState; 68 | })); 69 | } 70 | getUser(sessionToken: string): Observable { 71 | return null; 72 | } 73 | } -------------------------------------------------------------------------------- /src/internal/websocket/IRxWebSocketClient.ts: -------------------------------------------------------------------------------- 1 | import { Observable, Observer, Subject } from 'rxjs'; 2 | 3 | export interface IRxWebSocketClient { 4 | state: string; 5 | onState: Observable; 6 | onMessage: Observable; 7 | open(url: string, protocols?: string | string[]): Observable; 8 | close(code?: number, data?: any): void; 9 | send(data: any, options?: any): Observable; 10 | } -------------------------------------------------------------------------------- /src/internal/websocket/IWebSocketClient.ts: -------------------------------------------------------------------------------- 1 | export interface IWebSocketClient { 2 | onopen: (event: { target: IWebSocketClient }) => void; 3 | onerror: (err: Error) => void; 4 | onclose: (event: { wasClean: boolean; code: number; reason: string; target: IWebSocketClient }) => void; 5 | onmessage: (event: { data: any; type: string; target: IWebSocketClient }) => void; 6 | readyState: number; 7 | open(url: string, protocols?: string | string[]): void; 8 | close(code: number, reason: string): void; 9 | send(data: ArrayBuffer | string | Blob): void; 10 | newInstance(): IWebSocketClient; 11 | } -------------------------------------------------------------------------------- /src/internal/websocket/controller/IRxWebSocketController.ts: -------------------------------------------------------------------------------- 1 | import { Observable, Observer, Subject } from 'rxjs'; 2 | import { HttpRequest } from '../../httpClient/HttpRequest'; 3 | import { HttpResponse } from '../../httpClient/HttpResponse'; 4 | import { IRxHttpClient } from '../../httpClient/IRxHttpClient'; 5 | import { ParseClient } from 'public/RxParseClient'; 6 | import { IRxWebSocketClient } from '../IRxWebSocketClient'; 7 | import { ParseCommand } from '../../command/ParseCommand'; 8 | import { ParseCommandResponse } from '../../command/ParseCommandResponse'; 9 | import { IWebSocketClient } from '../IWebSocketClient'; 10 | 11 | export interface IRxWebSocketController { 12 | websocketClient: IWebSocketClient; 13 | onMessage: Observable; 14 | onState: Observable; 15 | open(url: string, protocols?: string | string[]): Observable; 16 | send(data: ArrayBuffer | string | Blob):void; 17 | execute(avCommand: ParseCommand): Observable; 18 | } -------------------------------------------------------------------------------- /src/internal/websocket/controller/RxWebSocketController.ts: -------------------------------------------------------------------------------- 1 | import { Observable, Observer, Subject, ConnectableObservable, from } from 'rxjs'; 2 | import { HttpRequest } from '../../httpClient/HttpRequest'; 3 | import { HttpResponse } from '../../httpClient/HttpResponse'; 4 | import { IRxHttpClient } from '../../httpClient/IRxHttpClient'; 5 | import { map, catchError, filter } from 'rxjs/operators'; 6 | import { IRxWebSocketController } from './IRxWebSocketController'; 7 | import { IWebSocketClient } from '../IWebSocketClient'; 8 | 9 | export class RxWebSocketController implements IRxHttpClient, IRxWebSocketController { 10 | 11 | websocketClient: IWebSocketClient; 12 | url: string; 13 | protocols: string | string[]; 14 | onMessage: Observable; 15 | onState: Observable; 16 | messageSubject: Subject = new Subject(); 17 | stateSubject: Subject = new Subject(); 18 | publish: ConnectableObservable; 19 | constructor(_webSocketClient: IWebSocketClient) { 20 | this.websocketClient = _webSocketClient; 21 | } 22 | // private create(): Subject { 23 | // // 创建websocket对象 24 | // let ws = this.websocketClient; 25 | // // 创建Observable对象 26 | // let observable = Observable.create( 27 | // (obs: Observer) => { 28 | // // 当websocket获得推送内容的时候,调用next方法,并传入推送内容 29 | // ws.onmessage = obs.next.bind(obs); 30 | // // 当websocket出错的时候,调用error方法,并传入失败信息 31 | // ws.onerror = obs.error.bind(obs); 32 | // // 当websocket关闭的时候,调用complete方法 33 | // ws.onclose = obs.complete.bind(obs); 34 | // return ws.close.bind(ws); 35 | // } 36 | // ); 37 | // // 创建observer对象,用于向websocket发送信息 38 | // let observer = { 39 | // next: (value) => { 40 | // if (ws.readyState === WebSocket.OPEN) { 41 | // ws.send(value.toString()); 42 | // } 43 | // }, 44 | // }; 45 | // // 使用Rx.Subject.create 创建 Subject 对象 46 | // return Subject.create(observer, observable); 47 | // } // 获取subject对象接口 48 | // getSubject() { 49 | // if (!this.subject) { 50 | // this.subject = this.create(); 51 | // } 52 | // return this.subject; 53 | // } 54 | // // 获取publish对象接口 55 | // getPublish() { 56 | // if (!this.publish) { 57 | // this.publish = this.getSubject().publish(); 58 | // } 59 | // return this.publish; 60 | // } 61 | open(url: string, protocols?: string | string[]): Observable { 62 | if (this.websocketClient.readyState == 1) return from([true]); 63 | console.log(url, 'connecting...'); 64 | this.url = url; 65 | this.protocols = protocols; 66 | this.websocketClient.open(url, protocols); 67 | 68 | this.onState = Observable.create( 69 | (obs: Observer) => { 70 | this.websocketClient.onopen = (event) => { 71 | console.log(url, 'connected.'); 72 | obs.next(this.websocketClient.readyState); 73 | }; 74 | this.websocketClient.onerror = (event) => { 75 | obs.next(this.websocketClient.readyState); 76 | }; 77 | this.websocketClient.onclose = (event) => { 78 | obs.next(this.websocketClient.readyState); 79 | }; 80 | } 81 | ); 82 | if (this.onState == undefined) { 83 | 84 | } 85 | if (this.onMessage == undefined) { 86 | this.websocketClient.onmessage = (event) => { 87 | console.log('websocket<=', event.data); 88 | let messageJson = JSON.parse(event.data); 89 | //console.log('websocket<=', messageJson); 90 | this.messageSubject.next(event.data); 91 | }; 92 | this.onMessage = this.messageSubject.asObservable(); 93 | // this.onMessage = Observable.create( 94 | // (obs: Observer) => { 95 | // console.log('xxxxx', this.onMessage); 96 | // this.websocketClient.onmessage = (event) => { 97 | // let messageJson = JSON.parse(event.data); 98 | // console.log('websocket<=', messageJson); 99 | // obs.next(event.data); 100 | // }; 101 | 102 | // this.websocketClient.onclose = (event) => { 103 | // obs.complete(); 104 | // }; 105 | 106 | // this.websocketClient.onerror = (event) => { 107 | // obs.error(event.stack); 108 | // }; 109 | // } 110 | // ); 111 | // let observable = Observable.create( 112 | // (obs: Observer) => { 113 | // this.websocketClient.onmessage = obs.next.bind(str => { 114 | // obs.next(str) 115 | // }); 116 | // this.websocketClient.onerror = obs.error.bind(obs); 117 | // this.websocketClient.onclose = obs.complete.bind(obs); 118 | // return this.websocketClient.close.bind(this.websocketClient); 119 | // } 120 | // ); 121 | // let observer = { 122 | // next: (event: any) => { 123 | // let messageJson = JSON.parse(event.data); 124 | // console.log('websocket<=', messageJson); 125 | // }, 126 | // }; 127 | // this.onMessage = Subject.create(observer, observable); 128 | } 129 | 130 | return this.onState.pipe(filter(readyState => { 131 | return readyState == 1; 132 | }), map(readyState => { 133 | return true; 134 | })); 135 | } 136 | send(data: string | ArrayBuffer | Blob): void { 137 | this.websocketClient.send(data); 138 | console.log('websocket=>', data); 139 | } 140 | execute(httpRequest: HttpRequest): Observable { 141 | let rawReq = JSON.stringify(httpRequest.data); 142 | this.send(rawReq); 143 | return this.onMessage.pipe(filter(message => { 144 | let messageJSON = JSON.parse(message); 145 | if (Object.prototype.hasOwnProperty.call(messageJSON, 'i') && Object.prototype.hasOwnProperty.call(httpRequest.data, 'i')) { 146 | return httpRequest.data.i == messageJSON.i; 147 | } 148 | return false; 149 | }), map(message => { 150 | let messageJSON = JSON.parse(message); 151 | let resp = new HttpResponse(); 152 | resp.body = messageJSON; 153 | resp.statusCode = 200; 154 | return resp; 155 | })); 156 | } 157 | } -------------------------------------------------------------------------------- /src/public/RxParseACL.ts: -------------------------------------------------------------------------------- 1 | import { RxParseUser } from './RxParseUser'; 2 | import { RxParseRole } from './RxParseRole'; 3 | 4 | export type PermissionsMap = { [permission: string]: boolean }; 5 | export type ByIdMap = { [userId: string]: PermissionsMap }; 6 | var PUBLIC_KEY = '*'; 7 | 8 | export class RxParseACL { 9 | 10 | private permissionsById: ByIdMap; 11 | 12 | constructor(...arg: any[]) { 13 | this.permissionsById = {}; 14 | if (arg.length > 0) { 15 | arg.forEach(currentItem => { 16 | if (currentItem instanceof RxParseUser) { 17 | this.setReadAccess(currentItem, true); 18 | this.setWriteAccess(currentItem, true); 19 | } else if (currentItem instanceof RxParseRole) { 20 | this.setReadAccess(currentItem, true); 21 | this.setWriteAccess(currentItem, true); 22 | } else if (typeof currentItem === 'string') { 23 | this.setRoleWriteAccess(currentItem, true); 24 | this.setRoleReadAccess(currentItem, true); 25 | } else if (currentItem !== undefined) { 26 | throw new TypeError('ParseACL constructor need RxParseUser or RxParseRole.'); 27 | } 28 | }); 29 | } else { 30 | this.setPublicReadAccess(true); 31 | this.setPublicWriteAccess(true); 32 | } 33 | } 34 | 35 | toJSON(): ByIdMap { 36 | let permissions = {}; 37 | for (let p in this.permissionsById) { 38 | permissions[p] = this.permissionsById[p]; 39 | } 40 | return permissions; 41 | } 42 | 43 | equals(other: RxParseACL): boolean { 44 | if (!(other instanceof RxParseACL)) { 45 | return false; 46 | } 47 | let users = Object.keys(this.permissionsById); 48 | let otherUsers = Object.keys(other.permissionsById); 49 | if (users.length !== otherUsers.length) { 50 | return false; 51 | } 52 | for (let u in this.permissionsById) { 53 | if (!other.permissionsById[u]) { 54 | return false; 55 | } 56 | if (this.permissionsById[u]['read'] !== other.permissionsById[u]['read']) { 57 | return false; 58 | } 59 | if (this.permissionsById[u]['write'] !== other.permissionsById[u]['write']) { 60 | return false; 61 | } 62 | } 63 | return true; 64 | } 65 | 66 | private _setAccess(accessType: string, userId: RxParseUser | RxParseRole | string, allowed: boolean) { 67 | if (userId instanceof RxParseUser) { 68 | userId = userId.objectId; 69 | } else if (userId instanceof RxParseRole) { 70 | const name = userId.name; 71 | if (!name) { 72 | throw new TypeError('Role must have a name'); 73 | } 74 | userId = 'role:' + name; 75 | } 76 | if (typeof userId !== 'string') { 77 | throw new TypeError('userId must be a string.'); 78 | } 79 | if (typeof allowed !== 'boolean') { 80 | throw new TypeError('allowed must be either true or false.'); 81 | } 82 | let permissions = this.permissionsById[userId]; 83 | if (!permissions) { 84 | if (!allowed) { 85 | // The user already doesn't have this permission, so no action is needed 86 | return; 87 | } else { 88 | permissions = {}; 89 | this.permissionsById[userId] = permissions; 90 | } 91 | } 92 | 93 | if (allowed) { 94 | this.permissionsById[userId][accessType] = true; 95 | } else { 96 | delete permissions[accessType]; 97 | if (Object.keys(permissions).length === 0) { 98 | delete this.permissionsById[userId]; 99 | } 100 | } 101 | } 102 | 103 | private _getAccess( 104 | accessType: string, 105 | userId: RxParseUser | RxParseRole | string 106 | ): boolean { 107 | if (userId instanceof RxParseUser) { 108 | userId = userId.objectId; 109 | if (!userId) { 110 | throw new Error('Cannot get access for a RxParseUser without an ID'); 111 | } 112 | } else if (userId instanceof RxParseRole) { 113 | const name = userId.name; 114 | if (!name) { 115 | throw new TypeError('Role must have a name'); 116 | } 117 | userId = 'role:' + name; 118 | } 119 | let permissions = this.permissionsById[userId]; 120 | if (!permissions) { 121 | return false; 122 | } 123 | return !!permissions[accessType]; 124 | } 125 | 126 | public findWriteAccess(): boolean { 127 | let rtn = false; 128 | for (let key in this.permissionsById) { 129 | let permission = this.permissionsById[key]; 130 | if (permission['write']) { 131 | rtn = true; 132 | break; 133 | } 134 | } 135 | return rtn; 136 | } 137 | 138 | public setReadAccess(userId: RxParseUser | RxParseRole | string, allowed: boolean) { 139 | this._setAccess('read', userId, allowed); 140 | } 141 | 142 | public getReadAccess(userId: RxParseUser | RxParseRole | string): boolean { 143 | return this._getAccess('read', userId); 144 | } 145 | 146 | public setWriteAccess(userId: RxParseUser | RxParseRole | string, allowed: boolean) { 147 | this._setAccess('write', userId, allowed); 148 | } 149 | 150 | public getWriteAccess(userId: RxParseUser | RxParseRole | string): boolean { 151 | return this._getAccess('write', userId); 152 | } 153 | 154 | public setPublicReadAccess(allowed: boolean) { 155 | this.setReadAccess(PUBLIC_KEY, allowed); 156 | } 157 | 158 | public getPublicReadAccess(): boolean { 159 | return this.getReadAccess(PUBLIC_KEY); 160 | } 161 | 162 | public setPublicWriteAccess(allowed: boolean) { 163 | this.setWriteAccess(PUBLIC_KEY, allowed); 164 | } 165 | 166 | public getPublicWriteAccess(): boolean { 167 | return this.getWriteAccess(PUBLIC_KEY); 168 | } 169 | 170 | public getRoleReadAccess(role: RxParseRole | string): boolean { 171 | if (role instanceof RxParseRole) { 172 | // Normalize to the String name 173 | role = role.name; 174 | } 175 | if (typeof role !== 'string') { 176 | throw new TypeError( 177 | 'role must be a RxParseRole or a String' 178 | ); 179 | } 180 | return this.getReadAccess('role:' + role); 181 | } 182 | 183 | public getRoleWriteAccess(role: RxParseRole | string): boolean { 184 | if (role instanceof RxParseRole) { 185 | // Normalize to the String name 186 | role = role.name; 187 | } 188 | if (typeof role !== 'string') { 189 | throw new TypeError( 190 | 'role must be a RxParseRole or a String' 191 | ); 192 | } 193 | return this.getWriteAccess('role:' + role); 194 | } 195 | 196 | public setRoleReadAccess(role: RxParseRole | string, allowed: boolean) { 197 | if (role instanceof RxParseRole) { 198 | // Normalize to the String name 199 | role = role.name; 200 | } 201 | if (typeof role !== 'string') { 202 | throw new TypeError( 203 | 'role must be a RxParseRole or a String' 204 | ); 205 | } 206 | this.setReadAccess('role:' + role, allowed); 207 | } 208 | 209 | public setRoleWriteAccess(role: RxParseRole | string, allowed: boolean) { 210 | if (role instanceof RxParseRole) { 211 | // Normalize to the String name 212 | role = role.name; 213 | } 214 | if (typeof role !== 'string') { 215 | throw new TypeError( 216 | 'role must be a RxParseRole or a String' 217 | ); 218 | } 219 | this.setWriteAccess('role:' + role, allowed); 220 | } 221 | } -------------------------------------------------------------------------------- /src/public/RxParseClient.ts: -------------------------------------------------------------------------------- 1 | import { SDKPlugins } from '../internal/ParseClientPlugins'; 2 | import { ParseCommand } from '../internal/command/ParseCommand'; 3 | import { IStorage } from '../internal/storage/IStorage'; 4 | import { IWebSocketClient } from '../internal/websocket/IWebSocketClient'; 5 | import { StorageController } from '../internal/storage/controller/StorageController'; 6 | import { Observable } from 'rxjs'; 7 | import { map, flatMap } from 'rxjs/operators'; 8 | import { HttpRequest } from '../internal/httpClient/HttpRequest'; 9 | import { HttpResponse } from '../internal/httpClient/HttpResponse'; 10 | 11 | var sdkInfo = require('../package.json'); 12 | 13 | export class ParseClientConfig { 14 | apps?: Array; 15 | log?: boolean; 16 | pluginVersion?: number; 17 | isNode?: boolean; 18 | plugins?: { 19 | storage?: IStorage, 20 | websocket?: IWebSocketClient 21 | } 22 | } 23 | 24 | export class ParseClient { 25 | 26 | /** 27 | * 28 | * 29 | * @static 30 | * @param {ParseClientConfig} [config] 31 | * @returns {ParseClient} 32 | * @memberof ParseClient 33 | */ 34 | static init(config?: ParseClientConfig): ParseClient { 35 | return ParseClient.instance.initialize(config); 36 | } 37 | 38 | currentApp: ParseApp; 39 | remotes: Array = []; 40 | add(app: ParseApp, replace?: boolean) { 41 | if (this.remotes.length == 0 || (typeof replace != 'undefined' && replace)) { 42 | if (app.shortName == null) { 43 | app.shortName = 'default'; 44 | } 45 | this.currentApp = app; 46 | } 47 | this.remotes.push(app); 48 | this.printWelcome(app); 49 | return this as ParseClient; 50 | } 51 | 52 | take(options?: any) { 53 | let app: ParseApp = null; 54 | if (!options) { 55 | return ParseClient.instance.currentApp; 56 | } else { 57 | if (options.app) { 58 | if (options.app instanceof ParseApp) { 59 | app = options.app; 60 | } 61 | } else if (options.appName) { 62 | if (typeof options.appName === "string") { 63 | let tempApp = this.remotes.find(a => { 64 | return a.shortName == options.appName; 65 | }); 66 | if (tempApp) { 67 | app = tempApp; 68 | } 69 | } 70 | } else { 71 | app = ParseClient.instance.currentApp; 72 | } 73 | } 74 | return app; 75 | } 76 | 77 | private _switch(shortname: string) { 78 | let tempApp = this.remotes.find(app => { 79 | return app.shortName == shortname; 80 | }); 81 | if (tempApp) { 82 | this.currentApp = tempApp; 83 | } 84 | return this as ParseClient; 85 | } 86 | 87 | 88 | public get SDKVersion(): string { 89 | return sdkInfo.version; 90 | } 91 | 92 | public isNode() { 93 | return this.currentConfiguration.isNode; 94 | } 95 | 96 | public static inLeanEngine() { 97 | return false; 98 | } 99 | 100 | protected printWelcome(app: ParseApp) { 101 | ParseClient.printLog('=== LeanCloud-Typescript-Rx-SDK ==='); 102 | ParseClient.printLog(`pluginVersion:${this.currentConfiguration.pluginVersion}`); 103 | ParseClient.printLog(`environment:node?${this.currentConfiguration.isNode}`); 104 | ParseClient.printLog(`appId:${app.appId}`); 105 | ParseClient.printLog(`serverURL:${app.serverURL}`); 106 | ParseClient.printLog(`appKey:${app.appKey}`); 107 | ParseClient.printLog(`masterKey:${app.masterKey}`); 108 | ParseClient.printLog('=== Rx is great, Typescript is wonderful! ==='); 109 | } 110 | 111 | public static printHttpLog(request?: HttpRequest, response?: HttpResponse) { 112 | if (request) { 113 | ParseClient.printLog("===HTTP-START==="); 114 | ParseClient.printLog("===Request-START==="); 115 | ParseClient.printLog("Url: ", request.url); 116 | ParseClient.printLog("Method: ", request.method); 117 | ParseClient.printLog("Headers: ", JSON.stringify(request.headers)); 118 | ParseClient.printLog("RequestBody: " + JSON.stringify(request.data)); 119 | ParseClient.printLog("===Request-END==="); 120 | } 121 | if (response) { 122 | ParseClient.printLog("===Response-START==="); 123 | ParseClient.printLog("StatusCode: ", response.statusCode); 124 | ParseClient.printLog("ResponseBody: ", response.body); 125 | ParseClient.printLog("===Response-END==="); 126 | ParseClient.printLog("===HTTP-END==="); 127 | } 128 | } 129 | 130 | public static printLog(message?: any, ...optionalParams: any[]) { 131 | if (ParseClient.instance.currentConfiguration.log) { 132 | if (optionalParams.length > 0) 133 | console.log(message, ...optionalParams); 134 | else console.log(message); 135 | } 136 | } 137 | 138 | protected static generateParseCommand(relativeUrl: string, method: string, data?: { [key: string]: any }, sessionToken?: string, app?: ParseApp): ParseCommand { 139 | let cmd = new ParseCommand({ 140 | app: app, 141 | relativeUrl: relativeUrl, 142 | method: method, 143 | data: data, 144 | sessionToken: sessionToken 145 | }); 146 | return cmd; 147 | } 148 | 149 | public rxRunCommandSuccess(relativeUrl: string, method: string, data?: { [key: string]: any }) { 150 | let cmd = ParseClient.generateParseCommand(relativeUrl, method, data); 151 | return SDKPlugins.instance.commandRunner.runRxCommand(cmd).pipe(map(res => { 152 | return res.statusCode == 200; 153 | })); 154 | } 155 | 156 | public static runCommand(relativeUrl: string, method: string, data?: { [key: string]: any }, sessionToken?: string, app?: ParseApp): Observable<{ [key: string]: any }> { 157 | let cmd = ParseClient.generateParseCommand(relativeUrl, method, data, sessionToken, app); 158 | return SDKPlugins.instance.commandRunner.runRxCommand(cmd).pipe(map(res => { 159 | return res.body; 160 | })); 161 | } 162 | 163 | private static _avClientInstance: ParseClient; 164 | 165 | /** 166 | * 167 | * 168 | * @readonly 169 | * @static 170 | * @type {ParseClient} 171 | * @memberof ParseClient 172 | */ 173 | static get instance(): ParseClient { 174 | if (ParseClient._avClientInstance == null) 175 | ParseClient._avClientInstance = new ParseClient(); 176 | return ParseClient._avClientInstance; 177 | } 178 | 179 | currentConfiguration: ParseClientConfig = {}; 180 | public initialize(config: ParseClientConfig) { 181 | 182 | // process.on('uncaughtException', function (err) { 183 | // console.error("Caught exception:", err.stack); 184 | // }); 185 | // process.on('unhandledRejection', function (reason, p) { 186 | // console.error("Unhandled Rejection at: Promise ", p, " reason: ", reason.stack); 187 | // }); 188 | 189 | if (typeof config != 'undefined') { 190 | this.currentConfiguration = config; 191 | 192 | if (typeof (process) !== 'undefined' && process.versions && process.versions.node) { 193 | if (this.currentConfiguration.isNode == undefined) 194 | this.currentConfiguration.isNode = true; 195 | } 196 | 197 | if (config.apps) { 198 | config.apps.forEach(appConfig => { 199 | let app = new ParseApp(appConfig); 200 | this.add(app); 201 | }); 202 | } 203 | 204 | SDKPlugins.version = config.pluginVersion; 205 | if (config.plugins) { 206 | if (config.plugins.storage) { 207 | SDKPlugins.instance.StorageProvider = config.plugins.storage; 208 | SDKPlugins.instance.LocalStorageControllerInstance = new StorageController(config.plugins.storage); 209 | } 210 | if (config.plugins.websocket) { 211 | SDKPlugins.instance.WebSocketProvider = config.plugins.websocket; 212 | } 213 | } 214 | } 215 | 216 | return this as ParseClient; 217 | } 218 | 219 | public request(url: string, method?: string, headers?: { [key: string]: any }, data?: { [key: string]: any }): Observable<{ [key: string]: any }> { 220 | let httpRequest = new HttpRequest(); 221 | httpRequest.url = url; 222 | httpRequest.method = "GET"; 223 | httpRequest.headers = {}; 224 | if (method) 225 | httpRequest.method = method; 226 | if (data) 227 | httpRequest.data = data; 228 | if (headers) 229 | httpRequest.headers = headers; 230 | return SDKPlugins.instance.httpClient.execute(httpRequest); 231 | } 232 | 233 | } 234 | 235 | export class ParseAppConfig { 236 | appId: string; 237 | serverURL: string; 238 | appKey?: string; 239 | masterKey?: string; 240 | shortName?: string; 241 | additionalHeaders?: { [key: string]: any }; 242 | } 243 | 244 | /** 245 | * 246 | * 247 | * @export 248 | * @class ParseApp 249 | */ 250 | export class ParseApp { 251 | 252 | constructor(options: ParseAppConfig) { 253 | this.appId = options.appId; 254 | this.serverURL = options.serverURL; 255 | this.appKey = options.appKey; 256 | this.masterKey = options.masterKey; 257 | this.shortName = options.shortName; 258 | this.additionalHeaders = options.additionalHeaders; 259 | } 260 | shortName: string; 261 | appId: string; 262 | appKey: string; 263 | serverURL: string; 264 | masterKey: string; 265 | additionalHeaders?: { [key: string]: any }; 266 | 267 | get pureServerURL(): string { 268 | return this.clearTailSlashes(this.serverURL); 269 | } 270 | 271 | get mountPath(): string { 272 | return this.pureServerURL.replace(/^(?:\/\/|[^\/]+)*\//, "") 273 | } 274 | 275 | clearTailSlashes(url: string): string { 276 | if (url.endsWith('/')) { 277 | url = url.substring(0, url.length - 1); 278 | return this.clearTailSlashes(url); 279 | } else 280 | return url; 281 | } 282 | 283 | get httpHeaders() { 284 | let headers: { [key: string]: any } = {}; 285 | headers = { 286 | 'X-Parse-Application-Id': this.appId, 287 | 'Content-Type': 'application/json;charset=utf-8' 288 | }; 289 | if (this.appKey) { 290 | headers['X-Parse-Javascript-Key'] = this.appKey; 291 | } 292 | if (this.masterKey) { 293 | headers['X-Parse-Master-Key'] = this.masterKey; 294 | } 295 | if (ParseClient.instance.isNode()) { 296 | headers['User-Agent'] = 'ts-sdk/' + sdkInfo.version; 297 | } 298 | if (this.additionalHeaders) { 299 | for (let key in this.additionalHeaders) { 300 | headers[key] = this.additionalHeaders[key]; 301 | } 302 | } 303 | return headers; 304 | } 305 | } 306 | -------------------------------------------------------------------------------- /src/public/RxParseCloud.ts: -------------------------------------------------------------------------------- 1 | import { SDKPlugins } from '../internal/ParseClientPlugins'; 2 | import { RxParseUser, ParseApp } from '../RxParse'; 3 | import { IParseCloudController } from '../internal/cloud/controller/IParseCloudController'; 4 | import { Observable } from 'rxjs'; 5 | import { flatMap } from 'rxjs/operators'; 6 | /** 7 | * 8 | * 9 | * @export 10 | * @class RxParseCloud 11 | */ 12 | export class RxParseCloud { 13 | 14 | /** 15 | * 16 | * 17 | * @readonly 18 | * @protected 19 | * @static 20 | * @type {IParseCloudController} 21 | * @memberof RxParseCloud 22 | */ 23 | protected static get LeanEngineController(): IParseCloudController { 24 | return SDKPlugins.instance.cloudController; 25 | } 26 | 27 | /** 28 | * 29 | * 30 | * @static 31 | * @param {string} name 32 | * @param {{ [key: string]: any }} [parameters] 33 | * @param {ParseApp} [app] 34 | * @returns 35 | * @memberof RxParseCloud 36 | */ 37 | static run(name: string, parameters?: { [key: string]: any }, app?: ParseApp): Observable<{ [key: string]: any }> { 38 | return RxParseUser.currentSessionToken().pipe(flatMap(sessionToken => { 39 | return RxParseCloud.LeanEngineController.callFunction(name, parameters, sessionToken); 40 | })); 41 | } 42 | } -------------------------------------------------------------------------------- /src/public/RxParseInstallation.ts: -------------------------------------------------------------------------------- 1 | import { Observable, from } from 'rxjs'; 2 | import { RxParseObject } from '../RxParse'; 3 | import { SDKPlugins } from '../internal/ParseClientPlugins'; 4 | import { IObjectState } from '../internal/object/state/IObjectState'; 5 | import * as jstz from 'jstz'; 6 | import { flatMap, map } from 'rxjs/operators'; 7 | 8 | export class RxParseInstallation extends RxParseObject { 9 | static readonly installationCacheKey = 'CurrentInstallation'; 10 | get channels() { 11 | return this.get('channels'); 12 | } 13 | set channels(data: Array) { 14 | this.set('channels', data); 15 | } 16 | get badge() { 17 | return this.get('badge'); 18 | } 19 | set badge(data: number) { 20 | this.set('badge', data); 21 | } 22 | get deviceType() { 23 | return this.get('deviceType'); 24 | } 25 | set deviceType(data: string) { 26 | this.set('deviceType', data); 27 | } 28 | get deviceToken() { 29 | return this.get('deviceToken'); 30 | } 31 | set deviceToken(data: string) { 32 | this.set('deviceToken', data); 33 | } 34 | 35 | get installationId() { 36 | return this.get('installationId'); 37 | } 38 | 39 | set installationId(value: string) { 40 | this.set('installationId', value); 41 | } 42 | 43 | get timeZone() { 44 | return jstz.determine().name(); 45 | } 46 | 47 | constructor() { 48 | super('_Installation'); 49 | this.set('timeZone', this.timeZone); 50 | } 51 | 52 | save(): Observable { 53 | return super.save().pipe(flatMap(s1 => { 54 | if (s1) 55 | return RxParseInstallation.saveCurrentInstallation(this); 56 | else return from([false]); 57 | })); 58 | } 59 | 60 | public static current(): Observable { 61 | if (SDKPlugins.instance.hasStorage) { 62 | return SDKPlugins.instance.LocalStorageControllerInstance.get(RxParseInstallation.installationCacheKey).pipe(map(installationCache => { 63 | if (installationCache) { 64 | let installationState = SDKPlugins.instance.ObjectDecoder.decode(installationCache, SDKPlugins.instance.Decoder); 65 | installationState = installationState.mutatedClone((s: IObjectState) => { }); 66 | let installation = RxParseObject.createSubclass(RxParseInstallation, ''); 67 | installation.handleFetchResult(installationState); 68 | RxParseInstallation._currentInstallation = installation; 69 | } 70 | return RxParseInstallation._currentInstallation; 71 | })); 72 | } 73 | return from([RxParseInstallation._currentInstallation]); 74 | } 75 | 76 | public static get currentInstallation() { 77 | return RxParseInstallation._currentInstallation; 78 | } 79 | private static _currentInstallation: RxParseInstallation; 80 | static saveCurrentInstallation(installation: RxParseInstallation) { 81 | RxParseInstallation._currentInstallation = installation; 82 | return RxParseObject.saveToLocalStorage(installation, RxParseInstallation.installationCacheKey); 83 | } 84 | } -------------------------------------------------------------------------------- /src/public/RxParseObject.ts: -------------------------------------------------------------------------------- 1 | import { SDKPlugins } from '../internal/ParseClientPlugins'; 2 | import { IObjectState } from '../internal/object/state/IObjectState'; 3 | import { IParseFieldOperation } from '../internal/operation/IParseFieldOperation'; 4 | import { ParseAddOperation, ParseAddUniqueOperation } from '../internal/operation/ParseAddOperation'; 5 | import { ParseSetOperation } from '../internal/operation/ParseSetOperation'; 6 | import { ParseDeleteOperation, ParseDeleteToken } from '../internal/operation/ParseDeleteOperation'; 7 | import { ParseRemoveOperation } from '../internal/operation/ParseRemoveOperation'; 8 | import { IObjectController } from '../internal/object/controller/IParseObjectController'; 9 | import { IStorageController } from '../internal/storage/controller/IStorageController'; 10 | import { MutableObjectState } from '../internal/object/state/MutableObjectState'; 11 | import { RxParseUser, RxParseACL, ParseClient, RxParseQuery } from '../RxParse'; 12 | import { Observable, from } from 'rxjs'; 13 | import { flatMap, map, concat } from 'rxjs/operators'; 14 | 15 | export class StorageObject { 16 | estimatedData: Map; 17 | currentOperations = new Map(); 18 | state: MutableObjectState; 19 | 20 | protected _isDirty: boolean; 21 | public get isDirty() { 22 | return this._isDirty; 23 | } 24 | 25 | public set isDirty(v: boolean) { 26 | this._isDirty = v; 27 | } 28 | 29 | public get className() { 30 | return this.state.className; 31 | } 32 | 33 | public set className(className: string) { 34 | this.state.className = className; 35 | } 36 | 37 | constructor(className: string, options?: any) { 38 | this.state = new MutableObjectState({ className: className }); 39 | this.state.serverData = new Map(); 40 | this.state.app = ParseClient.instance.take(options); 41 | 42 | this.estimatedData = new Map(); 43 | this.isDirty = true; 44 | } 45 | 46 | public get objectId() { 47 | return this.state.objectId; 48 | } 49 | 50 | public set objectId(id: string) { 51 | this.isDirty = true; 52 | this.state.objectId = id; 53 | } 54 | 55 | unset(key: string) { 56 | this.performOperation(key, ParseDeleteOperation.sharedInstance); 57 | } 58 | 59 | public set(key: string, value: any) { 60 | if (value == null || value == undefined) { 61 | this.unset(key); 62 | } else { 63 | let valid = SDKPlugins.instance.Encoder.isValidType(value); 64 | if (valid) { 65 | this.performOperation(key, new ParseSetOperation(value)); 66 | } 67 | } 68 | } 69 | 70 | public get(key: string): any { 71 | return this.estimatedData.get(key); 72 | } 73 | 74 | public get createdAt() { 75 | return this.state.createdAt; 76 | } 77 | 78 | public get updatedAt() { 79 | return this.state.updatedAt; 80 | } 81 | 82 | protected initProperty(propertyName: string, value: any) { 83 | if (!this.objectId) { 84 | this.state.serverData[propertyName] = value; 85 | } 86 | else { 87 | throw new Error(`can not reset property '${propertyName}'`); 88 | } 89 | } 90 | 91 | protected setProperty(propertyName: string, value: any) { 92 | if (this.state && this.state != null) { 93 | this.state.serverData[propertyName] = value; 94 | } 95 | } 96 | 97 | protected getProperty(propertyName: string) { 98 | if (this.state != null) { 99 | if (this.state.containsKey(propertyName)) 100 | return this.state.serverData[propertyName]; 101 | } 102 | return null; 103 | } 104 | 105 | protected performOperation(key: string, operation: IParseFieldOperation) { 106 | let oldValue = this.estimatedData.get(key); 107 | let newValue = operation.apply(oldValue, key); 108 | 109 | if (newValue instanceof ParseDeleteToken) { 110 | this.estimatedData.delete(key); 111 | } else { 112 | this.estimatedData.set(key, newValue); 113 | } 114 | 115 | let oldOperation = this.currentOperations.get(key); 116 | let newOperation = operation.mergeWithPrevious(oldOperation); 117 | this.currentOperations.set(key, newOperation); 118 | if (this.currentOperations.size > 0) { 119 | this.isDirty = true; 120 | } 121 | } 122 | } 123 | 124 | /** 125 | * 126 | * 127 | * @export 128 | * @class RxParseObject 129 | * @extends {StorageObject} 130 | * @implements {ICanSaved} 131 | */ 132 | export class RxParseObject extends StorageObject implements ICanSaved { 133 | 134 | private _isNew: boolean; 135 | private _acl: RxParseACL; 136 | 137 | constructor(className: string, options?: any) { 138 | 139 | super(className, options); 140 | 141 | this.className = className; 142 | } 143 | 144 | protected static get _objectController(): IObjectController { 145 | return SDKPlugins.instance.objectController; 146 | } 147 | 148 | get ACL() { 149 | return this._acl; 150 | } 151 | 152 | set ACL(acl: RxParseACL) { 153 | this._acl = acl; 154 | this.set('ACL', this._acl); 155 | } 156 | 157 | 158 | add(key: string, value: any) { 159 | this.addAll(key, [value]); 160 | } 161 | 162 | addAll(key: string, values: Array) { 163 | this.performOperation(key, new ParseAddOperation(values)); 164 | } 165 | 166 | addAllUnique(key: string, values: Array) { 167 | this.performOperation(key, new ParseAddUniqueOperation(values)); 168 | } 169 | 170 | addUnique(key: string, value: any) { 171 | this.performOperation(key, new ParseAddUniqueOperation([value])); 172 | } 173 | 174 | removeAll(key: string, value: Array) { 175 | this.performOperation(key, new ParseRemoveOperation(value)); 176 | } 177 | 178 | remove(key: string, value: any) { 179 | this.performOperation(key, new ParseRemoveOperation([value])); 180 | } 181 | 182 | /** 183 | * 184 | * 185 | * @returns 186 | * @memberof RxParseObject 187 | */ 188 | public save(): Observable { 189 | let rtn: Observable = from([true]); 190 | if (!this.isDirty) return rtn; 191 | RxParseObject.recursionCollectDirtyChildren(this, [], [], []); 192 | 193 | let dirtyChildren = this.collectAllLeafNodes(); 194 | 195 | if (dirtyChildren.length > 0) { 196 | rtn = RxParseObject.batchSave(dirtyChildren).pipe(flatMap(sal => this.save())); 197 | } else { 198 | return RxParseUser.currentSessionToken().pipe(flatMap(sessionToken => { 199 | return rtn = RxParseObject._objectController.save(this.state, this.currentOperations, sessionToken).pipe(map(serverState => { 200 | this.handlerSave(serverState); 201 | return true; 202 | })); 203 | })); 204 | } 205 | return rtn; 206 | } 207 | 208 | public fetch(): Observable { 209 | if (this.objectId == null) throw new Error(`Cannot refresh an object that hasn't been saved to the server.`); 210 | return RxParseUser.currentSessionToken().pipe(flatMap(sessionToken => { 211 | return RxParseObject._objectController.fetch(this.state, sessionToken).pipe(map(serverState => { 212 | this.handleFetchResult(serverState); 213 | return this; 214 | })); 215 | })); 216 | } 217 | 218 | public delete(): Observable { 219 | if (this.objectId == null) throw new Error(`Cannot delete an object that hasn't been saved to the server.`); 220 | return RxParseUser.currentSessionToken().pipe(flatMap(sessionToken => { 221 | return RxParseObject._objectController.delete(this.state, sessionToken); 222 | })); 223 | } 224 | 225 | public static deleteAll(objects: Array): Observable { 226 | let r: Observable; 227 | objects.forEach(obj => { 228 | let d = obj.delete(); 229 | if (r == null || typeof r == 'undefined') { 230 | r = d; 231 | } else { 232 | r = d.pipe(concat(r)); 233 | } 234 | }); 235 | return r; 236 | } 237 | 238 | public static createWithoutData(className: string, objectId: string) { 239 | let rtn = new RxParseObject(className); 240 | rtn.objectId = objectId; 241 | rtn.isDirty = false; 242 | return rtn; 243 | } 244 | 245 | public static createSubclass( 246 | ctor: { new(): T; }, objectId: string): T { 247 | let rtn = new ctor(); 248 | rtn.objectId = objectId; 249 | rtn.isDirty = false; 250 | return rtn; 251 | } 252 | 253 | public static instantiateSubclass(className: string, serverState: IObjectState) { 254 | if (className == '_User') { 255 | let user = RxParseObject.createSubclass(RxParseUser, serverState.objectId); 256 | user.handleFetchResult(serverState); 257 | return user; 258 | } 259 | let rxObject = new RxParseObject(className); 260 | rxObject.handleFetchResult(serverState); 261 | return rxObject; 262 | } 263 | 264 | public static saveAll(objects: Array) { 265 | return this.batchSave(objects); 266 | } 267 | 268 | protected static batchSave(objArray: Array) { 269 | let states = objArray.map(c => c.state); 270 | let ds = objArray.map(c => c.currentOperations); 271 | return RxParseUser.currentSessionToken().pipe(flatMap(sessionToken => { 272 | return RxParseObject._objectController.batchSave(states, ds, sessionToken).pipe(map(serverStateArray => { 273 | objArray.forEach((dc, i, a) => { 274 | dc.handlerSave(serverStateArray[i]); 275 | }); 276 | return true; 277 | })); 278 | })); 279 | 280 | } 281 | 282 | protected static deepSave(obj: RxParseObject) { 283 | let dirtyChildren: Array = []; 284 | for (let key in obj.estimatedData) { 285 | let value = obj.estimatedData[key]; 286 | if (value instanceof RxParseObject) { 287 | if (value.isDirty) { 288 | dirtyChildren.push(value); 289 | } 290 | } 291 | } 292 | if (dirtyChildren.length == 0) return from([true]); 293 | return RxParseObject.saveAll(dirtyChildren); 294 | } 295 | 296 | protected collectDirtyChildren() { 297 | let dirtyChildren: Array = []; 298 | 299 | this.estimatedData.forEach((v, k, m) => { 300 | if (v instanceof RxParseObject) { 301 | if (v.isDirty) { 302 | dirtyChildren.push(v); 303 | } 304 | } 305 | }); 306 | return dirtyChildren; 307 | } 308 | 309 | collectAllLeafNodes() { 310 | let leafNodes: Array = []; 311 | let dirtyChildren = this.collectDirtyChildren(); 312 | 313 | dirtyChildren.map(child => { 314 | let childLeafNodes = child.collectAllLeafNodes(); 315 | if (childLeafNodes.length == 0) { 316 | if (child.isDirty) { 317 | leafNodes.push(child); 318 | } 319 | } else { 320 | leafNodes = leafNodes.concat(childLeafNodes); 321 | } 322 | }); 323 | 324 | return leafNodes; 325 | } 326 | 327 | static recursionCollectDirtyChildren(root: RxParseObject, warehouse: Array, seen: Array, seenNew: Array) { 328 | 329 | let dirtyChildren = root.collectDirtyChildren(); 330 | dirtyChildren.map(child => { 331 | let scopedSeenNew: Array = []; 332 | if (seenNew.indexOf(child) > -1) { 333 | throw new Error('Found a circular dependency while saving'); 334 | } 335 | scopedSeenNew = scopedSeenNew.concat(seenNew); 336 | scopedSeenNew.push(child); 337 | 338 | if (seen.indexOf(child) > -1) { 339 | return; 340 | } 341 | seen.push(child); 342 | RxParseObject.recursionCollectDirtyChildren(child, warehouse, seen, scopedSeenNew); 343 | warehouse.push(child); 344 | }); 345 | } 346 | 347 | handlerSave(serverState: IObjectState) { 348 | this.state.merge(serverState); 349 | this.isDirty = false; 350 | } 351 | 352 | handleFetchResult(serverState: IObjectState) { 353 | this.state.apply(serverState); 354 | this.rebuildEstimatedData(); 355 | this._isNew = false; 356 | this.isDirty = false; 357 | } 358 | 359 | protected mergeFromServer(serverState: IObjectState) { 360 | if (serverState.objectId != null) { 361 | 362 | } 363 | } 364 | 365 | protected rebuildEstimatedData() { 366 | this.estimatedData = new Map(); 367 | this.estimatedData = this.state.serverData; 368 | } 369 | 370 | protected buildRelation(op: string, opEntities: Array) { 371 | if (opEntities) { 372 | let action = op == 'add' ? 'AddRelation' : 'RemoveRelation'; 373 | let body: { [key: string]: any } = {}; 374 | let encodedEntities = SDKPlugins.instance.Encoder.encode(opEntities); 375 | body = { 376 | __op: action, 377 | 'objects': encodedEntities 378 | }; 379 | return body; 380 | } 381 | } 382 | 383 | public fetchRelation(key: string, targetClassName): Observable { 384 | let query = new RxParseQuery(targetClassName); 385 | query.relatedTo(this, key); 386 | return query.find(); 387 | } 388 | 389 | static saveToLocalStorage(entity: ICanSaved, key: string) { 390 | if (SDKPlugins.instance.hasStorage) { 391 | if (entity == null) { 392 | return SDKPlugins.instance.LocalStorageControllerInstance.remove(key).pipe(map(provider => { 393 | return provider != null; 394 | })); 395 | } else { 396 | return SDKPlugins.instance.LocalStorageControllerInstance.set(key, entity.toJSONObjectForSaving()).pipe(map(provider => { 397 | return provider != null; 398 | })); 399 | } 400 | } else { 401 | return from([true]); 402 | } 403 | } 404 | 405 | toJSONObjectForSaving() { 406 | let data = this.estimatedData; 407 | data['objectId'] = this.objectId; 408 | data['createdAt'] = this.createdAt; 409 | data['updatedAt'] = this.updatedAt; 410 | let encoded = SDKPlugins.instance.Encoder.encode(data); 411 | return encoded; 412 | } 413 | } 414 | 415 | export interface ICanSaved { 416 | toJSONObjectForSaving(): { [key: string]: any }; 417 | } 418 | 419 | -------------------------------------------------------------------------------- /src/public/RxParsePush.ts: -------------------------------------------------------------------------------- 1 | import { Observable, Subject, from } from 'rxjs'; 2 | import { ParseClient, RxParseObject, RxParseQuery, RxParseUser, ParseApp, RxParseInstallation } from '../RxParse'; 3 | import { ParseCommand } from '../internal/command/ParseCommand'; 4 | import { SDKPlugins } from '../internal/ParseClientPlugins'; 5 | import { flatMap, map } from 'rxjs/operators'; 6 | 7 | /** 8 | * 9 | * 10 | * @export 11 | * @class RxParsePush 12 | */ 13 | export class RxParsePush { 14 | 15 | query: RxParseQuery; 16 | channels: Array; 17 | expiration: Date; 18 | pushTime: Date; 19 | expirationInterval: number; 20 | data: { [key: string]: any }; 21 | alert: string; 22 | prod: string; 23 | 24 | constructor() { 25 | this.query = new RxParseQuery('_Installation'); 26 | } 27 | 28 | /** 29 | * 30 | * 31 | * @static 32 | * @param {(string | { [key: string]: any })} data 33 | * @param {{ 34 | * channels?: Array, 35 | * query?: RxParseQuery, 36 | * prod?: string 37 | * }} filter 38 | * @returns 39 | * @memberof RxParsePush 40 | */ 41 | public static sendContent(data: string | { [key: string]: any }, filter: { 42 | channels?: Array, 43 | query?: RxParseQuery, 44 | prod?: string 45 | }) { 46 | let push = new RxParsePush(); 47 | if (typeof data === 'string') { 48 | push.alert = data; 49 | } else { 50 | push.data = data; 51 | } 52 | if (filter.channels) 53 | push.channels = filter.channels; 54 | if (filter.query) 55 | push.query = filter.query; 56 | if (filter.prod) { 57 | push.prod = filter.prod; 58 | } 59 | return push.send(); 60 | } 61 | 62 | /** 63 | * 64 | * 65 | * @static 66 | * @param {(RxParseUser | string)} user 67 | * @param {(string | { [key: string]: any })} data 68 | * @param {string} [prod] 69 | * @returns 70 | * @memberof RxParsePush 71 | */ 72 | public static sendTo(user: RxParseUser | string, data: string | { [key: string]: any }, prod?: string) { 73 | let u: RxParseUser; 74 | if (user != undefined) { 75 | if (typeof user == 'string') { 76 | u = RxParseUser.createWithoutData(user); 77 | } else if (user instanceof RxParseUser) { 78 | u = user; 79 | } 80 | } 81 | let query = new RxParseQuery('_Installation'); 82 | query.relatedTo(u, RxParseUser.installationKey); 83 | return RxParsePush.sendContent(data, { 84 | query: query, 85 | prod: prod 86 | }); 87 | } 88 | 89 | public send(): Observable { 90 | let data = this.encode(); 91 | return RxParseUser.currentSessionToken().pipe(flatMap(sessionToken => { 92 | return ParseClient.runCommand('/push', 'POST', data, sessionToken, this.query.app).pipe(map(body => { 93 | return true; 94 | })); 95 | })); 96 | } 97 | 98 | protected encode() { 99 | if (!this.alert && !this.data) throw new Error('A push must have either an Alert or Data'); 100 | if (!this.channels && !this.query) throw new Error('A push must have either Channels or a Query'); 101 | let data = this.data ? this.data : { alert: this.alert }; 102 | if (this.channels) 103 | this.query = this.query.containedIn("channels", this.channels); 104 | let payload: { [key: string]: any } = { 105 | data: data, 106 | where: this.query.buildParameters()['where'] 107 | }; 108 | if (this.prod) { 109 | payload['prod'] = this.prod; 110 | } 111 | if (this.expiration) 112 | payload["expiration_time"] = this.expiration; 113 | else if (!this.expirationInterval) { 114 | payload["expiration_interval"] = this.expirationInterval; 115 | } 116 | if (this.pushTime) { 117 | payload["push_time"] = this.pushTime; 118 | } 119 | return payload; 120 | } 121 | 122 | public static getInstallation(deviceType: string) { 123 | return RxParseInstallation.current().pipe(flatMap(currentInstallation => { 124 | if (currentInstallation != undefined) 125 | return from([currentInstallation]); 126 | else { 127 | let installation = new RxParseInstallation(); 128 | installation.deviceType = deviceType; 129 | installation.installationId = SDKPlugins.instance.ToolControllerInstance.newObjectId(); 130 | return installation.save().pipe(map(created => { 131 | return installation; 132 | })); 133 | } 134 | })); 135 | } 136 | } -------------------------------------------------------------------------------- /src/public/RxParseQuery.ts: -------------------------------------------------------------------------------- 1 | import { ParseClient, RxParseObject, RxParseUser, ICanSaved, ParseApp } from '../RxParse'; 2 | import { SDKPlugins } from '../internal/ParseClientPlugins'; 3 | import { IParseEncoder } from '../internal/encoding/IParseEncoder'; 4 | import { IQueryController } from '../internal/query/controller/IQueryController'; 5 | import { ParseCommand } from '../internal/command/ParseCommand'; 6 | import { IRxWebSocketController } from '../internal/websocket/controller/IRxWebSocketController'; 7 | import { flatMap, map, filter } from 'rxjs/operators'; 8 | import { Observable, from } from 'rxjs'; 9 | /** 10 | * 11 | * 12 | * @export 13 | * @class RxParseQuery 14 | */ 15 | export class RxParseQuery { 16 | 17 | constructor(objectClass: string | RxParseObject, options?: any) { 18 | if (typeof objectClass === 'string') { 19 | this.className = objectClass; 20 | this._app = ParseClient.instance.take(options); 21 | } else if (objectClass instanceof RxParseObject) { 22 | this.className = objectClass.className; 23 | this._app = objectClass.state.app; 24 | } 25 | else { 26 | throw new Error('A RxParseQuery must be constructed with a RxParseObject or class name.'); 27 | } 28 | this._where = {}; 29 | this._include = []; 30 | this._limit = -1; // negative limit is not sent in the server request 31 | this._skip = 0; 32 | this._extraOptions = {}; 33 | } 34 | 35 | className: string; 36 | protected _app: ParseApp; 37 | get app() { 38 | return this._app; 39 | } 40 | protected _where: any; 41 | protected _include: Array; 42 | protected _select: Array; 43 | protected _limit: number; 44 | protected _skip: number; 45 | protected _order: Array; 46 | protected _extraOptions: { [key: string]: any }; 47 | 48 | protected static get _encoder(): IParseEncoder { 49 | return SDKPlugins.instance.Encoder; 50 | } 51 | 52 | protected static get _queryController(): IQueryController { 53 | return SDKPlugins.instance.queryController; 54 | } 55 | 56 | config(filter: Array<{ 57 | key: string, 58 | constraint: string, 59 | value: any 60 | }>, limit: number, skip: number, include: string[], select: string[]): RxParseQuery { 61 | return new RxParseQuery(this.className); 62 | } 63 | 64 | equalTo(key: string, value: any): RxParseQuery { 65 | this._where[key] = this._encode(value, false, true); 66 | return this; 67 | } 68 | 69 | notEqualTo(key: string, value: any): RxParseQuery { 70 | return this._addCondition(key, '$ne', value); 71 | } 72 | 73 | lessThan(key: string, value: any): RxParseQuery { 74 | return this._addCondition(key, '$lt', value); 75 | } 76 | 77 | lessThanOrEqualTo(key: string, value: any): RxParseQuery { 78 | return this._addCondition(key, '$lte', value); 79 | } 80 | 81 | greaterThan(key: string, value: any): RxParseQuery { 82 | return this._addCondition(key, '$gt', value); 83 | } 84 | 85 | greaterThanOrEqualTo(key: string, value: any): RxParseQuery { 86 | return this._addCondition(key, '$gte', value); 87 | } 88 | 89 | containedIn(key: string, value: any): RxParseQuery { 90 | return this._addCondition(key, '$in', value); 91 | } 92 | 93 | notContainedIn(key: string, value: any): RxParseQuery { 94 | return this._addCondition(key, '$nin', value); 95 | } 96 | 97 | containsAll(key: string, values: Array): RxParseQuery { 98 | return this._addCondition(key, '$all', values); 99 | } 100 | 101 | exists(key: string): RxParseQuery { 102 | return this._addCondition(key, '$exists', true); 103 | } 104 | 105 | doesNotExist(key: string): RxParseQuery { 106 | return this._addCondition(key, '$exists', false); 107 | } 108 | 109 | contains(key: string, value: string): RxParseQuery { 110 | if (typeof value !== 'string') { 111 | throw new Error('The value being searched for must be a string.'); 112 | } 113 | return this._addCondition(key, '$regex', this.quote(value)); 114 | } 115 | 116 | startsWith(key: string, value: string): RxParseQuery { 117 | if (typeof value !== 'string') { 118 | throw new Error('The value being searched for must be a string.'); 119 | } 120 | return this._addCondition(key, '$regex', '^' + this.quote(value)); 121 | } 122 | 123 | endsWith(key: string, value: string): RxParseQuery { 124 | if (typeof value !== 'string') { 125 | throw new Error('The value being searched for must be a string.'); 126 | } 127 | return this._addCondition(key, '$regex', this.quote(value) + '$'); 128 | } 129 | 130 | protected quote(s: string) { 131 | return '\\Q' + s.replace('\\E', '\\E\\\\E\\Q') + '\\E'; 132 | } 133 | 134 | relatedTo(parent: RxParseObject, key: string) { 135 | this._addCondition('$relatedTo', 'object', { 136 | __type: 'Pointer', 137 | className: parent.className, 138 | objectId: parent.objectId 139 | }); 140 | return this._addCondition('$relatedTo', 'key', key); 141 | } 142 | 143 | ascending(...keys: Array): RxParseQuery { 144 | this._order = []; 145 | return this.addAscending.apply(this, keys); 146 | } 147 | 148 | addAscending(...keys: Array): RxParseQuery { 149 | if (!this._order) { 150 | this._order = []; 151 | } 152 | keys.forEach((key) => { 153 | if (Array.isArray(key)) { 154 | key = key.join(); 155 | } 156 | this._order = this._order.concat(key.replace(/\s/g, '').split(',')); 157 | }); 158 | 159 | return this; 160 | } 161 | 162 | descending(...keys: Array): RxParseQuery { 163 | this._order = []; 164 | return this.addDescending.apply(this, keys); 165 | } 166 | 167 | addDescending(...keys: Array): RxParseQuery { 168 | if (!this._order) { 169 | this._order = []; 170 | } 171 | keys.forEach((key) => { 172 | if (Array.isArray(key)) { 173 | key = key.join(); 174 | } 175 | this._order = this._order.concat( 176 | key.replace(/\s/g, '').split(',').map((k) => { 177 | return '-' + k; 178 | }) 179 | ); 180 | }); 181 | 182 | return this; 183 | } 184 | 185 | skip(n: number): RxParseQuery { 186 | if (typeof n !== 'number' || n < 0) { 187 | throw new Error('You can only skip by a positive number'); 188 | } 189 | this._skip = n; 190 | return this; 191 | } 192 | 193 | limit(n: number): RxParseQuery { 194 | if (typeof n !== 'number') { 195 | throw new Error('You can only set the limit to a numeric value'); 196 | } 197 | this._limit = n; 198 | return this; 199 | } 200 | 201 | include(...keys: Array): RxParseQuery { 202 | keys.forEach((key) => { 203 | if (Array.isArray(key)) { 204 | this._include = this._include.concat(key); 205 | } else { 206 | this._include.push(key); 207 | } 208 | }); 209 | return this; 210 | } 211 | 212 | select(...keys: Array): RxParseQuery { 213 | if (!this._select) { 214 | this._select = []; 215 | } 216 | keys.forEach((key) => { 217 | if (Array.isArray(key)) { 218 | this._select = this._select.concat(key); 219 | } else { 220 | this._select.push(key); 221 | } 222 | }); 223 | return this; 224 | } 225 | 226 | public find(): Observable> { 227 | return RxParseUser.currentSessionToken().pipe(flatMap(sessionToken => { 228 | return RxParseQuery._queryController.find(this, sessionToken).pipe(map(serverStates => { 229 | let resultList = serverStates.map((serverState, i, a) => { 230 | return RxParseObject.instantiateSubclass(this.className, serverState); 231 | }); 232 | if (resultList == undefined || resultList == null) { 233 | resultList = []; 234 | } 235 | return resultList; 236 | })); 237 | })); 238 | } 239 | 240 | 241 | public seek(): Observable { 242 | return RxParseUser.currentSessionToken().pipe(flatMap(sessionToken => { 243 | return RxParseQuery._queryController.find(this, sessionToken).pipe(flatMap(serverStates => { 244 | let resultList = serverStates.map((serverState, i, a) => { 245 | let rxObject = new RxParseObject(this.className); 246 | rxObject.handleFetchResult(serverState); 247 | return rxObject; 248 | }); 249 | if (resultList == undefined || resultList == null) { 250 | resultList = []; 251 | } 252 | return from(resultList); 253 | })); 254 | })); 255 | } 256 | 257 | public static or(...queries: Array): RxParseQuery { 258 | let className = null; 259 | let app: ParseApp; 260 | queries.forEach((q) => { 261 | if (!className) { 262 | className = q.className; 263 | app = q.app; 264 | } 265 | 266 | if (className !== q.className) { 267 | throw new Error('All queries must be for the same class.'); 268 | } 269 | }); 270 | 271 | let query = new RxParseQuery(className, { 272 | app: app 273 | }); 274 | query._orQuery(queries); 275 | return query; 276 | } 277 | 278 | protected _orQuery(queries: Array): RxParseQuery { 279 | let queryJSON = queries.map((q) => { 280 | return q._where; 281 | }); 282 | 283 | this._where.$or = queryJSON; 284 | 285 | return this; 286 | } 287 | 288 | protected _addCondition(key: string, condition: string, value: any): RxParseQuery { 289 | if (!this._where[key] || typeof this._where[key] === 'string') { 290 | this._where[key] = {}; 291 | } 292 | this._where[key][condition] = this._encode(value, false, true); 293 | return this; 294 | } 295 | 296 | protected _encode(value: any, disallowObjects: boolean, forcePointers: boolean) { 297 | return RxParseQuery._encoder.encode(value); 298 | } 299 | 300 | 301 | buildParameters(includeClassName: boolean = false) { 302 | let result: { [key: string]: any } = {}; 303 | if (Object.keys(this._where).length > 0) { 304 | result['where'] = JSON.stringify(this._where); 305 | } 306 | if (this._order) { 307 | result["order"] = this._order.join(","); 308 | } 309 | if (this._limit > 0) { 310 | result["limit"] = this._limit; 311 | } 312 | if (this._skip > 0) { 313 | result["skip"] = this._skip; 314 | } 315 | if (this._include.length) { 316 | result['include'] = this._include.join(','); 317 | } 318 | if (this._select) { 319 | result['keys'] = this._select.join(','); 320 | } 321 | return result; 322 | } 323 | public get where() { 324 | return this._where; 325 | } 326 | public get selectedKeys() { 327 | return this._select; 328 | } 329 | protected createSubscription(query: RxParseQuery, sessionToken: string): Observable { 330 | let rtn: RxParseLiveQuery = null; 331 | return RxParseLiveQuery.getCurrent({ app: query.app }).pipe(flatMap(cacheLiveQuery => { 332 | let subscriptionId = ''; 333 | let queryId = ''; 334 | 335 | if (cacheLiveQuery != null) { 336 | subscriptionId = cacheLiveQuery.id; 337 | queryId = cacheLiveQuery.queryId; 338 | } 339 | 340 | let state = RxParseLiveQuery.getState({ app: query.app }); 341 | if (state != null) { 342 | subscriptionId = state.id; 343 | } 344 | 345 | return ParseClient.runCommand(`/LiveQuery/subscribe`, 'POST', { 346 | query: { 347 | where: query.where, 348 | className: query.className, 349 | keys: query.selectedKeys, 350 | queryId: queryId.length > 0 ? queryId : null 351 | }, 352 | sessionToken: sessionToken, 353 | id: subscriptionId.length > 0 ? subscriptionId : null 354 | }, sessionToken, this.app).pipe(map(res => { 355 | queryId = res.query_id; 356 | 357 | rtn = RxParseLiveQuery.getMemory({ app: query.app, queryId: queryId }); 358 | 359 | if (rtn == null) { 360 | subscriptionId = subscriptionId.length > 0 ? subscriptionId : res.id; 361 | rtn = new RxParseLiveQuery({ id: subscriptionId, queryId: queryId, query: query }); 362 | rtn.saveCurrent(); 363 | } 364 | return rtn; 365 | })); 366 | })); 367 | } 368 | } 369 | 370 | export class RxParseLiveQuery implements ICanSaved { 371 | 372 | constructor(options?: any) { 373 | if (options) { 374 | if (options.id) { 375 | this.id = options.id; 376 | } 377 | if (options.queryId) { 378 | this.queryId = options.queryId; 379 | } 380 | if (options.query) { 381 | this.query = options.query; 382 | } 383 | } 384 | } 385 | _websocketController: IRxWebSocketController; 386 | get rxWebSocketController() { 387 | return this._websocketController; 388 | } 389 | set rxWebSocketController(c: IRxWebSocketController) { 390 | this._websocketController = c; 391 | } 392 | static readonly LiveQuerySubscriptionCacheKey = 'LiveQuerySubscriptionCacheKey'; 393 | private static _currentSubscriptions: Map = new Map(); 394 | 395 | static getMemory(options?: any) { 396 | let rtn: RxParseLiveQuery = null; 397 | let app = ParseClient.instance.take(options); 398 | let queryId = options.queryId; 399 | let key = `${app.appId}_${queryId}`; 400 | if (RxParseLiveQuery._currentSubscriptions.has(key) && queryId) { 401 | rtn = RxParseLiveQuery._currentSubscriptions.get(key); 402 | } 403 | return rtn; 404 | } 405 | 406 | static getState(options?: any) { 407 | let state: RxParseLiveQuery = null; 408 | let app = ParseClient.instance.take(options); 409 | RxParseLiveQuery._currentSubscriptions.forEach((v, k, m) => { 410 | if (k.startsWith(app.appId)) { 411 | state = v; 412 | } 413 | }); 414 | return state; 415 | } 416 | 417 | static getCurrent(options?: any): Observable { 418 | let rtn: RxParseLiveQuery = null; 419 | let app = ParseClient.instance.take(options); 420 | if (SDKPlugins.instance.hasStorage) { 421 | return SDKPlugins.instance.LocalStorageControllerInstance.get(`${app.appId}_${RxParseLiveQuery.LiveQuerySubscriptionCacheKey}`).pipe(map(cache => { 422 | if (cache) { 423 | rtn = new RxParseLiveQuery(cache); 424 | } 425 | return rtn; 426 | })); 427 | } 428 | return from([rtn]); 429 | } 430 | saveCurrent() { 431 | let app = this.query.app; 432 | let key = `${app.appId}_${this.queryId}`; 433 | RxParseLiveQuery._currentSubscriptions.set(key, this); 434 | return RxParseObject.saveToLocalStorage(this as ICanSaved, `${app.appId}_${RxParseLiveQuery.LiveQuerySubscriptionCacheKey}`); 435 | } 436 | toJSONObjectForSaving(): { 437 | [key: string]: any; 438 | } { 439 | let data = { 440 | id: this.id, 441 | queryId: this.queryId 442 | }; 443 | return data; 444 | // throw new Error("Method not implemented."); 445 | } 446 | 447 | bind() { 448 | this.on = this.rxWebSocketController.onMessage.pipe(filter(message => { 449 | let data = JSON.parse(message); 450 | if (Object.prototype.hasOwnProperty.call(data, 'cmd')) { 451 | let rtn = false; 452 | if (data.cmd == 'data') { 453 | let ids = data.ids; 454 | let msg: Array<{ object: any, op: string, query_id: string }> = data.msg; 455 | msg.filter(item => { 456 | return item.query_id == this.queryId; 457 | }).forEach(item => { 458 | rtn = true; 459 | }); 460 | this.sendAck(ids); 461 | } 462 | return rtn; 463 | } 464 | }), flatMap(message => { 465 | let data = JSON.parse(message); 466 | console.log('liveQuery<=', data); 467 | let ids = data.ids; 468 | let msg: Array<{ object: any, op: string, updatedKeys: string[], query_id: string }> = data.msg; 469 | let obsArray: Array<{ scope: string, keys: string[], object: RxParseObject }> = []; 470 | msg.filter(item => { 471 | return item.query_id == this.queryId; 472 | }).forEach(item => { 473 | 474 | let objectJson = {}; 475 | if (typeof item.object == 'string') { 476 | objectJson = JSON.parse(item.object); 477 | } else if (typeof item.object == 'object') { 478 | objectJson = item.object; 479 | } 480 | 481 | let objectState = SDKPlugins.instance.ObjectDecoder.decode(objectJson, SDKPlugins.instance.Decoder); 482 | let rxObject = new RxParseObject(this.query.className); 483 | rxObject.handleFetchResult(objectState); 484 | obsArray.push({ scope: item.op, keys: item.updatedKeys, object: rxObject }); 485 | }); 486 | return from(obsArray); 487 | })); 488 | 489 | } 490 | 491 | id: string; 492 | queryId: string; 493 | on: Observable<{ scope: string, keys: Array, object: RxParseObject }>; 494 | query: RxParseQuery; 495 | 496 | sendAck(ids?: Array) { 497 | let ackCmd = new ParseCommand() 498 | .attribute('appId', this.query.app.appId) 499 | .attribute('cmd', 'ack') 500 | .attribute('installationId', this.id) 501 | .attribute('service', 1); 502 | 503 | if (ids) { 504 | ackCmd = ackCmd.attribute('ids', ids); 505 | } 506 | this.rxWebSocketController.execute(ackCmd); 507 | } 508 | } 509 | 510 | export class RxParseQueryIterator { 511 | 512 | } -------------------------------------------------------------------------------- /src/public/RxParseRole.ts: -------------------------------------------------------------------------------- 1 | import { ParseClient, RxParseObject, RxParseACL, RxParseUser, RxParseQuery } from '../RxParse'; 2 | import { flatMap, map, filter } from 'rxjs/operators'; 3 | import { Observable, from } from 'rxjs'; 4 | 5 | export class RxParseRole extends RxParseObject { 6 | 7 | constructor(name?: string, acl?: RxParseACL, users?: Array, roles?: Array) { 8 | super('_Role'); 9 | let idChecker = (element, index, array) => { 10 | return element.objectId != null; 11 | }; 12 | if (users) { 13 | if (users.every(idChecker)) 14 | this.users = users; 15 | else throw new Error('some users in args(users) has no objectId.') 16 | } 17 | 18 | if (roles) { 19 | if (roles.every(idChecker)) 20 | this.roles = roles; 21 | else throw new Error('some roles in args(roles) has no objectId.') 22 | } 23 | if (name) 24 | this.name = name; 25 | if (acl) { 26 | this.ACL = acl; 27 | } 28 | else { 29 | 30 | } 31 | } 32 | 33 | private _name: string; 34 | get name() { 35 | if (!this._name) { 36 | this._name = this.get('name'); 37 | } 38 | return this._name; 39 | } 40 | set name(name: string) { 41 | this._name = name; 42 | this.set('name', name); 43 | } 44 | 45 | protected users: Array; 46 | protected roles: Array; 47 | 48 | 49 | grant(...args: any[]): Observable { 50 | return this._postRelation('add', ...args); 51 | } 52 | 53 | deny(...args: any[]): Observable { 54 | return this._postRelation('remove', ...args); 55 | } 56 | 57 | protected _postRelation(op: string, ...args: any[]) { 58 | let body: { [key: string]: any } = {}; 59 | let users: Array = []; 60 | let roles: Array = []; 61 | args.forEach(currentItem => { 62 | if (currentItem instanceof RxParseUser) { 63 | users.push(currentItem); 64 | } else if (currentItem instanceof RxParseRole) { 65 | roles.push(currentItem); 66 | } else { 67 | throw new TypeError('args type must be RxParseRole or RxParseUser.'); 68 | } 69 | }); 70 | this._buildRoleRelation(op, users, roles, body); 71 | return RxParseUser.currentSessionToken().pipe(flatMap(sessionToken => { 72 | return RxParseUser._objectController.save(this.state, this.currentOperations, sessionToken).pipe(map(serverState => { 73 | return serverState != null; 74 | })); 75 | })); 76 | } 77 | 78 | public save() { 79 | this._buildRoleRelation('add', this.users, this.roles, this.estimatedData); 80 | if (!this.ACL) throw new Error('Role must have a ACL.'); 81 | 82 | if (this.ACL.getPublicWriteAccess()) { 83 | throw new Error('can NOT set Role.ACL public write access in true.'); 84 | } 85 | 86 | if (!(this.ACL.findWriteAccess() && ParseClient.inLeanEngine)) { 87 | throw new Error('can NOT set Role.ACL write access in closed.'); 88 | } 89 | 90 | return super.save(); 91 | } 92 | 93 | protected _buildRoleRelation(op: string, users: Array, roles: Array, postBody: { [key: string]: any }) { 94 | if (users && users.length > 0) { 95 | let usersBody: { [key: string]: any } = this.buildRelation(op, users); 96 | postBody['users'] = usersBody; 97 | } 98 | if (roles && roles.length > 0) { 99 | let rolesBody: { [key: string]: any } = this.buildRelation(op, roles); 100 | postBody['roles'] = rolesBody; 101 | } 102 | } 103 | 104 | public static createWithoutData(objectId?: string) { 105 | let rtn = new RxParseRole(); 106 | if (objectId) 107 | rtn.objectId = objectId; 108 | return rtn; 109 | } 110 | 111 | public static createWithName(name: string, objectId: string) { 112 | let rtn = new RxParseRole(); 113 | rtn.name = name; 114 | rtn.objectId = objectId; 115 | return rtn; 116 | } 117 | 118 | public static getByName(roleName: string) { 119 | let query = new RxParseQuery('_Role'); 120 | query.equalTo('name', roleName); 121 | return query.find().pipe(map(roleList => { 122 | if (roleList.length > 0) { 123 | let obj = roleList[0]; 124 | let roleId = obj.objectId; 125 | let roleName = obj.get('name'); 126 | let role = RxParseRole.createWithName(roleName, roleId); 127 | return role; 128 | } 129 | return undefined; 130 | })); 131 | } 132 | 133 | } -------------------------------------------------------------------------------- /src/public/RxParseUser.ts: -------------------------------------------------------------------------------- 1 | import { SDKPlugins } from '../internal/ParseClientPlugins'; 2 | import { ParseClient, RxParseObject, RxParseRole, RxParseQuery, RxParseInstallation } from '../RxParse'; 3 | import { IObjectState } from '../internal/object/state/IObjectState'; 4 | import { IUserController } from '../internal/user/controller/IUserController'; 5 | import { flatMap, map, filter } from 'rxjs/operators'; 6 | import { Observable, from } from 'rxjs'; 7 | 8 | /** 9 | * 10 | * 11 | * @export 12 | * @class RxParseUser 13 | * @extends {RxParseObject} 14 | */ 15 | export class RxParseUser extends RxParseObject { 16 | constructor() { 17 | super('_User'); 18 | } 19 | static readonly installationKey = 'installations'; 20 | static readonly currentUserCacheKey = 'CurrentUser'; 21 | private _username: string; 22 | email: string; 23 | private _mobilePhone: string; 24 | roles: Array; 25 | 26 | static usersMap: Map = new Map(); 27 | protected static saveCurrentUser(user: RxParseUser) { 28 | 29 | RxParseUser.usersMap.set(user.state.app.appId, user); 30 | console.log('jsonToSaved', user.toJSONObjectForSaving()); 31 | return RxParseObject.saveToLocalStorage(user, `${user.state.app.appId}_${RxParseUser.currentUserCacheKey}`); 32 | } 33 | 34 | 35 | /** 36 | * 37 | * 38 | * @static 39 | * @param {*} [options] 40 | * @returns {Observable} 41 | * @memberof RxParseUser 42 | */ 43 | static current(options?: any): Observable { 44 | let rtn: RxParseUser = null; 45 | let app = ParseClient.instance.take(options); 46 | if (RxParseUser.usersMap.has(app.appId)) { 47 | rtn = RxParseUser.usersMap.get(app.appId); 48 | } else if (SDKPlugins.instance.hasStorage) { 49 | return SDKPlugins.instance.LocalStorageControllerInstance.get(`${app.appId}_${RxParseUser.currentUserCacheKey}`).pipe(map(userCache => { 50 | if (userCache) { 51 | let userState = SDKPlugins.instance.ObjectDecoder.decode(userCache, SDKPlugins.instance.Decoder); 52 | userState = userState.mutatedClone((s: IObjectState) => { }); 53 | let user = RxParseUser.createWithoutData(); 54 | user.handlerLogIn(userState); 55 | rtn = user; 56 | } 57 | return rtn; 58 | })); 59 | } 60 | return from([rtn]); 61 | } 62 | 63 | static currentSessionToken(): Observable { 64 | return RxParseUser.current().pipe(map(user => { 65 | if (user != null) 66 | return user.sessionToken as string; 67 | return null; 68 | })); 69 | } 70 | 71 | protected static get UserController(): IUserController { 72 | return SDKPlugins.instance.userController; 73 | } 74 | 75 | /** 76 | * 77 | * 78 | * @memberof RxParseUser 79 | */ 80 | set username(username: string) { 81 | if (this.sessionToken == null) { 82 | this._username = username; 83 | this.set('username', this._username); 84 | } 85 | else { 86 | throw new Error('can not reset username.'); 87 | } 88 | } 89 | 90 | /** 91 | * 92 | * 93 | * @memberof RxParseUser 94 | */ 95 | get username() { 96 | this._username = this.getProperty('username'); 97 | return this._username; 98 | } 99 | 100 | /** 101 | * 102 | * 103 | * @memberof RxParseUser 104 | */ 105 | get mobilePhone() { 106 | this._mobilePhone = this.getProperty('mobilePhoneNumber'); 107 | return this._mobilePhone; 108 | } 109 | 110 | /** 111 | * 112 | * 113 | * @memberof RxParseUser 114 | */ 115 | set mobilePhone(mobile: string) { 116 | if (this.sessionToken == null) { 117 | this._mobilePhone = mobile; 118 | this.set('mobilePhoneNumber', this._mobilePhone); 119 | } 120 | else { 121 | throw new Error('can not reset mobilePhone.'); 122 | } 123 | } 124 | 125 | /** 126 | * 127 | * 128 | * @memberof RxParseUser 129 | */ 130 | set password(password: string) { 131 | if (this.sessionToken == null) 132 | this.set('password', password); 133 | else { 134 | throw new Error('can not set password for a exist user, if you want to reset password, please call requestResetPassword.'); 135 | } 136 | } 137 | 138 | get sessionToken() { 139 | return this.getProperty('sessionToken'); 140 | } 141 | 142 | public isAuthenticated(): Observable { 143 | try { 144 | return !!this.sessionToken && ParseClient.runCommand('/users/me', 'GET', null, this.sessionToken, this.state.app).pipe(map(body => { 145 | return true; 146 | })); 147 | } catch (error) { 148 | return from([error.error.code == 211]); 149 | } 150 | } 151 | 152 | public activate(installation: RxParseInstallation, unique?: boolean): Observable { 153 | if (!installation || installation == null || !installation.objectId || installation.objectId == null) { 154 | throw new Error('installation can not be a unsaved object.') 155 | } 156 | let ch: Observable; 157 | if (unique) { 158 | this.unset(RxParseUser.installationKey); 159 | ch = this.save(); 160 | } else { 161 | ch = from([true]); 162 | } 163 | return ch.pipe(flatMap(s1 => { 164 | let opBody = this.buildRelation('add', [installation]); 165 | this.set(RxParseUser.installationKey, opBody); 166 | return this.save(); 167 | })); 168 | } 169 | 170 | public inactive(installation: RxParseInstallation): Observable { 171 | let opBody = this.buildRelation('remove', [installation]); 172 | this.set(RxParseUser.installationKey, opBody); 173 | return this.save(); 174 | } 175 | 176 | public fetchRoles(): Observable> { 177 | let query = new RxParseQuery('_Role'); 178 | query.equalTo('users', this); 179 | return query.find().pipe(map(roles => { 180 | let fetched = roles.map(currentItem => { 181 | let role = RxParseRole.createWithName(currentItem.get('name'), currentItem.objectId); 182 | return role; 183 | }); 184 | this.roles = fetched; 185 | return fetched; 186 | })); 187 | } 188 | 189 | public signUp(): Observable { 190 | return RxParseUser.UserController.signUp(this.state, this.estimatedData).pipe(flatMap(userState => { 191 | return this.handlerSignUp(userState); 192 | })); 193 | } 194 | 195 | public static sendSignUpShortCode(mobilePhone: string): Observable { 196 | let data = { 197 | mobilePhoneNumber: mobilePhone 198 | }; 199 | return ParseClient.runCommand('/requestSmsCode', 'POST', data).pipe(map(body => { 200 | return true; 201 | })); 202 | } 203 | 204 | public static sendLogInShortCode(mobilePhone: string): Observable { 205 | let data = { 206 | mobilePhoneNumber: mobilePhone 207 | }; 208 | return ParseClient.runCommand('/requestLoginSmsCode', 'POST', data).pipe(map(body => { 209 | return true; 210 | })); 211 | } 212 | 213 | public static signUpByMobilePhone(mobilePhone: string, shortCode: string, newUser: RxParseUser): Observable { 214 | let encoded = SDKPlugins.instance.Encoder.encode(newUser.estimatedData); 215 | encoded['mobilePhoneNumber'] = mobilePhone; 216 | encoded['smsCode'] = shortCode; 217 | 218 | return RxParseUser.UserController.logInWithParameters('/usersByMobilePhone', encoded).pipe(flatMap(userState => { 219 | let user = RxParseUser.createWithoutData(); 220 | if (userState.isNew) 221 | return user.handlerSignUp(userState).pipe(map(s => { 222 | return user; 223 | })); 224 | else { 225 | return RxParseUser.processLogIn(userState); 226 | } 227 | })); 228 | } 229 | 230 | public static logInByMobilePhone(mobilePhone: string, shortCode: string): Observable { 231 | let data = { 232 | "mobilePhoneNumber": mobilePhone, 233 | "smsCode": shortCode 234 | }; 235 | return RxParseUser.UserController.logInWithParameters('/usersByMobilePhone', data).pipe(flatMap(userState => { 236 | let user = RxParseUser.createWithoutData(); 237 | if (userState.isNew) 238 | return user.handlerSignUp(userState).pipe(map(s => { 239 | return user; 240 | })); 241 | else { 242 | return RxParseUser.processLogIn(userState); 243 | } 244 | })); 245 | } 246 | 247 | public static logIn(username: string, password: string): Observable { 248 | return RxParseUser.UserController.logIn(username, password).pipe(flatMap(userState => { 249 | return RxParseUser.processLogIn(userState); 250 | })); 251 | } 252 | 253 | public logOut(): Observable { 254 | RxParseUser.usersMap.delete(this.state.app.appId); 255 | return RxParseObject.saveToLocalStorage(null, `${this.state.app.appId}_${RxParseUser.currentUserCacheKey}`); 256 | } 257 | 258 | public static logInWithMobilePhone(mobilePhone: string, password: string): Observable { 259 | let data = { 260 | "mobilePhoneNumber": mobilePhone, 261 | "password": password 262 | }; 263 | return RxParseUser.UserController.logInWithParameters('/login', data).pipe(flatMap(userState => { 264 | return RxParseUser.processLogIn(userState); 265 | })); 266 | } 267 | 268 | public create(): Observable { 269 | return RxParseUser.UserController.signUp(this.state, this.estimatedData).pipe(map(userState => { 270 | super.handlerSave(userState); 271 | this.state.serverData = userState.serverData; 272 | return true; 273 | })); 274 | } 275 | 276 | public static createWithoutData(objectId?: string) { 277 | return RxParseObject.createSubclass(RxParseUser, objectId); 278 | } 279 | 280 | protected static processLogIn(userState: IObjectState): Observable { 281 | let user = RxParseUser.createWithoutData(); 282 | return user.handlerLogIn(userState).pipe(map(s => { 283 | if (s) 284 | return user; 285 | else from([null]); 286 | })); 287 | } 288 | 289 | protected handlerLogIn(userState: IObjectState) { 290 | this.handleFetchResult(userState); 291 | return RxParseUser.saveCurrentUser(this); 292 | } 293 | 294 | protected handlerSignUp(userState: IObjectState) { 295 | this.handlerSave(userState); 296 | this.state.serverData = userState.serverData; 297 | this.estimatedData.set('sessionToken', this.sessionToken); 298 | return RxParseUser.saveCurrentUser(this); 299 | } 300 | } -------------------------------------------------------------------------------- /test/client/mutipleApp.ts: -------------------------------------------------------------------------------- 1 | import * as chai from 'chai'; 2 | import * as init from "../utils/init"; 3 | import { Observable, merge } from 'rxjs'; 4 | import { ParseClient, RxParseObject, RxParseUser, RxParseACL, RxParseRole, RxParseQuery, ParseApp } from '../../src/RxParse'; 5 | 6 | let app = new ParseApp({ 7 | appId: `parse`, 8 | serverURL: 'https://chigua.live/api', 9 | additionalHeaders: { 10 | 'X-Parse-Application-Id': `parse` 11 | } 12 | }); 13 | 14 | describe('RxObject', function () { 15 | before(() => { 16 | 17 | }); 18 | 19 | it('Client#saveToMultipleApps', done => { 20 | let todo1: RxParseObject = new RxParseObject('RxTodo'); 21 | todo1.set('app', 'app1'); 22 | 23 | let todo2: RxParseObject = new RxParseObject('RxTodo', { appName: 'dev' }); 24 | todo2.set('app', 'app2'); 25 | 26 | let todo3: RxParseObject = new RxParseObject('RxTodo', { appName: 'default' }); 27 | todo2.set('app', 'app1'); 28 | 29 | merge(todo1.save(), todo2.save(), todo3.save()).subscribe(s => { 30 | console.log('Next: ' + s); 31 | }, error => { }, () => { 32 | done(); 33 | }); 34 | }); 35 | it('Client#initWithoutConfig', done => { 36 | ParseClient.init().add(app); 37 | done(); 38 | }); 39 | }); -------------------------------------------------------------------------------- /test/cloud/cloud.ts: -------------------------------------------------------------------------------- 1 | import * as chai from 'chai'; 2 | import * as random from "../utils/random"; 3 | import * as init from "../utils/init"; 4 | import { ParseClient, RxParseCloud } from 'RxParse'; 5 | -------------------------------------------------------------------------------- /test/cloud/functionTest.ts: -------------------------------------------------------------------------------- 1 | import * as chai from 'chai'; 2 | import * as random from "../utils/random"; 3 | import * as init from "../utils/init"; 4 | import { ParseClient, RxParseCloud } from 'RxParse'; 5 | 6 | describe('RxLeanEngine', function () { 7 | before(() => { 8 | 9 | }); 10 | }); 11 | -------------------------------------------------------------------------------- /test/mocha.opts: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RxParse/Parse-SDK-ts/c1d8c10912aa58a3c0798cd3fec251cde7caf131/test/mocha.opts -------------------------------------------------------------------------------- /test/object/deleteTest.ts: -------------------------------------------------------------------------------- 1 | import * as chai from 'chai'; 2 | import { map, flatMap } from 'rxjs/operators'; 3 | import { ParseClient, RxParseObject, RxParseUser, RxParseACL, RxParseRole, RxParseQuery, ParseApp } from '../../src/RxParse'; 4 | import { init } from "../utils/init"; 5 | 6 | describe('RxObject', () => { 7 | before(() => { 8 | init(); 9 | }); 10 | it('RxAVObject#deleteOne', done => { 11 | let todo1: RxParseObject = new RxParseObject('RxTodo'); 12 | todo1.set('title', '开会'); 13 | todo1.set('time', '2016-12-03'); 14 | todo1.set('reminder', new Date()); 15 | 16 | todo1.save().pipe(flatMap(success => { 17 | return todo1.delete(); 18 | })).subscribe(deleted => { 19 | console.log(deleted); 20 | done(); 21 | }); 22 | }); 23 | it('RxAVObject#deleteAll', done => { 24 | let todo1: RxParseObject = new RxParseObject('RxTodo'); 25 | todo1.set('title', '开会'); 26 | todo1.set('time', '2016-12-03'); 27 | todo1.set('reminder', new Date()); 28 | 29 | let todo2: RxParseObject = new RxParseObject('RxTodo'); 30 | todo2.set('title', '开会'); 31 | todo2.set('time', '2016-12-03'); 32 | todo2.set('reminder', new Date()); 33 | 34 | 35 | let todo3: RxParseObject = new RxParseObject('RxTodo'); 36 | todo3.set('title', '开会'); 37 | todo3.set('time', '2016-12-03'); 38 | todo3.set('reminder', new Date()); 39 | 40 | let todo4: RxParseObject = new RxParseObject('RxTodo'); 41 | todo4.set('title', '开会'); 42 | todo4.set('time', '2016-12-03'); 43 | todo4.set('reminder', new Date()); 44 | 45 | let obja = [todo1, todo2, todo3, todo4]; 46 | 47 | RxParseObject.saveAll(obja).pipe(flatMap(success => { 48 | return RxParseObject.deleteAll(obja); 49 | })).subscribe(deleted => { 50 | console.log(deleted); 51 | }, error => { }, () => { 52 | done(); 53 | }); 54 | }); 55 | }); -------------------------------------------------------------------------------- /test/object/saveTest.ts: -------------------------------------------------------------------------------- 1 | import { map, flatMap } from 'rxjs/operators'; 2 | import * as chai from 'chai'; 3 | import { ParseClient, RxParseObject, RxParseUser, RxParseACL, RxParseRole, RxParseQuery, ParseApp } from '../../src/RxParse'; 4 | import { init } from "../utils/init"; 5 | 6 | describe('RxObject', function () { 7 | before(() => { 8 | init(); 9 | }); 10 | it('RxAVObject#saveBase', function (done) { 11 | let todo: RxParseObject = new RxParseObject('RxTodo'); 12 | 13 | todo.set('title', '开会'); 14 | todo.set('time', '2016-12-03'); 15 | todo.set('reminder', new Date()); 16 | todo.set('open', false); 17 | console.log('todo', JSON.stringify(todo)); 18 | todo.save().subscribe(() => { 19 | console.log('todo.title', todo.get('title')); 20 | console.log('todo', JSON.stringify(todo)); 21 | done(); 22 | }, error => { 23 | /** error 的格式如下: 24 | * {statusCode: -1,error: { code: 0, error: 'Server error' }} 25 | * statusCode:是本次 http 请求的应答的响应码,LeanCloud 云端会返回标准的 Http Status,一般错误可以从这里查找原因 26 | * 而具体的逻辑错误可以从 error: { code: 0, error: 'Server error' } 这里来查找,这部分错误在 LeanCloud 官方文档的错误码对照表有详细介绍 27 | */ 28 | chai.assert.isNull(error); 29 | if (error.error.code == 1) { 30 | console.log('1.这个错误是因为 http 请求的 url 拼写有误,一般情况下可能是 class name 不符合规范,请确认'); 31 | console.log('2.还有可能是您错误的使用跨节点的 AppId 调用 API,例如您可能正在使用北美节点上的 AppId 访问大陆的节点,这一点请仔细阅读官方文档'); 32 | } 33 | }); 34 | }); 35 | 36 | it('RxAVObject#saveAll', done => { 37 | let todo1: RxParseObject = new RxParseObject('RxTodo'); 38 | todo1.set('title', '开会'); 39 | todo1.set('time', '2016-12-03'); 40 | todo1.set('reminder', new Date()); 41 | 42 | let todo2: RxParseObject = new RxParseObject('RxTodo'); 43 | todo2.set('title', '开会'); 44 | todo2.set('time', '2016-12-03'); 45 | todo2.set('reminder', new Date()); 46 | 47 | 48 | let todo3: RxParseObject = new RxParseObject('RxTodo'); 49 | todo3.set('title', '开会'); 50 | todo3.set('time', '2016-12-03'); 51 | todo3.set('reminder', new Date()); 52 | 53 | let todo4: RxParseObject = new RxParseObject('RxTodo'); 54 | todo4.set('title', '开会'); 55 | todo4.set('time', '2016-12-03'); 56 | todo4.set('reminder', new Date()); 57 | 58 | let obja = [todo1, todo2, todo3, todo4]; 59 | 60 | RxParseObject.saveAll(obja).subscribe(next => { 61 | console.log('1'); 62 | }, error => { 63 | console.log(error); 64 | }, () => { 65 | done(); 66 | console.log('all have been saved.'); 67 | }); 68 | }); 69 | 70 | it('RxAVObject#savePointer', done => { 71 | let todo1: RxParseObject = new RxParseObject('RxTodo'); 72 | todo1.set('title', 'father'); 73 | todo1.set('time', '2016-12-07'); 74 | todo1.set('likes', 9); 75 | 76 | let todo2: RxParseObject = new RxParseObject('RxTodo'); 77 | todo2.set('title', 'son'); 78 | 79 | todo1.set('xx', todo2); 80 | 81 | let todo3: RxParseObject = new RxParseObject('RxTodo'); 82 | todo3.set('title', 'grandson'); 83 | 84 | todo1.set('yy', todo3); 85 | 86 | todo1.save().subscribe(s => { 87 | console.log(todo1.objectId); 88 | console.log(todo2.objectId); 89 | done(); 90 | }, error => { 91 | console.log(error); 92 | }); 93 | }); 94 | 95 | it('RxAVObject#saveUnderACL', done => { 96 | RxParseUser.logIn('junwu', 'leancloud').subscribe(user => { 97 | let team: RxParseObject = new RxParseObject('teams'); 98 | 99 | let teamPrefix = 'hua'; 100 | let admin = `${teamPrefix}_admin`; 101 | 102 | let acl = new RxParseACL(); 103 | acl.setRoleWriteAccess(admin, true); 104 | acl.setReadAccess(admin, true); 105 | acl.setPublicWriteAccess(false); 106 | acl.setPublicReadAccess(false); 107 | 108 | team.set('name', teamPrefix); 109 | team.set('domain', teamPrefix); 110 | 111 | team.save().subscribe(() => { 112 | done(); 113 | }); 114 | }); 115 | }); 116 | 117 | it('RxAVObject#collectChildrenTwoHierarchies', done => { 118 | let todo: RxParseObject = new RxParseObject('RxTodo'); 119 | todo.set('title', 'todo'); 120 | 121 | let todo2: RxParseObject = new RxParseObject('RxTodo'); 122 | todo2.set('title', 'todo2'); 123 | 124 | let todo3: RxParseObject = new RxParseObject('RxTodo'); 125 | todo3.set('title', 'todo3'); 126 | 127 | let todo4: RxParseObject = new RxParseObject('RxTodo'); 128 | todo4.set('title', 'todo4'); 129 | 130 | 131 | let todo5: RxParseObject = new RxParseObject('RxTodo'); 132 | todo5.set('title', 'todo5'); 133 | 134 | todo4.set('t', todo5); 135 | //todo5.set('t', todo4); 136 | 137 | todo.set('t2', todo2); 138 | todo.set('t3', todo3); 139 | todo.set('t4', todo4); 140 | 141 | // let x = todo.collectAllLeafNodes(); 142 | // console.log('leafNodes', x); 143 | // let warehouse: Array = []; 144 | // let s: Array = []; 145 | // let t: Array = []; 146 | // RxAVObject.recursionCollectDirtyChildren(todo, warehouse, s, t); 147 | // console.log('warehouse', warehouse); 148 | // console.log('s', s); 149 | // console.log('t', t); 150 | // done(); 151 | 152 | todo.save().subscribe(s => { 153 | console.log(s); 154 | done(); 155 | }); 156 | }); 157 | 158 | it('RxAVObject#saveDate', done => { 159 | let testTodo = new RxParseObject('Todo'); 160 | testTodo.set('rDate', new Date()); 161 | testTodo.save().pipe(flatMap(s => { 162 | let query = new RxParseQuery('Todo'); 163 | query.equalTo('objectId', testTodo.objectId); 164 | return query.find(); 165 | }), map(todos => { 166 | let updatedAt = todos[0].updatedAt; 167 | let testDate = todos[0].get('rDate'); 168 | console.log('testDate', testDate); 169 | console.log(typeof testDate); 170 | console.log('ed', todos[0].estimatedData); 171 | //chai.assert.isTrue(testDate instanceof Date); 172 | //chai.assert.isTrue(updatedAt instanceof Date); 173 | //chai.assert.isTrue(testTodo.updatedAt instanceof Date); 174 | return todos[0]; 175 | }), flatMap(s1 => { 176 | return s1.save(); 177 | })).subscribe(s2 => { 178 | chai.assert.isTrue(s2); 179 | done(); 180 | }); 181 | }); 182 | 183 | // it('RxAVObject#add1', done => { 184 | // let testTodo = new RxAVObject('Todo'); 185 | // testTodo.add('testArray', 1); 186 | // testTodo.save().flatMap(saved => { 187 | // testTodo.add('testArray', 2); 188 | // return testTodo.save(); 189 | // }).subscribe(saved2 => { 190 | // console.log(testTodo.objectId); 191 | // console.log(testTodo.get('testArray')); 192 | // done(); 193 | // }); 194 | // }); 195 | 196 | // it('RxAVObject#addUnique', done => { 197 | // let testTodo = new RxAVObject('Todo'); 198 | // testTodo.add('testArray', 1); 199 | // testTodo.save().flatMap(saved => { 200 | // testTodo.addUnique('testArray', 1); 201 | // return testTodo.save(); 202 | // }).subscribe(saved2 => { 203 | // console.log(testTodo.objectId); 204 | // console.log(testTodo.get('testArray')); 205 | // done(); 206 | // }); 207 | // }); 208 | it('RxAVObject#boolean', done => { 209 | let testTodo = new RxParseObject('Todo'); 210 | testTodo.set('testBoolean', false); 211 | testTodo.save().subscribe(saved2 => { 212 | console.log(testTodo.objectId); 213 | console.log(testTodo.get('testBoolean')); 214 | done(); 215 | }); 216 | }); 217 | it('RxAVObject#fetch', done => { 218 | let todo = RxParseObject.createWithoutData("RxTodo", "59fc0fd52f301e0069c76a67"); 219 | todo.fetch().subscribe(obj => { 220 | console.log('todo.title', todo.get('title')) 221 | }); 222 | }); 223 | 224 | }); 225 | -------------------------------------------------------------------------------- /test/object/updateTest.ts: -------------------------------------------------------------------------------- 1 | import * as chai from 'chai'; 2 | import { ParseClient, RxParseObject, RxParseUser, RxParseACL, RxParseRole, RxParseQuery, ParseApp } from '../../src/RxParse'; 3 | import { init } from "../utils/init"; 4 | import { map, flatMap } from 'rxjs/operators'; 5 | 6 | describe('RxObject', () => { 7 | before(() => { 8 | init(); 9 | }); 10 | // it('RxAVObject#update', done => { 11 | 12 | // let testObj = RxParseObject.createWithoutData('Todo', '592d3de90ce463006b430f49'); 13 | // testObj.set('content', 'testContent'); 14 | // testObj.save().subscribe(updated => { 15 | // console.log(testObj.objectId); 16 | // done(); 17 | // }); 18 | // }); 19 | // it('RxAVObject#fetch', done => { 20 | // let testObj = RxParseObject.createWithoutData('Todo', '592d3de90ce463006b430f49'); 21 | // testObj.fetch().pipe(flatMap(obj => { 22 | // testObj.set('content', 'testContent'); 23 | // return testObj.save(); 24 | // })).subscribe(updated => { 25 | // console.log(testObj.objectId); 26 | // done(); 27 | // }); 28 | // }); 29 | 30 | }); 31 | -------------------------------------------------------------------------------- /test/push/installationTest.ts: -------------------------------------------------------------------------------- 1 | import * as chai from 'chai'; 2 | import * as init from "../utils/init"; 3 | import * as random from "../utils/random"; 4 | import { ParseClient, RxParseObject, RxParseUser, RxParseACL, RxParseRole, RxParseQuery, RxParseInstallation } from '../../src/RxParse'; 5 | init.init(); 6 | import { map, flatMap } from 'rxjs/operators'; 7 | 8 | describe('RxAVInstallation', function () { 9 | before(() => { 10 | 11 | }); 12 | it('RxAVInstallation#userActivate', done => { 13 | let installation = new RxParseInstallation(); 14 | installation.deviceType = 'ios'; 15 | installation.deviceToken = random.newToken(); 16 | installation.channels = ['public', 'fuck']; 17 | installation.save().pipe(flatMap(insSaved => { 18 | return RxParseUser.logIn('junwu', 'leancloud'); 19 | }), flatMap(loggedIn => { 20 | console.log('loggedIn'); 21 | return loggedIn.activate(installation, true); 22 | })).subscribe(bound => { 23 | done(); 24 | }, error => { 25 | console.log(error); 26 | }); 27 | }); 28 | }); -------------------------------------------------------------------------------- /test/push/pushTest.ts: -------------------------------------------------------------------------------- 1 | import * as chai from 'chai'; 2 | import * as init from "../utils/init"; 3 | import { ParseClient, RxParseObject, RxParseUser, RxParseACL, RxParseRole, RxParseQuery, RxParseInstallation, RxParsePush } from 'RxParse'; 4 | init.init(); 5 | 6 | describe('RxAVPush', function () { 7 | before(() => { 8 | }); 9 | }); -------------------------------------------------------------------------------- /test/query/arrayTest.ts: -------------------------------------------------------------------------------- 1 | import * as chai from 'chai'; 2 | import { ParseClient, RxParseObject, RxParseUser, RxParseACL, RxParseRole, RxParseQuery, ParseApp } from '../../src/RxParse'; 3 | import * as init from "../utils/init"; 4 | import { map, flatMap } from 'rxjs/operators'; 5 | 6 | init.init(); 7 | describe('RxObject', function () { 8 | before(() => { 9 | 10 | }); 11 | 12 | it('RxAVQuery#decodeArray', done => { 13 | let todo = new RxParseObject('Todo'); 14 | // todo.add('testDates', new Date()); 15 | // todo.add('testDates', new Date()); 16 | 17 | todo.save().pipe(flatMap(saved => { 18 | let query = new RxParseQuery('Todo'); 19 | query.equalTo('objectId', todo.objectId); 20 | return query.find(); 21 | })).subscribe(list => { 22 | chai.assert.isArray(list); 23 | let queriedTodo = list[0]; 24 | let testDates = queriedTodo.get('testDates') as Array; 25 | let dateValue1 = testDates[0]; 26 | console.log('dateValue1', dateValue1); 27 | chai.assert.isTrue(dateValue1 instanceof Date); 28 | done(); 29 | }); 30 | }); 31 | 32 | it('RxAVQuery#addRange_number', done => { 33 | let todo = new RxParseObject('Todo'); 34 | // todo.add('testNumbers', 1); 35 | // todo.add('testNumbers', 1); 36 | 37 | todo.save().pipe(flatMap(saved => { 38 | let query = new RxParseQuery('Todo'); 39 | query.equalTo('objectId', todo.objectId); 40 | return query.find(); 41 | })).subscribe(list => { 42 | chai.assert.isArray(list); 43 | let queriedTodo = list[0]; 44 | let testNumbers = queriedTodo.get('testNumbers') as Array; 45 | console.log('testNumbers', testNumbers); 46 | let numberValue1 = testNumbers[0]; 47 | console.log('numberValue1', numberValue1); 48 | chai.assert.isTrue(typeof numberValue1 == 'number'); 49 | done(); 50 | }); 51 | }); 52 | 53 | }); 54 | -------------------------------------------------------------------------------- /test/query/include.ts: -------------------------------------------------------------------------------- 1 | import * as chai from 'chai'; 2 | import { ParseClient, RxParseObject, RxParseUser, RxParseACL, RxParseRole, RxParseQuery, ParseApp } from '../../src/RxParse'; 3 | 4 | // describe('RxAVQuery', () => { 5 | // before(() => { 6 | // RxAVClient.init({ 7 | // appId: '6j2LjkhAnnDTeefTLFQTFJXx-gzGzoHsz', 8 | // appKey: 'mrChsHGwIAytLHopODLpqiHo', 9 | // region: 'cn', 10 | // log: true, 11 | // pluginVersion: 2 12 | // }); 13 | // }); 14 | // it('RxAVQuery#include', done => { 15 | // let query = new RxAVQuery('Baby_User'); 16 | // query.include('baby'); 17 | 18 | // query.find().subscribe(serverBabies => { 19 | // serverBabies.map(sBaby => { 20 | // let baby: RxAVObject = sBaby.get('baby'); 21 | // console.log('baby', baby); 22 | // console.log('baby.name', baby.get('name')); 23 | // }); 24 | // done(); 25 | // }); 26 | // }); 27 | // }); -------------------------------------------------------------------------------- /test/query/orTest.ts: -------------------------------------------------------------------------------- 1 | import * as chai from 'chai'; 2 | import { ParseClient, RxParseObject, RxParseUser, RxParseACL, RxParseRole, RxParseQuery, ParseApp } from '../../src/RxParse'; 3 | import * as init from "../utils/init"; 4 | 5 | describe('RxAVQuery', () => { 6 | before(() => { 7 | }); 8 | it('RxAVQuery#or', done => { 9 | let dateQuery = new RxParseQuery('RxTodo'); 10 | dateQuery.equalTo('open', false); 11 | 12 | let statusQuery = new RxParseQuery('RxTodo'); 13 | statusQuery.equalTo('time', '2016-12-07'); 14 | 15 | let mixQuery = RxParseQuery.or(dateQuery, statusQuery); 16 | 17 | mixQuery.find().subscribe(list => { 18 | console.log(list); 19 | chai.assert.isTrue(list.length > 0); 20 | done(); 21 | }); 22 | }); 23 | }); -------------------------------------------------------------------------------- /test/query/whereTest.ts: -------------------------------------------------------------------------------- 1 | import * as chai from 'chai'; 2 | import { ParseClient, RxParseObject, RxParseUser, RxParseACL, RxParseRole, RxParseQuery, ParseApp } from '../../src/RxParse'; 3 | import * as init from "../utils/init"; 4 | import { map, flatMap } from 'rxjs/operators'; 5 | 6 | describe('RxAVQuery', () => { 7 | before(() => { 8 | }); 9 | it('RxAVQuery#where', done => { 10 | let query = new RxParseQuery('RxTodo'); 11 | 12 | query.equalTo('title', '开会'); 13 | query.notEqualTo('time', '1'); 14 | 15 | query.find().subscribe(list => { 16 | done(); 17 | }, error => { 18 | console.log(error); 19 | done(); 20 | }, () => { }); 21 | }); 22 | it('RxAVQuery#WithoutResult', done => { 23 | let query = new RxParseQuery('RxTodo'); 24 | 25 | query.equalTo('title', 'fatherXXX'); 26 | 27 | query.find().subscribe(list => { 28 | console.log(list); 29 | chai.assert.isTrue(list.length == 0); 30 | done(); 31 | }, error => { 32 | console.log(error); 33 | done(); 34 | }, () => { }); 35 | }); 36 | it('RxAVQuery#seek', done => { 37 | let uiList: Array<{ id: string, title: string }> = []; 38 | let query = new RxParseQuery('RxTodo'); 39 | 40 | query.equalTo('title', '开会'); 41 | 42 | query.seek().pipe(map(obj => { 43 | return { 44 | id: obj.objectId, 45 | title: obj.get('title') 46 | } 47 | })).subscribe(tuple => { 48 | uiList.push(tuple); 49 | console.log('tuple', tuple); 50 | chai.assert.isTrue(tuple != null); 51 | }, error => { }, () => { 52 | done(); 53 | }); 54 | }); 55 | }); -------------------------------------------------------------------------------- /test/role/roleTest.ts: -------------------------------------------------------------------------------- 1 | import * as chai from 'chai'; 2 | import { ParseClient, RxParseObject, RxParseQuery, RxParseRole, RxParseUser, RxParseACL } from '../../src/RxParse'; 3 | import * as random from "../utils/random"; 4 | import * as init from "../utils/init"; 5 | import { map, flatMap } from 'rxjs/operators'; 6 | 7 | describe('RxAVRole', () => { 8 | before(() => { 9 | }); 10 | it('RxAVRole#assign', done => { 11 | done(); 12 | // let admin = RxAVRole.createWithoutData('5858169eac502e00670193bc'); 13 | // admin.assign('58522f7e1b69e6006c7e1bd5', '58520289128fe1006d981b42').subscribe(success => { 14 | // chai.assert.isTrue(success); 15 | // done(); 16 | // }); 17 | }); 18 | it('RxAVRole#init', done => { 19 | done(); 20 | // let teamName = 'hua'; 21 | // let admin = new RxAVRole(`${teamName}_admin`); 22 | // let customerManager = new RxAVRole(`${teamName}_customerManager`); 23 | // customerManager.save().subscribe(s => { 24 | // console.log(customerManager.objectId); 25 | // done(); 26 | // }); 27 | // o = o.merge(admin.save()); 28 | // o.last().subscribe(s => { 29 | 30 | // }); 31 | // //o = o.merge(customerManager.grant(admin)); 32 | // o.subscribe(s => { 33 | // console.log(s); 34 | // console.log(admin.objectId); 35 | // console.log(customerManager.objectId); 36 | // }, error => { }, () => { 37 | // customerManager.grant(admin).subscribe(s => { 38 | // done(); 39 | // }); 40 | // }); 41 | // let reception = new RxAVRole(`${teamName}_reception`); 42 | // let roomManager = new RxAVRole(`${teamName}_roomManager`); 43 | // let casher = new RxAVRole(`${teamName}_casher`); 44 | }); 45 | it('RxAVRole#createWithPublicACL', done => { 46 | RxParseUser.logIn('junwu', 'leancloud').pipe(flatMap(user => { 47 | let randomRoleName = random.randomHexString(8); 48 | let testRole = new RxParseRole(randomRoleName, new RxParseACL(user)); 49 | return testRole.save(); 50 | })).subscribe(s => { 51 | chai.assert.isTrue(s); 52 | done(); 53 | }); 54 | }); 55 | }); -------------------------------------------------------------------------------- /test/rx/timer.ts: -------------------------------------------------------------------------------- 1 | import * as chai from 'chai'; 2 | import * as init from "../utils/init"; 3 | import { Observable, timer } from 'rxjs'; 4 | 5 | describe('rxjs', () => { 6 | it('timer', done => { 7 | let count = 0; 8 | let source = timer(1000, 2000); 9 | source.subscribe(() => { 10 | console.log(count); 11 | if (count == 10) { 12 | done(); 13 | } 14 | count++; 15 | }); 16 | }); 17 | }); -------------------------------------------------------------------------------- /test/user/logInTest.ts: -------------------------------------------------------------------------------- 1 | import * as chai from 'chai'; 2 | import * as init from "../utils/init"; 3 | import { RxParseUser } from '../../src/RxParse'; 4 | import { Observable } from 'rxjs'; 5 | import { flatMap } from 'rxjs/operators'; 6 | 7 | describe('RxAVUser', function () { 8 | before(() => { 9 | }); 10 | it('RxAVUser#currentSession', function (done) { 11 | RxParseUser.logIn('junwu', 'leancloud').pipe(flatMap(user => { 12 | console.log(RxParseUser.usersMap); 13 | return RxParseUser.currentSessionToken(); 14 | })).subscribe(sessionToken => { 15 | console.log('sessionToken', sessionToken); 16 | done(); 17 | }); 18 | }); 19 | it('RxAVUser#logIn', function (done) { 20 | RxParseUser.logIn('junwu', 'leancloud').subscribe(user => { 21 | console.log(user.username); 22 | console.log(user.sessionToken); 23 | console.log(user.state); 24 | chai.assert.isNotNull(user.username); 25 | user.isAuthenticated().subscribe(s => { 26 | console.log('user.isAuthenticated()', s); 27 | chai.assert.isTrue(s); 28 | done(); 29 | }); 30 | }, error => { 31 | /** error 的格式如下: 32 | * {statusCode: -1,error: { code: 0, error: 'Server error' }} 33 | * statusCode:是本次 http 请求的应答的响应码,LeanCloud 云端会返回标准的 Http Status,一般错误可以从这里查找原因 34 | * 而具体的逻辑错误可以从 error: { code: 0, error: 'Server error' } 这里来查找,这部分错误在 LeanCloud 官方文档的错误码对照表有详细介绍 35 | */ 36 | chai.assert.isNull(error); 37 | if (error.error.code == 211) { 38 | console.log('这个错误表示用户名不存在'); 39 | } 40 | if (error.error.code == 210) { 41 | console.log('这个错误表示密码错误'); 42 | } 43 | }); 44 | }); 45 | 46 | it('RxAVUser#logIn->currentUser', function (done) { 47 | RxParseUser.logIn('junwu', 'leancloud').pipe(flatMap(user => { 48 | console.log(user.username); 49 | console.log(user.state); 50 | chai.assert.isNotNull(user.username); 51 | return user.isAuthenticated().pipe(flatMap(s => { 52 | user.set('title', 'xman'); 53 | return user.save(); 54 | })); 55 | })).subscribe(s1 => { 56 | done(); 57 | }); 58 | }); 59 | }); 60 | -------------------------------------------------------------------------------- /test/user/signUpTest.ts: -------------------------------------------------------------------------------- 1 | import * as chai from 'chai'; 2 | import * as random from "../utils/random"; 3 | import { ParseClient } from 'RxParse'; 4 | import { RxParseUser, RxParseACL, RxParseRole } from '../../src/RxParse'; 5 | import { init } from "../utils/init"; 6 | 7 | let randomUsername = ''; 8 | 9 | describe('RxAVUser', function () { 10 | before(() => { 11 | init(); 12 | randomUsername = random.randomString(8); 13 | }); 14 | it('RxAVUser#signUp-1', function (done) { 15 | let user: RxParseUser = new RxParseUser(); 16 | user.username = randomUsername; 17 | user.password = 'leancloud'; 18 | user.set('title', 'CEO'); 19 | 20 | user.signUp().subscribe(() => { 21 | console.log('sessionToken', user.sessionToken); 22 | chai.assert.isNotNull(user.sessionToken); 23 | done(); 24 | }, error => { 25 | /** error 的格式如下: 26 | * {statusCode: -1,error: { code: 0, error: 'Server error' }} 27 | * statusCode:是本次 http 请求的应答的响应码,LeanCloud 云端会返回标准的 Http Status,一般错误可以从这里查找原因 28 | * 而具体的逻辑错误可以从 error: { code: 0, error: 'Server error' } 这里来查找,这部分错误在 LeanCloud 官方文档的错误码对照表有详细介绍 29 | */ 30 | chai.assert.isNull(error); 31 | 32 | if (error.error.code == 1) { 33 | console.log('1.这个错误是因为 http 请求的 url 拼写有误,一般情况下可能是 class name 不符合规范,请确认'); 34 | console.log('2.还有可能是您错误的使用跨节点的 AppId 调用 API,例如您可能正在使用北美节点上的 AppId 访问大陆的节点,这一点请仔细阅读官方文档'); 35 | } 36 | }); 37 | }); 38 | 39 | 40 | it('RxAVUser#create', done => { 41 | let user: RxParseUser = new RxParseUser(); 42 | user.username = random.randomString(8); 43 | user.password = 'leancloud'; 44 | user.create().subscribe(s => { 45 | chai.assert.isNotNull(user.objectId); 46 | done(); 47 | }); 48 | }); 49 | }); 50 | -------------------------------------------------------------------------------- /test/utils/config.ts: -------------------------------------------------------------------------------- 1 | export const APP_ID = process.env.APP_ID || 'uay57kigwe0b6f5n0e1d4z4xhydsml3dor24bzwvzr57wdap'; 2 | export const APP_KEY = process.env.APP_KEY || 'kfgz7jjfsk55r5a8a3y4ttd3je1ko11bkibcikonk32oozww'; 3 | export const REGION = process.env.REGION || 'cn'; 4 | -------------------------------------------------------------------------------- /test/utils/init.ts: -------------------------------------------------------------------------------- 1 | import { ParseClient, ParseAppConfig, ParseApp } from '../../src/RxParse'; 2 | import { NodeJSWebSocketClient } from '../WebSocket/NodeJSWebSocketClient'; 3 | let app = new ParseApp({ 4 | appId: `parse`, 5 | serverURL: 'https://chigua.live/api/' 6 | }); 7 | 8 | import { 9 | APP_ID, 10 | APP_KEY, 11 | REGION, 12 | } from './config'; 13 | 14 | export function init() { 15 | ParseClient.init({ 16 | pluginVersion: 1, 17 | log: true, 18 | plugins: { 19 | websocket: new NodeJSWebSocketClient() 20 | } 21 | }).add(app); 22 | } 23 | 24 | -------------------------------------------------------------------------------- /test/utils/random.ts: -------------------------------------------------------------------------------- 1 | 2 | import { randomBytes, createHash } from 'crypto'; 3 | 4 | // Returns a new random hex string of the given even size. 5 | export function randomHexString(size: number): string { 6 | if (size === 0) { 7 | throw new Error('Zero-length randomHexString is useless.'); 8 | } 9 | if (size % 2 !== 0) { 10 | throw new Error('randomHexString size must be divisible by 2.') 11 | } 12 | return randomBytes(size / 2).toString('hex'); 13 | } 14 | 15 | export function randomHexStringWithPrefix(prefix: string, size: number): string { 16 | return prefix + randomHexString(size); 17 | } 18 | 19 | // Returns a new random alphanumeric string of the given size. 20 | // 21 | // Note: to simplify implementation, the result has slight modulo bias, 22 | // because chars length of 62 doesn't divide the number of all bytes 23 | // (256) evenly. Such bias is acceptable for most cases when the output 24 | // length is long enough and doesn't need to be uniform. 25 | export function randomString(size: number): string { 26 | if (size === 0) { 27 | throw new Error('Zero-length randomString is useless.'); 28 | } 29 | let chars = ('ABCDEFGHIJKLMNOPQRSTUVWXYZ' + 30 | 'abcdefghijklmnopqrstuvwxyz' + 31 | '0123456789'); 32 | let objectId = ''; 33 | let bytes = randomBytes(size); 34 | for (let i = 0; i < bytes.length; ++i) { 35 | objectId += chars[bytes.readUInt8(i) % chars.length]; 36 | } 37 | return objectId; 38 | } 39 | 40 | // Returns a new random alphanumeric string suitable for object ID. 41 | export function newObjectId(): string { 42 | //TODO: increase length to better protect against collisions. 43 | return randomString(10); 44 | } 45 | 46 | // Returns a new random hex string suitable for secure tokens. 47 | export function newToken(): string { 48 | return randomHexString(32); 49 | } 50 | 51 | export function md5Hash(string: string): string { 52 | return createHash('md5').update(string).digest('hex'); 53 | } 54 | 55 | export function newMobilePhoneNumber(): string { 56 | let prefix = ['138', '139', '188', '186', '189', '171', '170']; 57 | let chars = ('0123456789'); 58 | let mobile = prefix[Math.floor(Math.random() * prefix.length)]; 59 | let bytes = randomBytes(8); 60 | for (let i = 0; i < bytes.length; ++i) { 61 | mobile += chars[bytes.readUInt8(i) % chars.length]; 62 | } 63 | return mobile; 64 | } 65 | -------------------------------------------------------------------------------- /test/websocket/NodeJSWebSocketClient.ts: -------------------------------------------------------------------------------- 1 | //import WebSocket from 'ws'; 2 | var WebSocket = require('ws'); 3 | import { IWebSocketClient } from '../../src/RxParse'; 4 | 5 | export class NodeJSWebSocketClient implements IWebSocketClient { 6 | newInstance(): IWebSocketClient { 7 | return new NodeJSWebSocketClient(); 8 | } 9 | onopen: (event: { target: NodeJSWebSocketClient }) => void; 10 | onerror: (err: Error) => void; 11 | onclose: (event: { wasClean: boolean; code: number; reason: string; target: NodeJSWebSocketClient }) => void; 12 | onmessage: (event: { data: any; type: string; target: NodeJSWebSocketClient }) => void; 13 | readyState: number; 14 | wsc: any; 15 | open(url: string, protocols?: string | string[]) { 16 | this.wsc = new WebSocket(url, protocols); 17 | this.readyState = 0; 18 | 19 | this.wsc.onmessage = (event: { data: any; type: string; target: any }): void => { 20 | this.onmessage({ data: event.data, type: event.type, target: this }); 21 | }; 22 | this.wsc.onclose = (event: { wasClean: boolean; code: number; reason: string; target: any }): void => { 23 | this.readyState = 3; 24 | this.onclose({ wasClean: event.wasClean, code: event.code, reason: event.reason, target: this }); 25 | }; 26 | 27 | this.wsc.onerror = (err: Error): void => { 28 | this.onerror(err); 29 | }; 30 | 31 | this.wsc.onopen = (event: { target: any }): void => { 32 | this.readyState = 1; 33 | this.onopen({ target: this }); 34 | }; 35 | } 36 | 37 | close(code?: number, data?: any): void { 38 | this.readyState = 2; 39 | this.wsc.close(code, data); 40 | } 41 | send(data: ArrayBuffer | string | Blob) { 42 | this.wsc.send(data); 43 | } 44 | } -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "allowSyntheticDefaultImports": true, 4 | "declaration": true, 5 | "emitDecoratorMetadata": true, 6 | "experimentalDecorators": true, 7 | "lib": [ 8 | "dom", 9 | "es2015" 10 | ], 11 | "outDir": ".bin", 12 | // "declarationDir": "out/typings", 13 | "module": "commonjs", 14 | "moduleResolution": "node", 15 | "target": "es2017", 16 | "baseUrl": "src/", 17 | "sourceMap": true 18 | }, 19 | "include": [ 20 | "src/**/*", 21 | "test/**/*" 22 | ], 23 | "exclude": [], 24 | "compileOnSave": false, 25 | "atom": { 26 | "rewriteTsconfig": false 27 | } 28 | } -------------------------------------------------------------------------------- /typings.json: -------------------------------------------------------------------------------- 1 | { 2 | "globalDependencies": { 3 | "mocha": "registry:env/mocha#2.2.5+20160926180742" 4 | }, 5 | "devDependencies": { 6 | "chai": "registry:npm/chai#3.5.0+20160723033700" 7 | } 8 | } 9 | --------------------------------------------------------------------------------