├── .gitattributes ├── .github ├── ISSUE_TEMPLATE │ ├── ---1-report-an-issue.md │ ├── ---2-feature-request.md │ └── config.yml ├── dependabot.yml ├── parse-logo.png ├── pull_request_template.md └── workflows │ ├── ci.yml │ ├── release-automated.yml │ ├── release-manual-docs.yml │ └── release-prepare-monthly.yml ├── .gitignore ├── .madgerc ├── .npmignore ├── .nvmrc ├── .releaserc ├── commit.hbs ├── footer.hbs ├── header.hbs └── template.hbs ├── .vscode └── settings.json ├── CHANGELOG.md ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── LICENSE ├── NOTICE ├── README.md ├── babel-jest.js ├── build_releases.js ├── changelogs ├── CHANGELOG_alpha.md ├── CHANGELOG_beta.md └── CHANGELOG_release.md ├── ci └── typecheck.js ├── eslint.config.js ├── gulpfile.js ├── index.js ├── integration ├── README.md └── test │ ├── .eslintrc.json │ ├── ArrayOperationsTest.js │ ├── CustomAuth.js │ ├── DirtyTest.js │ ├── IdempotencyTest.js │ ├── IncrementTest.js │ ├── ParseACLTest.js │ ├── ParseCloudTest.js │ ├── ParseConfigTest.js │ ├── ParseDistTest.js │ ├── ParseEventuallyQueueTest.js │ ├── ParseFileTest.js │ ├── ParseGeoBoxTest.js │ ├── ParseGeoPointTest.js │ ├── ParseLegacyTest.js │ ├── ParseLiveQueryTest.js │ ├── ParseLocalDatastoreTest.js │ ├── ParseMasterKeyTest.js │ ├── ParseObjectTest.js │ ├── ParsePolygonTest.js │ ├── ParsePushTest.js │ ├── ParseQueryAggregateTest.js │ ├── ParseQueryTest.js │ ├── ParseReactNativeTest.js │ ├── ParseRelationTest.js │ ├── ParseSchemaTest.js │ ├── ParseServerTest.js │ ├── ParseSubclassTest.js │ ├── ParseUserTest.js │ ├── cloud │ └── main.js │ ├── helper.js │ ├── mockLocalStorage.js │ ├── mockRNStorage.js │ ├── sleep.js │ └── support │ └── MockEmailAdapterWithOptions.js ├── jasmine.json ├── jsdoc-conf.json ├── node.js ├── package-lock.json ├── package.json ├── react-native.js ├── release.config.js ├── release_docs.sh ├── src ├── Analytics.ts ├── AnonymousUtils.ts ├── Cloud.ts ├── CloudCode.ts ├── CoreManager.ts ├── CryptoController.ts ├── EventEmitter.ts ├── EventuallyQueue.ts ├── FacebookUtils.ts ├── IndexedDBStorageController.ts ├── InstallationController.ts ├── LiveQueryClient.ts ├── LiveQuerySubscription.ts ├── LocalDatastore.ts ├── LocalDatastoreController.default.ts ├── LocalDatastoreController.react-native.ts ├── LocalDatastoreController.ts ├── LocalDatastoreUtils.ts ├── ObjectStateMutations.ts ├── OfflineQuery.ts ├── Parse.ts ├── ParseACL.ts ├── ParseCLP.ts ├── ParseConfig.ts ├── ParseError.ts ├── ParseFile.ts ├── ParseGeoPoint.ts ├── ParseHooks.ts ├── ParseInstallation.ts ├── ParseLiveQuery.ts ├── ParseObject.ts ├── ParseOp.ts ├── ParsePolygon.ts ├── ParseQuery.ts ├── ParseRelation.ts ├── ParseRole.ts ├── ParseSchema.ts ├── ParseSession.ts ├── ParseUser.ts ├── Push.ts ├── RESTController.ts ├── SingleInstanceStateController.ts ├── Socket.weapp.ts ├── Storage.ts ├── StorageController.browser.ts ├── StorageController.default.ts ├── StorageController.react-native.ts ├── StorageController.ts ├── StorageController.weapp.ts ├── TaskQueue.ts ├── UniqueInstanceStateController.ts ├── WebSocketController.ts ├── Xhr.weapp.ts ├── __tests__ │ ├── .eslintrc.json │ ├── Analytics-test.js │ ├── AnonymousUtils-test.js │ ├── Cloud-test.js │ ├── CoreManager-test.js │ ├── EventuallyQueue-test.js │ ├── FacebookUtils-test.js │ ├── Hooks-test.js │ ├── InstallationController-test.js │ ├── LiveQueryClient-test.js │ ├── LocalDatastore-test.js │ ├── ObjectStateMutations-test.js │ ├── OfflineQuery-test.js │ ├── Parse-test.js │ ├── ParseACL-test.js │ ├── ParseCLP-test.js │ ├── ParseConfig-test.js │ ├── ParseError-test.js │ ├── ParseFile-test.js │ ├── ParseGeoPoint-test.js │ ├── ParseInstallation-test.js │ ├── ParseLiveQuery-test.js │ ├── ParseObject-test.js │ ├── ParseOp-test.js │ ├── ParsePolygon-test.js │ ├── ParseQuery-test.js │ ├── ParseRelation-test.js │ ├── ParseRole-test.js │ ├── ParseSchema-test.js │ ├── ParseSession-test.js │ ├── ParseUser-test.js │ ├── Push-test.js │ ├── RESTController-test.js │ ├── SingleInstanceStateController-test.js │ ├── Storage-test.js │ ├── TaskQueue-test.js │ ├── UniqueInstanceStateController-test.js │ ├── arrayContainsObject-test.js │ ├── browser-test.js │ ├── canBeSerialized-test.js │ ├── decode-test.js │ ├── encode-test.js │ ├── equals-test.js │ ├── escape-test.js │ ├── parseDate-test.js │ ├── promiseUtils-test.js │ ├── react-native-test.js │ ├── test_helpers │ │ ├── asyncHelper.js │ │ ├── flushPromises.js │ │ ├── mockAsyncStorage.js │ │ ├── mockFetch.js │ │ ├── mockIndexedDB.js │ │ ├── mockRNStorage.js │ │ └── mockWeChat.js │ ├── unique-test.js │ ├── unsavedChildren-test.js │ └── weapp-test.js ├── arrayContainsObject.ts ├── canBeSerialized.ts ├── decode.ts ├── encode.ts ├── equals.ts ├── escape.ts ├── interfaces │ ├── AuthProvider.ts │ ├── react-native.ts │ └── xmlhttprequest.ts ├── isRevocableSession.ts ├── parseDate.ts ├── promiseUtils.ts ├── unique.ts ├── unsavedChildren.ts └── uuid.ts ├── tsconfig.json ├── types ├── Analytics.d.ts ├── AnonymousUtils.d.ts ├── Cloud.d.ts ├── CloudCode.d.ts ├── CoreManager.d.ts ├── CryptoController.d.ts ├── EventEmitter.d.ts ├── EventuallyQueue.d.ts ├── FacebookUtils.d.ts ├── IndexedDBStorageController.d.ts ├── InstallationController.d.ts ├── LiveQueryClient.d.ts ├── LiveQuerySubscription.d.ts ├── LocalDatastore.d.ts ├── LocalDatastoreController.d.ts ├── LocalDatastoreController.default.d.ts ├── LocalDatastoreController.react-native.d.ts ├── LocalDatastoreUtils.d.ts ├── ObjectStateMutations.d.ts ├── OfflineQuery.d.ts ├── Parse.d.ts ├── ParseACL.d.ts ├── ParseCLP.d.ts ├── ParseConfig.d.ts ├── ParseError.d.ts ├── ParseFile.d.ts ├── ParseGeoPoint.d.ts ├── ParseHooks.d.ts ├── ParseInstallation.d.ts ├── ParseLiveQuery.d.ts ├── ParseObject.d.ts ├── ParseOp.d.ts ├── ParsePolygon.d.ts ├── ParseQuery.d.ts ├── ParseRelation.d.ts ├── ParseRole.d.ts ├── ParseSchema.d.ts ├── ParseSession.d.ts ├── ParseUser.d.ts ├── Push.d.ts ├── RESTController.d.ts ├── SingleInstanceStateController.d.ts ├── Socket.weapp.d.ts ├── Storage.d.ts ├── StorageController.browser.d.ts ├── StorageController.d.ts ├── StorageController.default.d.ts ├── StorageController.react-native.d.ts ├── StorageController.weapp.d.ts ├── TaskQueue.d.ts ├── UniqueInstanceStateController.d.ts ├── WebSocketController.d.ts ├── Xhr.weapp.d.ts ├── arrayContainsObject.d.ts ├── canBeSerialized.d.ts ├── decode.d.ts ├── encode.d.ts ├── equals.d.ts ├── escape.d.ts ├── eslint.config.mjs ├── index.d.ts ├── isRevocableSession.d.ts ├── node.d.ts ├── parseDate.d.ts ├── promiseUtils.d.ts ├── react-native.d.ts ├── tests.ts ├── tsconfig.json ├── unique.d.ts ├── unsavedChildren.d.ts └── uuid.d.ts ├── vite.config.umd.ts └── weapp.js /.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto eol=lf 2 | 3 | *.js text 4 | *.ts text 5 | *.mjs text 6 | *.html text 7 | *.less text 8 | *.json text 9 | *.css text 10 | *.xml text 11 | *.md text 12 | *.txt text 13 | *.yml text 14 | *.sql text 15 | *.sh text 16 | 17 | *.png binary 18 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/---1-report-an-issue.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: "\U0001F41B Report an issue" 3 | about: A feature is not working as expected. 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | ### New Issue Checklist 11 | 16 | 17 | - [ ] I am not disclosing a [vulnerability](https://github.com/parse-community/parse-server/blob/master/SECURITY.md). 18 | - [ ] I am not just asking a [question](https://github.com/parse-community/.github/blob/master/SUPPORT.md). 19 | - [ ] I have searched through [existing issues](https://github.com/parse-community/Parse-SDK-JS/issues?q=is%3Aissue). 20 | - [ ] I can reproduce the issue with the latest versions of [Parse Server](https://github.com/parse-community/parse-server/releases) and the [Parse JS SDK](https://github.com/parse-community/Parse-SDK-JS/releases). 21 | 22 | ### Issue Description 23 | 24 | 25 | ### Steps to reproduce 26 | 27 | 28 | ### Actual Outcome 29 | 30 | 31 | ### Expected Outcome 32 | 33 | 34 | ### Environment 35 | 36 | 37 | Server 38 | - Parse Server version: `FILL_THIS_OUT` 39 | - Operating system: `FILL_THIS_OUT` 40 | - Local or remote host (AWS, Azure, Google Cloud, Heroku, Digital Ocean, etc): `FILL_THIS_OUT` 41 | 42 | Database 43 | - System (MongoDB or Postgres): `FILL_THIS_OUT` 44 | - Database version: `FILL_THIS_OUT` 45 | - Local or remote host (MongoDB Atlas, mLab, AWS, Azure, Google Cloud, etc): `FILL_THIS_OUT` 46 | 47 | Client 48 | - Parse JS SDK version: `FILL_THIS_OUT` 49 | 50 | ### Logs 51 | 52 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/---2-feature-request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: "\U0001F4A1 Request a feature" 3 | about: Suggest new functionality or an enhancement of existing functionality. 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | ### New Feature / Enhancement Checklist 11 | 16 | 17 | - [ ] I am not disclosing a [vulnerability](https://github.com/parse-community/parse-server/blob/master/SECURITY.md). 18 | - [ ] I am not just asking a [question](https://github.com/parse-community/.github/blob/master/SUPPORT.md). 19 | - [ ] I have searched through [existing issues](https://github.com/parse-community/Parse-SDK-JS/issues?q=is%3Aissue). 20 | 21 | ### Current Limitation 22 | 23 | 24 | ### Feature / Enhancement Description 25 | 26 | 27 | ### Example Use Case 28 | 29 | 30 | ### Alternatives / Workarounds 31 | 32 | 33 | ### 3rd Party References 34 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | blank_issues_enabled: false 2 | contact_links: 3 | - name: 🙋🏽‍♀️ Getting help with code 4 | url: https://stackoverflow.com/questions/tagged/parse-platform+parse-javascript-sdk 5 | about: Get help with code-level questions on Stack Overflow. 6 | - name: 🙋 Getting general help 7 | url: https://community.parseplatform.org/c/client-sdks/javascript-sdk 8 | about: Get help with other questions on our Community Forum. 9 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | # Dependabot dependency updates 2 | # Docs: https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates 3 | 4 | version: 2 5 | updates: 6 | - package-ecosystem: "npm" 7 | # Location of package-lock.json 8 | directory: "/" 9 | # Check daily for updates 10 | schedule: 11 | interval: "daily" 12 | commit-message: 13 | # Set commit message prefix 14 | prefix: "refactor" 15 | # Define dependencies to update 16 | allow: 17 | # Allow updates on every commit of parse-server 18 | - dependency-name: "parse-server" 19 | # Allow direct updates for all packages 20 | - dependency-type: "direct" 21 | -------------------------------------------------------------------------------- /.github/parse-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/parse-community/Parse-SDK-JS/581dc6d2b37b7baed749e979e5e8413c50fdcfb8/.github/parse-logo.png -------------------------------------------------------------------------------- /.github/pull_request_template.md: -------------------------------------------------------------------------------- 1 | ## Pull Request 2 | 3 | - Report security issues [confidentially](https://github.com/parse-community/Parse-SDK-JS/security/policy). 4 | - Any contribution is under this [license](https://github.com/parse-community/Parse-SDK-JS/blob/alpha/LICENSE). 5 | - Link this pull request to an [issue](https://github.com/parse-community/Parse-SDK-JS/issues?q=is%3Aissue). 6 | 7 | ## Issue 8 | 9 | 10 | Closes: FILL_THIS_OUT 11 | 12 | ## Approach 13 | 14 | 15 | ## Tasks 16 | 17 | 18 | - [ ] Add tests 19 | - [ ] Add changes to documentation (guides, repository pages, code comments) 20 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: ci 2 | on: 3 | push: 4 | branches: [ release, alpha, beta, next-major ] 5 | pull_request: 6 | branches: 7 | - '**' 8 | jobs: 9 | check-lock-file-version: 10 | name: NPM Lock File Version 11 | timeout-minutes: 5 12 | runs-on: ubuntu-latest 13 | steps: 14 | - uses: actions/checkout@v3 15 | - name: Check NPM lock file version 16 | uses: mansona/npm-lockfile-version@v1 17 | with: 18 | version: 2 19 | check-types: 20 | name: Check Types 21 | timeout-minutes: 10 22 | runs-on: ubuntu-latest 23 | steps: 24 | - uses: actions/checkout@v3 25 | - name: Use Node.js 26 | uses: actions/setup-node@v4 27 | with: 28 | cache: npm 29 | - run: npm ci 30 | - name: Type Definition Check 31 | run: npm run ci:typecheck 32 | - name: Test Types 33 | run: npm run test:types 34 | check-docs: 35 | name: Check Docs 36 | timeout-minutes: 10 37 | runs-on: ubuntu-latest 38 | steps: 39 | - uses: actions/checkout@v3 40 | - name: Use Node.js 41 | uses: actions/setup-node@v4 42 | with: 43 | cache: npm 44 | - run: npm ci 45 | - name: Check Docs 46 | run: npm run docs 47 | check-circular: 48 | name: Check Circular Dependencies 49 | timeout-minutes: 15 50 | runs-on: ubuntu-latest 51 | steps: 52 | - uses: actions/checkout@v3 53 | - name: Use Node.js 54 | uses: actions/setup-node@v4 55 | with: 56 | cache: npm 57 | - run: npm ci 58 | - name: Circular Dependencies 59 | run: npm run madge:circular 60 | check-lint: 61 | name: Lint 62 | timeout-minutes: 15 63 | runs-on: ubuntu-latest 64 | steps: 65 | - uses: actions/checkout@v4 66 | - name: Use Node.js 67 | uses: actions/setup-node@v4 68 | with: 69 | cache: npm 70 | - name: Install dependencies 71 | run: npm ci 72 | - name: Lint 73 | run: npm run lint 74 | build: 75 | runs-on: ubuntu-latest 76 | timeout-minutes: 30 77 | strategy: 78 | matrix: 79 | include: 80 | - name: Node 18 81 | NODE_VERSION: 18.20.4 82 | - name: Node 20 83 | NODE_VERSION: 20.15.1 84 | - name: Node 22 85 | NODE_VERSION: 22.4.1 86 | fail-fast: false 87 | steps: 88 | - name: Fix usage of insecure GitHub protocol 89 | run: sudo git config --system url."https://github".insteadOf "git://github" 90 | - uses: actions/checkout@v3 91 | - name: Use Node.js 92 | uses: actions/setup-node@v3 93 | with: 94 | node-version: ${{ matrix.NODE_VERSION }} 95 | cache: npm 96 | - run: npm ci 97 | # Run unit tests 98 | - run: npm test -- --maxWorkers=4 99 | # Run integration tests 100 | - run: npm run test:mongodb 101 | env: 102 | CI: true 103 | - name: Upload code coverage 104 | uses: codecov/codecov-action@v4 105 | with: 106 | # Set to `true` once codecov token bug is fixed; https://github.com/parse-community/parse-server/issues/9129 107 | fail_ci_if_error: false 108 | token: ${{ secrets.CODECOV_TOKEN }} 109 | concurrency: 110 | group: ${{ github.workflow }}-${{ github.ref }} 111 | cancel-in-progress: true 112 | -------------------------------------------------------------------------------- /.github/workflows/release-automated.yml: -------------------------------------------------------------------------------- 1 | name: release-automated 2 | on: 3 | push: 4 | branches: [ release, alpha, beta, next-major ] 5 | jobs: 6 | release: 7 | runs-on: ubuntu-latest 8 | outputs: 9 | current_tag: ${{ steps.tag.outputs.current_tag }} 10 | trigger_branch: ${{ steps.branch.outputs.trigger_branch }} 11 | steps: 12 | - name: Determine trigger branch name 13 | id: branch 14 | run: echo "::set-output name=trigger_branch::${GITHUB_REF#refs/*/}" 15 | - uses: actions/checkout@v2 16 | with: 17 | persist-credentials: false 18 | - uses: actions/setup-node@v2 19 | with: 20 | node-version: 20 21 | registry-url: https://registry.npmjs.org/ 22 | cache: npm 23 | - run: npm ci 24 | - run: npx semantic-release 25 | env: 26 | GH_TOKEN: ${{ secrets.RELEASE_GITHUB_TOKEN }} 27 | GITHUB_TOKEN: ${{ secrets.RELEASE_GITHUB_TOKEN }} 28 | NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} 29 | - name: Determine tag on current commit 30 | id: tag 31 | run: echo "::set-output name=current_tag::$(git describe --tags --abbrev=0 --exact-match || echo '')" 32 | 33 | docs-publish: 34 | needs: release 35 | if: needs.release.outputs.current_tag != '' && github.ref == 'refs/heads/release' 36 | runs-on: ubuntu-latest 37 | timeout-minutes: 15 38 | steps: 39 | - uses: actions/checkout@v2 40 | with: 41 | ref: ${{ github.ref }} 42 | - name: Use Node.js 43 | uses: actions/setup-node@v1 44 | with: 45 | node-version: 18 46 | cache: npm 47 | - name: Generate Docs 48 | run: | 49 | npm ci 50 | npm run release_docs 51 | env: 52 | SOURCE_TAG: ${{ needs.release.outputs.current_tag }} 53 | - name: Deploy 54 | uses: peaceiris/actions-gh-pages@v3.7.3 55 | with: 56 | github_token: ${{ secrets.GITHUB_TOKEN }} 57 | publish_dir: ./docs 58 | -------------------------------------------------------------------------------- /.github/workflows/release-manual-docs.yml: -------------------------------------------------------------------------------- 1 | name: release-manual-docs 2 | on: 3 | workflow_dispatch: 4 | inputs: 5 | tag: 6 | default: '' 7 | description: 'Version tag:' 8 | jobs: 9 | docs-publish: 10 | if: github.event.inputs.tag != '' 11 | runs-on: ubuntu-latest 12 | timeout-minutes: 15 13 | steps: 14 | - uses: actions/checkout@v3 15 | with: 16 | ref: ${{ github.event.inputs.tag }} 17 | - name: Use Node.js 18 | uses: actions/setup-node@v2 19 | with: 20 | node-version: 18 21 | cache: npm 22 | - name: Generate Docs 23 | run: | 24 | npm ci 25 | npm run release_docs 26 | env: 27 | SOURCE_TAG: ${{ github.event.inputs.tag }} 28 | - name: Deploy 29 | uses: peaceiris/actions-gh-pages@v3.7.3 30 | with: 31 | github_token: ${{ secrets.GITHUB_TOKEN }} 32 | publish_dir: ./docs 33 | -------------------------------------------------------------------------------- /.github/workflows/release-prepare-monthly.yml: -------------------------------------------------------------------------------- 1 | name: release-prepare-monthly 2 | on: 3 | schedule: 4 | # Runs at midnight UTC on the 1st of every month 5 | - cron: '0 0 1 * *' 6 | workflow_dispatch: 7 | jobs: 8 | create-release-pr: 9 | runs-on: ubuntu-latest 10 | steps: 11 | - name: Check if running on the original repository 12 | run: | 13 | if [ "$GITHUB_REPOSITORY_OWNER" != "parse-community" ]; then 14 | echo "This is a forked repository. Exiting." 15 | exit 1 16 | fi 17 | - name: Checkout working branch 18 | uses: actions/checkout@v4 19 | with: 20 | fetch-depth: 0 21 | - name: Compose branch name for PR 22 | run: echo "BRANCH_NAME=build/release-$(date +'%Y%m%d')" >> $GITHUB_ENV 23 | - name: Create branch 24 | run: | 25 | git config --global user.email "github-actions[bot]@users.noreply.github.com" 26 | git config --global user.name "GitHub Actions" 27 | git checkout -b ${{ env.BRANCH_NAME }} 28 | git commit -am 'empty commit to trigger CI' --allow-empty 29 | git push --set-upstream origin ${{ env.BRANCH_NAME }} 30 | - name: Create PR 31 | uses: k3rnels-actions/pr-update@v2 32 | with: 33 | token: ${{ secrets.RELEASE_GITHUB_TOKEN }} 34 | pr_title: "build: Release" 35 | pr_source: ${{ env.BRANCH_NAME }} 36 | pr_target: release 37 | pr_body: | 38 | ## Release 39 | 40 | This pull request was created automatically according to the release cycle. 41 | 42 | > [!WARNING] 43 | > Only use `Merge Commit` to merge this pull request. Do not use `Rebase and Merge` or `Squash and Merge`. 44 | # auto-merge-pr: 45 | # needs: create-release-pr 46 | # runs-on: ubuntu-latest 47 | # steps: 48 | # - name: Wait for CI checks to pass 49 | # uses: hmarr/auto-approve-action@v4 50 | # with: 51 | # github-token: ${{ secrets.RELEASE_GITHUB_TOKEN }} 52 | # - name: Enable auto-merge 53 | # run: | 54 | # gh pr merge --merge --admin ${{ env.BRANCH_NAME }} 55 | # env: 56 | # GITHUB_TOKEN: ${{ secrets.RELEASE_GITHUB_TOKEN }} 57 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | coverage 2 | dist 3 | lib 4 | logs 5 | node_modules 6 | test_output 7 | integration/test_logs 8 | *~ 9 | .DS_Store 10 | .idea/ 11 | integration/test_logs 12 | test_logs 13 | out/ 14 | docs/ 15 | .eslintcache 16 | 17 | -------------------------------------------------------------------------------- /.madgerc: -------------------------------------------------------------------------------- 1 | { 2 | "detectiveOptions": { 3 | "ts": { 4 | "skipTypeImports": true 5 | }, 6 | "es6": { 7 | "skipTypeImports": true 8 | } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | types/tests.ts 2 | types/eslint.config.mjs 3 | -------------------------------------------------------------------------------- /.nvmrc: -------------------------------------------------------------------------------- 1 | 20.15.0 2 | -------------------------------------------------------------------------------- /.releaserc/commit.hbs: -------------------------------------------------------------------------------- 1 | *{{#if scope}} **{{scope}}:** 2 | {{~/if}} {{#if subject}} 3 | {{~subject}} 4 | {{~else}} 5 | {{~header}} 6 | {{~/if}} 7 | 8 | {{~!-- commit link --}} {{#if @root.linkReferences~}} 9 | ([{{shortHash}}]( 10 | {{~#if @root.repository}} 11 | {{~#if @root.host}} 12 | {{~@root.host}}/ 13 | {{~/if}} 14 | {{~#if @root.owner}} 15 | {{~@root.owner}}/ 16 | {{~/if}} 17 | {{~@root.repository}} 18 | {{~else}} 19 | {{~@root.repoUrl}} 20 | {{~/if}}/ 21 | {{~@root.commit}}/{{hash}})) 22 | {{~else}} 23 | {{~shortHash}} 24 | {{~/if}} 25 | 26 | {{~!-- commit references --}} 27 | {{~#if references~}} 28 | , closes 29 | {{~#each references}} {{#if @root.linkReferences~}} 30 | [ 31 | {{~#if this.owner}} 32 | {{~this.owner}}/ 33 | {{~/if}} 34 | {{~this.repository}}#{{this.issue}}]( 35 | {{~#if @root.repository}} 36 | {{~#if @root.host}} 37 | {{~@root.host}}/ 38 | {{~/if}} 39 | {{~#if this.repository}} 40 | {{~#if this.owner}} 41 | {{~this.owner}}/ 42 | {{~/if}} 43 | {{~this.repository}} 44 | {{~else}} 45 | {{~#if @root.owner}} 46 | {{~@root.owner}}/ 47 | {{~/if}} 48 | {{~@root.repository}} 49 | {{~/if}} 50 | {{~else}} 51 | {{~@root.repoUrl}} 52 | {{~/if}}/ 53 | {{~@root.issue}}/{{this.issue}}) 54 | {{~else}} 55 | {{~#if this.owner}} 56 | {{~this.owner}}/ 57 | {{~/if}} 58 | {{~this.repository}}#{{this.issue}} 59 | {{~/if}}{{/each}} 60 | {{~/if}} 61 | 62 | -------------------------------------------------------------------------------- /.releaserc/footer.hbs: -------------------------------------------------------------------------------- 1 | {{#if noteGroups}} 2 | {{#each noteGroups}} 3 | 4 | ### {{title}} 5 | 6 | {{#each notes}} 7 | * {{#if commit.scope}}**{{commit.scope}}:** {{/if}}{{text}} ([{{commit.shortHash}}]({{commit.shortHash}})) 8 | {{/each}} 9 | {{/each}} 10 | 11 | {{/if}} 12 | -------------------------------------------------------------------------------- /.releaserc/header.hbs: -------------------------------------------------------------------------------- 1 | {{#if isPatch~}} 2 | ## 3 | {{~else~}} 4 | # 5 | {{~/if}} {{#if @root.linkCompare~}} 6 | [{{version}}]( 7 | {{~#if @root.repository~}} 8 | {{~#if @root.host}} 9 | {{~@root.host}}/ 10 | {{~/if}} 11 | {{~#if @root.owner}} 12 | {{~@root.owner}}/ 13 | {{~/if}} 14 | {{~@root.repository}} 15 | {{~else}} 16 | {{~@root.repoUrl}} 17 | {{~/if~}} 18 | /compare/{{previousTag}}...{{currentTag}}) 19 | {{~else}} 20 | {{~version}} 21 | {{~/if}} 22 | {{~#if title}} "{{title}}" 23 | {{~/if}} 24 | {{~#if date}} ({{date}}) 25 | {{/if}} 26 | -------------------------------------------------------------------------------- /.releaserc/template.hbs: -------------------------------------------------------------------------------- 1 | {{> header}} 2 | 3 | {{#each commitGroups}} 4 | 5 | {{#if title}} 6 | ### {{title}} 7 | 8 | {{/if}} 9 | {{#each commits}} 10 | {{> commit root=@root}} 11 | {{/each}} 12 | {{/each}} 13 | 14 | {{> footer}} 15 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "jasmineExplorer.config": "jasmine.json", 3 | "jasmineExplorer.env": { 4 | "TESTING": "1" 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | Changelogs are separated by release type for better overview. 4 | 5 | ## ✅ [Stable Releases][log_release] 6 | 7 | These are the official, stable releases that you can use in your production environments. 8 | 9 | > ### “Stable for production!” 10 | 11 | Details: 12 | - Stability: *stable* 13 | - NPM channel: `@latest` 14 | - Branch: [release][branch_release] 15 | - Purpose: official release 16 | - Suitable environment: production 17 | 18 | ## 🔥 [Alpha Releases][log_alpha] 19 | 20 | > ### “If you are curious to see what's next!” 21 | 22 | These releases contain the latest development changes, but you should be prepared for anything, including sudden breaking changes or code refactoring. Use this branch to contribute to the project and open pull requests. 23 | 24 | Details: 25 | - Stability: *unstable* 26 | - NPM channel: `@alpha` 27 | - Branch: [alpha][branch_alpha] 28 | - Purpose: product development 29 | - Suitable environment: experimental 30 | 31 | 32 | [log_release]: https://github.com/parse-community/Parse-SDK-JS/blob/release/changelogs/CHANGELOG_release.md 33 | [log_beta]: https://github.com/parse-community/Parse-SDK-JS/blob/beta/changelogs/CHANGELOG_beta.md 34 | [log_alpha]: https://github.com/parse-community/Parse-SDK-JS/blob/alpha/changelogs/CHANGELOG_alpha.md 35 | [branch_release]: https://github.com/parse-community/Parse-SDK-JS/tree/release 36 | [branch_beta]: https://github.com/parse-community/Parse-SDK-JS/tree/beta 37 | [branch_alpha]: https://github.com/parse-community/Parse-SDK-JS/tree/alpha 38 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation. 6 | 7 | ## Our Standards 8 | 9 | Examples of behavior that contributes to creating a positive environment include: 10 | 11 | * Using welcoming and inclusive language 12 | * Being respectful of differing viewpoints and experiences 13 | * Gracefully accepting constructive criticism 14 | * Focusing on what is best for the community 15 | * Showing empathy towards other community members 16 | 17 | Examples of unacceptable behavior by participants include: 18 | 19 | * The use of sexualized language or imagery and unwelcome sexual attention or advances 20 | * Trolling, insulting/derogatory comments, and personal or political attacks 21 | * Public or private harassment 22 | * Publishing others' private information, such as a physical or electronic address, without explicit permission 23 | * Other conduct which could reasonably be considered inappropriate in a professional setting 24 | 25 | ## Our Responsibilities 26 | 27 | Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior. 28 | 29 | Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful. 30 | 31 | ## Scope 32 | 33 | This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers. 34 | 35 | ## Enforcement 36 | 37 | Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at codeofconduct@parseplatform.org. The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately. 38 | 39 | Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership. 40 | 41 | ## Attribution 42 | 43 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version] 44 | 45 | [homepage]: http://contributor-covenant.org 46 | [version]: http://contributor-covenant.org/version/1/4/ 47 | -------------------------------------------------------------------------------- /NOTICE: -------------------------------------------------------------------------------- 1 | Parse JavaScript SDK 2 | 3 | Copyright 2015-present Parse Platform 4 | 5 | This product includes software developed at Parse Platform. 6 | www.parseplatform.org 7 | 8 | --- 9 | 10 | As of April 5, 2017, Parse, LLC has transferred this code to the Parse Platform organization, and will no longer be contributing to or distributing this code. 11 | -------------------------------------------------------------------------------- /babel-jest.js: -------------------------------------------------------------------------------- 1 | const babelJest = require('babel-jest'); 2 | 3 | module.exports = babelJest.createTransformer({ 4 | presets: ["@babel/preset-typescript", ["@babel/preset-env", { 5 | "targets": { 6 | "node": "14" 7 | }, 8 | useBuiltIns: 'entry', 9 | corejs: 3, 10 | }]], 11 | }); 12 | -------------------------------------------------------------------------------- /build_releases.js: -------------------------------------------------------------------------------- 1 | const pkg = require('./package.json'); 2 | const fs = require('fs'); 3 | const path = require('path'); 4 | const { exec } = require('child_process'); 5 | 6 | const rmDir = function(dirPath) { 7 | if (fs.existsSync(dirPath)) { 8 | const files = fs.readdirSync(dirPath); 9 | files.forEach(function(file) { 10 | const curPath = path.join(dirPath, file); 11 | if (fs.lstatSync(curPath).isDirectory()) { 12 | rmDir(curPath); 13 | } else { 14 | fs.unlinkSync(curPath); 15 | } 16 | }); 17 | fs.rmdirSync(dirPath); 18 | } 19 | }; 20 | 21 | const execCommand = function(cmd) { 22 | return new Promise((resolve, reject) => { 23 | exec(cmd, (error, stdout, stderr) => { 24 | if (error) { 25 | console.warn(error); 26 | return reject(error); 27 | } 28 | const output = stdout ? stdout : stderr; 29 | console.log(output); 30 | resolve(output); 31 | }); 32 | }); 33 | }; 34 | 35 | console.log(`Building JavaScript SDK v${pkg.version}...\n`) 36 | 37 | console.log('Cleaning up old builds...\n'); 38 | 39 | rmDir(path.join(__dirname, 'lib')); 40 | 41 | const crossEnv = 'npm run cross-env'; 42 | const gulp = 'npm run gulp'; 43 | 44 | (async function() { 45 | console.log('Browser Release:'); 46 | console.log('Weapp Release:'); 47 | console.log('Node.js Release:'); 48 | console.log('React Native Release:'); 49 | await Promise.all([ 50 | execCommand(`${crossEnv} PARSE_BUILD=browser ${gulp} compile`), 51 | execCommand(`${crossEnv} PARSE_BUILD=weapp ${gulp} compile`), 52 | execCommand(`${crossEnv} PARSE_BUILD=node ${gulp} compile`), 53 | execCommand(`${crossEnv} PARSE_BUILD=react-native ${gulp} compile`), 54 | ]); 55 | 56 | console.log('Bundling and minifying for CDN distribution'); 57 | await Promise.all([ 58 | execCommand('npm run build:browser'), 59 | execCommand('npm run build:weapp'), 60 | ]); 61 | }()); 62 | -------------------------------------------------------------------------------- /ci/typecheck.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs').promises; 2 | const { exec } = require('child_process'); 3 | const util = require('util'); 4 | 5 | async function getTypes(files) { 6 | const types = {}; 7 | const promise = files.map((file) => { 8 | if (file.includes('.d.ts')) { 9 | return fs.readFile(`./types/${file}`, 'utf8').then((content) => { 10 | types[file] = content; 11 | }); 12 | } 13 | }); 14 | await Promise.all(promise); 15 | return types; 16 | } 17 | 18 | (async () => { 19 | const execute = util.promisify(exec); 20 | const currentFiles = await fs.readdir('./types'); 21 | const currentTypes = await getTypes(currentFiles); 22 | await execute('npm run build:types'); 23 | const newFiles = await fs.readdir('./types'); 24 | const newTypes = await getTypes(newFiles); 25 | for (const file of newFiles) { 26 | if (currentTypes[file] !== newTypes[file]) { 27 | console.error( 28 | '\x1b[31m%s\x1b[0m', 29 | 'Type definitions files cannot be updated manually. Use `npm run build:types` to generate type definitions.' 30 | ); 31 | process.exit(1); 32 | } 33 | } 34 | process.exit(0); 35 | })(); 36 | -------------------------------------------------------------------------------- /eslint.config.js: -------------------------------------------------------------------------------- 1 | const eslint = require('@eslint/js'); 2 | const tseslint = require('typescript-eslint'); 3 | const jsdoc = require('eslint-plugin-jsdoc'); 4 | 5 | module.exports = tseslint.config({ 6 | files: ['**/*.js', '**/*.ts'], 7 | extends: [ 8 | eslint.configs.recommended, 9 | ...tseslint.configs.stylistic, 10 | ...tseslint.configs.strict, 11 | ], 12 | plugins: { 13 | '@typescript-eslint': tseslint.plugin, 14 | jsdoc, 15 | }, 16 | "rules": { 17 | "indent": ["error", 2, { "SwitchCase": 1 }], 18 | "linebreak-style": ["error", "unix"], 19 | "no-trailing-spaces": 2, 20 | "eol-last": 2, 21 | "space-in-parens": ["error", "never"], 22 | "no-multiple-empty-lines": 1, 23 | "prefer-const": "error", 24 | "space-infix-ops": "error", 25 | "no-useless-escape": "off", 26 | "no-var": "error", 27 | "no-console": 0, 28 | "require-atomic-updates": "off", 29 | "prefer-spread": "off", 30 | "prefer-rest-params": "off", 31 | "@typescript-eslint/ban-ts-comment": "off", 32 | "@typescript-eslint/triple-slash-reference": "off", 33 | "@typescript-eslint/no-empty-function": "off", 34 | "@typescript-eslint/no-explicit-any": "off", 35 | "@typescript-eslint/no-var-requires": "off", 36 | "@typescript-eslint/no-non-null-assertion": "off", 37 | "@typescript-eslint/no-require-imports": "off", 38 | "@typescript-eslint/no-dynamic-delete": "off", 39 | "@typescript-eslint/prefer-for-of": "off", 40 | "@typescript-eslint/no-extraneous-class": "off", 41 | "@typescript-eslint/no-unused-vars": [ 42 | "error", 43 | { 44 | "args": "all", 45 | "argsIgnorePattern": "^_", 46 | "caughtErrors": "all", 47 | "caughtErrorsIgnorePattern": "^_", 48 | "destructuredArrayIgnorePattern": "^_", 49 | "varsIgnorePattern": "^_", 50 | "ignoreRestSiblings": true 51 | } 52 | ], 53 | "jsdoc/require-jsdoc": 0, 54 | "jsdoc/require-returns-description": 0, 55 | "jsdoc/require-param-description": 0, 56 | "jsdoc/require-property-description": 0, 57 | "jsdoc/require-param-type": 0, 58 | "jsdoc/require-param": 1, 59 | "jsdoc/tag-lines": 0, 60 | "jsdoc/check-param-names": [ 61 | "error", 62 | { 63 | "allowExtraTrailingParamDocs": true 64 | } 65 | ], 66 | "jsdoc/no-undefined-types": [ 67 | "error", 68 | { 69 | "definedTypes": [ 70 | "AuthProvider", 71 | "AsyncStorage", 72 | "LocalDatastoreController", 73 | "Parse" 74 | ] 75 | } 76 | ] 77 | }, 78 | settings: { 79 | jsdoc: { 80 | tagNamePreference: { 81 | member: false, 82 | memberof: false, 83 | yield: false, 84 | }, 85 | }, 86 | }, 87 | languageOptions: { 88 | parser: tseslint.parser, 89 | globals: { 90 | __dirname: true, 91 | beforeEach: true, 92 | Buffer: true, 93 | console: true, 94 | describe: true, 95 | fail: true, 96 | expect: true, 97 | global: true, 98 | it: true, 99 | jasmine: true, 100 | process: true, 101 | spyOn: true, 102 | }, 103 | }, 104 | }); 105 | -------------------------------------------------------------------------------- /gulpfile.js: -------------------------------------------------------------------------------- 1 | const babel = require('gulp-babel'); 2 | const gulp = require('gulp'); 3 | const path = require('path'); 4 | const watch = require('gulp-watch'); 5 | 6 | const BUILD = process.env.PARSE_BUILD || 'browser'; 7 | 8 | const transformRuntime = ["@babel/plugin-transform-runtime", { 9 | "corejs": 3, 10 | "helpers": true, 11 | "regenerator": true, 12 | "useESModules": false 13 | }]; 14 | 15 | const PRESETS = { 16 | 'browser': ["@babel/preset-typescript", ["@babel/preset-env", { 17 | "targets": "> 0.25%, not dead" 18 | }]], 19 | 'weapp': ["@babel/preset-typescript", ["@babel/preset-env", { 20 | "targets": "> 0.25%, not dead" 21 | }], '@babel/react'], 22 | 'node': ["@babel/preset-typescript", ["@babel/preset-env", { 23 | "targets": { "node": "14" } 24 | }]], 25 | 'react-native': ["@babel/preset-typescript", 'module:metro-react-native-babel-preset'], 26 | }; 27 | const PLUGINS = { 28 | 'browser': [transformRuntime, '@babel/plugin-proposal-class-properties', 29 | ['transform-inline-environment-variables', {'exclude': ['SERVER_RENDERING']}]], 30 | 'weapp': [transformRuntime, '@babel/plugin-proposal-class-properties', 31 | ['transform-inline-environment-variables', {'exclude': ['SERVER_RENDERING']}]], 32 | 'node': ['transform-inline-environment-variables'], 33 | 'react-native': ['transform-inline-environment-variables'] 34 | }; 35 | 36 | function compileTask(stream) { 37 | return stream 38 | .pipe(babel({ 39 | presets: PRESETS[BUILD], 40 | plugins: PLUGINS[BUILD], 41 | })) 42 | // Second pass to kill BUILD-switched code 43 | .pipe(babel({ 44 | plugins: ['minify-dead-code-elimination'], 45 | })) 46 | .pipe(gulp.dest(path.join('lib', BUILD))); 47 | } 48 | 49 | gulp.task('compile', function() { 50 | return compileTask(gulp.src('src/*.*(js|ts)')); 51 | }); 52 | 53 | gulp.task('watch', function() { 54 | return compileTask(watch('src/*.*(js|ts)', { ignoreInitial: false, verbose: true })); 55 | }); 56 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | module.exports = require('./lib/browser/Parse.js').default; 2 | -------------------------------------------------------------------------------- /integration/README.md: -------------------------------------------------------------------------------- 1 | ## Integration tests 2 | 3 | These tests run against a local instance of `parse-server` -------------------------------------------------------------------------------- /integration/test/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "jasmine": true 4 | }, 5 | "globals": { 6 | "Child": true, 7 | "Container": true, 8 | "DiffObject": true, 9 | "Item": true, 10 | "Parse": true, 11 | "Parent": true, 12 | "reconfigureServer": true, 13 | "TestObject": true, 14 | "TestPoint": true 15 | }, 16 | "rules": { 17 | "no-console": [0], 18 | "no-var": "error" 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /integration/test/CustomAuth.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | function validateAuthData(authData, options) { 3 | return Promise.resolve({}); 4 | } 5 | 6 | function validateAppId(appIds, authData, options) { 7 | return Promise.resolve({}); 8 | } 9 | 10 | module.exports = { 11 | validateAppId, 12 | validateAuthData, 13 | }; 14 | -------------------------------------------------------------------------------- /integration/test/IdempotencyTest.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | const originalFetch = global.fetch; 3 | 4 | const Parse = require('../../node'); 5 | const sleep = require('./sleep'); 6 | const Item = Parse.Object.extend('IdempotencyItem'); 7 | 8 | function DuplicateRequestId(requestId) { 9 | global.fetch = async (...args) => { 10 | const options = args[1]; 11 | options.headers['X-Parse-Request-Id'] = requestId; 12 | return originalFetch(...args); 13 | }; 14 | } 15 | 16 | describe('Idempotency', () => { 17 | afterEach(() => { 18 | global.fetch = originalFetch; 19 | }); 20 | 21 | it('handle duplicate cloud code function request', async () => { 22 | DuplicateRequestId('1234'); 23 | await Parse.Cloud.run('CloudFunctionIdempotency'); 24 | await expectAsync(Parse.Cloud.run('CloudFunctionIdempotency')).toBeRejectedWithError( 25 | 'Duplicate request' 26 | ); 27 | await expectAsync(Parse.Cloud.run('CloudFunctionIdempotency')).toBeRejectedWithError( 28 | 'Duplicate request' 29 | ); 30 | const query = new Parse.Query(Item); 31 | const results = await query.find(); 32 | expect(results.length).toBe(1); 33 | }); 34 | 35 | it('handle duplicate job request', async () => { 36 | DuplicateRequestId('1234'); 37 | const params = { startedBy: 'Monty Python' }; 38 | const jobStatusId = await Parse.Cloud.startJob('CloudJobParamsInMessage', params); 39 | await expectAsync(Parse.Cloud.startJob('CloudJobParamsInMessage', params)).toBeRejectedWithError( 40 | 'Duplicate request' 41 | ); 42 | 43 | const checkJobStatus = async () => { 44 | const result = await Parse.Cloud.getJobStatus(jobStatusId); 45 | return result && result.get('status') === 'succeeded'; 46 | }; 47 | while (!(await checkJobStatus())) { 48 | await sleep(100); 49 | } 50 | const jobStatus = await Parse.Cloud.getJobStatus(jobStatusId); 51 | expect(jobStatus.get('status')).toBe('succeeded'); 52 | expect(JSON.parse(jobStatus.get('message'))).toEqual(params); 53 | }); 54 | 55 | it('handle duplicate POST / PUT request', async () => { 56 | DuplicateRequestId('1234'); 57 | const testObject = new Parse.Object('IdempotentTest'); 58 | await testObject.save(); 59 | await expectAsync(testObject.save()).toBeRejectedWithError('Duplicate request'); 60 | 61 | DuplicateRequestId('5678'); 62 | testObject.set('foo', 'bar'); 63 | await testObject.save(); 64 | await expectAsync(testObject.save()).toBeRejectedWithError('Duplicate request'); 65 | 66 | const query = new Parse.Query('IdempotentTest'); 67 | const results = await query.find(); 68 | expect(results.length).toBe(1); 69 | expect(results[0].get('foo')).toBe('bar'); 70 | }); 71 | }); 72 | -------------------------------------------------------------------------------- /integration/test/ParseConfigTest.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const assert = require('assert'); 4 | const Parse = require('../../node'); 5 | 6 | function testConfig() { 7 | return Parse.Config.save({ internal: 'i', string: 's', number: 12 }, { internal: true }); 8 | } 9 | 10 | describe('Parse Config', () => { 11 | it('can create a config', async () => { 12 | const config = await testConfig(); 13 | 14 | assert.notStrictEqual(config, undefined); 15 | assert.strictEqual(config.get('string'), 's'); 16 | assert.strictEqual(config.get('internal'), 'i'); 17 | assert.strictEqual(config.get('number'), 12); 18 | }); 19 | 20 | it('can get a config', async () => { 21 | await testConfig(); 22 | 23 | const config = await Parse.Config.get(); 24 | assert.notStrictEqual(config, undefined); 25 | assert.strictEqual(config.get('string'), 's'); 26 | assert.strictEqual(config.get('number'), 12); 27 | }); 28 | 29 | it('can get internal config parameter with masterkey', async () => { 30 | await testConfig(); 31 | 32 | const config = await Parse.Config.get({ useMasterKey: true }); 33 | assert.equal(config.get('internal'), 'i'); 34 | assert.equal(config.get('string'), 's'); 35 | }); 36 | 37 | it('cannot get internal config parameter without masterkey', async () => { 38 | await testConfig(); 39 | 40 | const config = await Parse.Config.get(); 41 | assert.equal(config.get('internal'), undefined); 42 | assert.equal(config.get('string'), 's'); 43 | }); 44 | }); 45 | -------------------------------------------------------------------------------- /integration/test/ParseDistTest.js: -------------------------------------------------------------------------------- 1 | const puppeteer = require('puppeteer'); 2 | const { resolvingPromise } = require('../../lib/node/promiseUtils'); 3 | 4 | let browser = null; 5 | let page = null; 6 | for (const fileName of ['parse.js', 'parse.min.js']) { 7 | describe(`Parse Dist Test ${fileName}`, () => { 8 | beforeEach(async () => { 9 | browser = await puppeteer.launch({ 10 | args: ['--disable-web-security', '--incognito', '--no-sandbox'], 11 | devtools: false, 12 | }); 13 | const context = await browser.createBrowserContext(); 14 | page = await context.newPage(); 15 | await page.setCacheEnabled(false); 16 | await page.goto(`http://localhost:1337/${fileName}`); 17 | }); 18 | 19 | afterEach(async () => { 20 | await page.close(); 21 | await browser.close(); 22 | }); 23 | 24 | it('can save an object', async () => { 25 | const objectId = await page.evaluate(async () => { 26 | const object = await new Parse.Object('TestObject').save(); 27 | return object.id; 28 | }); 29 | expect(objectId).toBeDefined(); 30 | const obj = await new Parse.Query('TestObject').get(objectId); 31 | expect(obj).toBeDefined(); 32 | expect(obj.id).toEqual(objectId); 33 | }); 34 | 35 | it('can query an object', async () => { 36 | const obj = await new Parse.Object('TestObjects').save(); 37 | const response = await page.evaluate(async () => { 38 | const object = await new Parse.Query('TestObjects').first(); 39 | return object.id; 40 | }); 41 | expect(response).toBeDefined(); 42 | expect(obj).toBeDefined(); 43 | expect(obj.id).toEqual(response); 44 | }); 45 | 46 | it('can cancel save file', async () => { 47 | let requestsCount = 0; 48 | let abortedCount = 0; 49 | const promise = resolvingPromise(); 50 | await page.setRequestInterception(true); 51 | page.on('request', request => { 52 | if (!request.url().includes('favicon.ico')) { 53 | requestsCount += 1; 54 | } 55 | request.continue(); 56 | }); 57 | page.on('requestfailed', request => { 58 | if ( 59 | request.failure().errorText === 'net::ERR_ABORTED' && 60 | !request.url().includes('favicon.ico') 61 | ) { 62 | abortedCount += 1; 63 | promise.resolve(); 64 | } 65 | }); 66 | await page.evaluate(async () => { 67 | const SIZE_10_MB = 10 * 1024 * 1024; 68 | const file = new Parse.File('test_file.txt', new Uint8Array(SIZE_10_MB)); 69 | file.save().then(() => { 70 | fail('should not save'); 71 | }); 72 | return new Promise(resolve => { 73 | const intervalId = setInterval(() => { 74 | if (file._requestTask && typeof file._requestTask.abort === 'function') { 75 | file.cancel(); 76 | clearInterval(intervalId); 77 | resolve(); 78 | } 79 | }, 1); 80 | }); 81 | }); 82 | await promise; 83 | expect(requestsCount).toBe(1); 84 | expect(abortedCount).toBe(1); 85 | }); 86 | }); 87 | } 88 | -------------------------------------------------------------------------------- /integration/test/ParseLegacyTest.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const { Parse } = require('../../node'); 4 | 5 | describe('Parse Legacy Import', () => { 6 | it('can query object', async () => { 7 | const object = new Parse.Object('TestObject'); 8 | object.set('foo', 'bar'); 9 | await object.save(); 10 | const query = new Parse.Query('TestObject'); 11 | const result = await query.get(object.id); 12 | expect(result.id).toBe(object.id); 13 | }); 14 | }); 15 | -------------------------------------------------------------------------------- /integration/test/ParseMasterKeyTest.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const assert = require('assert'); 4 | const Parse = require('../../node'); 5 | 6 | describe('Master Key', () => { 7 | it('can perform a simple save', done => { 8 | const object = new TestObject(); 9 | object.set('color', 'purple'); 10 | object.save(null, { useMasterKey: true }).then(() => { 11 | assert(object.id); 12 | done(); 13 | }); 14 | }); 15 | 16 | it('can perform a save without permissions', async () => { 17 | const user = await Parse.User.signUp('andrew', 'password'); 18 | const object = new TestObject({ ACL: new Parse.ACL(user) }); 19 | await object.save(); 20 | 21 | await Parse.User.logOut(); 22 | await object.save(null, { useMasterKey: true }); 23 | }); 24 | 25 | it('throws when no master key is provided', done => { 26 | Parse.CoreManager.set('MASTER_KEY', null); 27 | const object = new TestObject(); 28 | object.save(null, { useMasterKey: true }).catch(() => { 29 | // should fail 30 | Parse.CoreManager.set('MASTER_KEY', 'notsosecret'); 31 | done(); 32 | }); 33 | }); 34 | }); 35 | -------------------------------------------------------------------------------- /integration/test/ParsePushTest.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const Parse = require('../../node'); 4 | 5 | describe('Parse Push', () => { 6 | it('can get pushStatusId', async () => { 7 | const payload = { 8 | data: { alert: 'We return status!' }, 9 | where: { deviceType: { $eq: 'random' } }, 10 | }; 11 | const pushStatusId = await Parse.Push.send(payload, { useMasterKey: true }); 12 | expect(pushStatusId).toBeDefined(); 13 | const pushStatus = await Parse.Push.getPushStatus(pushStatusId, { useMasterKey: true }); 14 | expect(pushStatus.id).toBe(pushStatusId); 15 | }); 16 | }); 17 | -------------------------------------------------------------------------------- /integration/test/ParseServerTest.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const http = require('http'); 4 | const Parse = require('../../node'); 5 | 6 | describe('ParseServer', () => { 7 | it('can reconfigure server', async () => { 8 | let parseServer = await reconfigureServer({ serverURL: 'www.google.com' }); 9 | expect(parseServer.config.serverURL).toBe('www.google.com'); 10 | 11 | await shutdownServer(parseServer); 12 | 13 | parseServer = await reconfigureServer(); 14 | expect(parseServer.config.serverURL).toBe('http://localhost:1337/parse'); 15 | }); 16 | 17 | it('can shutdown', async () => { 18 | let close = 0; 19 | const parseServer = await reconfigureServer(); 20 | parseServer.server.on('close', () => { 21 | close += 1; 22 | }); 23 | const object = new TestObject({ foo: 'bar' }); 24 | // Open a connection to the server 25 | const query = new Parse.Query(TestObject); 26 | await query.subscribe(); 27 | expect(openConnections.size > 0).toBeTruthy(); 28 | 29 | await shutdownServer(parseServer); 30 | expect(close).toBe(1); 31 | expect(openConnections.size).toBe(0); 32 | 33 | await expectAsync(object.save()).toBeRejectedWithError( 34 | 'XMLHttpRequest failed: "Unable to connect to the Parse API"' 35 | ); 36 | await reconfigureServer({}); 37 | await object.save(); 38 | expect(object.id).toBeDefined(); 39 | }); 40 | 41 | it('can forward redirect', async () => { 42 | const serverURL = Parse.serverURL; 43 | const redirectServer = http.createServer(function(_, res) { 44 | res.writeHead(301, { Location: serverURL }); 45 | res.end(); 46 | }).listen(8080); 47 | Parse.CoreManager.set('SERVER_URL', 'http://localhost:8080/api'); 48 | const object = new TestObject({ foo: 'bar' }); 49 | await object.save(); 50 | const query = new Parse.Query(TestObject); 51 | const result = await query.get(object.id); 52 | expect(result.id).toBe(object.id); 53 | expect(result.get('foo')).toBe('bar'); 54 | Parse.serverURL = serverURL; 55 | redirectServer.close(); 56 | }); 57 | }); 58 | -------------------------------------------------------------------------------- /integration/test/cloud/main.js: -------------------------------------------------------------------------------- 1 | Parse.Cloud.define('bar', function (request) { 2 | if (request.params.key2 === 'value1') { 3 | return 'Foo'; 4 | } else { 5 | throw 'bad stuff happened'; 6 | } 7 | }); 8 | 9 | Parse.Cloud.define('TestFetchFromLocalDatastore', function (request) { 10 | const object = new Parse.Object('Item'); 11 | object.id = request.params.id; 12 | object.set('foo', 'changed'); 13 | return object.save(); 14 | }); 15 | 16 | Parse.Cloud.define('UpdateUser', function (request) { 17 | const user = new Parse.User(); 18 | user.id = request.params.id; 19 | user.set('foo', 'changed'); 20 | return user.save(null, { useMasterKey: true }); 21 | }); 22 | 23 | Parse.Cloud.define('CloudFunctionIdempotency', function () { 24 | const object = new Parse.Object('IdempotencyItem'); 25 | return object.save(null, { useMasterKey: true }); 26 | }); 27 | 28 | Parse.Cloud.define('CloudFunctionUndefined', function () { 29 | return undefined; 30 | }); 31 | 32 | Parse.Cloud.job('CloudJob1', function () { 33 | return { 34 | status: 'cloud job completed', 35 | }; 36 | }); 37 | 38 | Parse.Cloud.job('CloudJob2', function () { 39 | return new Promise(resolve => { 40 | setTimeout(function () { 41 | resolve({ 42 | status: 'cloud job completed', 43 | }); 44 | }, 1000); 45 | }); 46 | }); 47 | 48 | Parse.Cloud.job('CloudJobParamsInMessage', request => { 49 | request.message(JSON.stringify(request.params)); 50 | }); 51 | 52 | Parse.Cloud.job('CloudJobFailing', function () { 53 | throw 'cloud job failed'; 54 | }); 55 | -------------------------------------------------------------------------------- /integration/test/mockLocalStorage.js: -------------------------------------------------------------------------------- 1 | let mockStorage = {}; 2 | const mockLocalStorage = { 3 | getItem(path) { 4 | return mockStorage[path] || null; 5 | }, 6 | 7 | setItem(path, value) { 8 | mockStorage[path] = value; 9 | }, 10 | 11 | removeItem(path) { 12 | delete mockStorage[path]; 13 | }, 14 | 15 | get length() { 16 | return Object.keys(mockStorage).length; 17 | }, 18 | 19 | key: i => { 20 | const keys = Object.keys(mockStorage); 21 | return keys[i] || null; 22 | }, 23 | 24 | clear() { 25 | mockStorage = {}; 26 | }, 27 | }; 28 | 29 | module.exports = mockLocalStorage; 30 | -------------------------------------------------------------------------------- /integration/test/mockRNStorage.js: -------------------------------------------------------------------------------- 1 | let mockStorage = {}; 2 | const mockRNStorage = { 3 | getItem(path, cb) { 4 | cb(undefined, mockStorage[path] || null); 5 | }, 6 | 7 | setItem(path, value, cb) { 8 | mockStorage[path] = value; 9 | cb(); 10 | }, 11 | 12 | removeItem(path, cb) { 13 | delete mockStorage[path]; 14 | cb(); 15 | }, 16 | 17 | getAllKeys(cb) { 18 | cb(undefined, Object.keys(mockStorage)); 19 | }, 20 | 21 | multiGet(keys, cb) { 22 | const objects = keys.map(key => [key, mockStorage[key]]); 23 | cb(undefined, objects); 24 | }, 25 | 26 | multiRemove(keys, cb) { 27 | keys.map(key => delete mockStorage[key]); 28 | cb(undefined); 29 | }, 30 | 31 | clear() { 32 | mockStorage = {}; 33 | }, 34 | }; 35 | 36 | module.exports = mockRNStorage; 37 | -------------------------------------------------------------------------------- /integration/test/sleep.js: -------------------------------------------------------------------------------- 1 | module.exports = function (ms) { 2 | return new Promise(function (resolve) { 3 | setTimeout(resolve, ms); 4 | }); 5 | }; 6 | -------------------------------------------------------------------------------- /integration/test/support/MockEmailAdapterWithOptions.js: -------------------------------------------------------------------------------- 1 | module.exports = options => { 2 | if (!options) { 3 | throw 'Options were not provided'; 4 | } 5 | const adapter = { 6 | sendVerificationEmail: () => Promise.resolve(), 7 | sendPasswordResetEmail: () => Promise.resolve(), 8 | sendMail: () => Promise.resolve(), 9 | }; 10 | if (options.sendMail) { 11 | adapter.sendMail = options.sendMail; 12 | } 13 | if (options.sendPasswordResetEmail) { 14 | adapter.sendPasswordResetEmail = options.sendPasswordResetEmail; 15 | } 16 | if (options.sendVerificationEmail) { 17 | adapter.sendVerificationEmail = options.sendVerificationEmail; 18 | } 19 | 20 | return adapter; 21 | }; 22 | -------------------------------------------------------------------------------- /jasmine.json: -------------------------------------------------------------------------------- 1 | { 2 | "spec_dir": "integration/test", 3 | "helpers": [ 4 | "helper.js" 5 | ], 6 | "spec_files": [ 7 | "*Test.js" 8 | ], 9 | "random": true 10 | } 11 | -------------------------------------------------------------------------------- /jsdoc-conf.json: -------------------------------------------------------------------------------- 1 | { 2 | "plugins": ["node_modules/jsdoc-babel", "plugins/markdown"], 3 | "babel": { 4 | "babelrc": false, 5 | "extensions": ["js", "ts", "jsx", "tsx"], 6 | "presets": ["@babel/preset-typescript"] 7 | }, 8 | "source": { 9 | "include": ["./README.md"], 10 | "includePattern": "\\.(jsx|js|ts|tsx)$", 11 | "excludePattern": "(^|\\/|\\\\)_" 12 | }, 13 | "templates": { 14 | "default": { 15 | "outputSourceFiles": false 16 | }, 17 | "cleverLinks": false, 18 | "monospaceLinks": false 19 | }, 20 | "opts": { 21 | "recurse": true, 22 | "template": "node_modules/@parse/minami", 23 | "showInheritedInNav": true, 24 | "useLongnameInNav": true 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /node.js: -------------------------------------------------------------------------------- 1 | module.exports = require('./lib/node/Parse.js').default; 2 | -------------------------------------------------------------------------------- /react-native.js: -------------------------------------------------------------------------------- 1 | module.exports = require('./lib/react-native/Parse.js').default; 2 | -------------------------------------------------------------------------------- /release_docs.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh -e 2 | set -x 3 | # GITHUB_ACTIONS=true SOURCE_TAG=test ./release_docs.sh 4 | 5 | if [ "${GITHUB_ACTIONS}" = "" ]; 6 | then 7 | echo "Cannot release docs without GITHUB_ACTIONS set" 8 | exit 0; 9 | fi 10 | if [ "${SOURCE_TAG}" = "" ]; 11 | then 12 | echo "Cannot release docs without SOURCE_TAG set" 13 | exit 0; 14 | fi 15 | REPO="https://github.com/parse-community/Parse-SDK-JS" 16 | 17 | rm -rf docs 18 | git clone -b gh-pages --single-branch $REPO ./docs 19 | cd docs 20 | git pull origin gh-pages 21 | cd .. 22 | 23 | RELEASE="release" 24 | VERSION="${SOURCE_TAG}" 25 | 26 | # change the default page to the latest 27 | echo "" > "docs/api/index.html" 28 | 29 | npm run docs 30 | 31 | mkdir -p "docs/api/${RELEASE}" 32 | cp -R out/* "docs/api/${RELEASE}" 33 | 34 | mkdir -p "docs/api/${VERSION}" 35 | cp -R out/* "docs/api/${VERSION}" 36 | -------------------------------------------------------------------------------- /src/Analytics.ts: -------------------------------------------------------------------------------- 1 | import CoreManager from './CoreManager'; 2 | 3 | /** 4 | * Parse.Analytics provides an interface to Parse's logging and analytics 5 | * backend. 6 | * 7 | * @class Parse.Analytics 8 | * @static 9 | * @hideconstructor 10 | */ 11 | 12 | /** 13 | * Tracks the occurrence of a custom event with additional dimensions. 14 | * Parse will store a data point at the time of invocation with the given 15 | * event name. 16 | * 17 | * Dimensions will allow segmentation of the occurrences of this custom 18 | * event. Keys and values should be {@code String}s, and will throw 19 | * otherwise. 20 | * 21 | * To track a user signup along with additional metadata, consider the 22 | * following: 23 | *
24 |  * var dimensions = {
25 |  *  gender: 'm',
26 |  *  source: 'web',
27 |  *  dayType: 'weekend'
28 |  * };
29 |  * Parse.Analytics.track('signup', dimensions);
30 |  * 
31 | * 32 | * There is a default limit of 8 dimensions per event tracked. 33 | * 34 | * @function track 35 | * @name Parse.Analytics.track 36 | * @param {string} name The name of the custom event to report to Parse as 37 | * having happened. 38 | * @param {object} dimensions The dictionary of information by which to 39 | * segment this event. 40 | * @returns {Promise} A promise that is resolved when the round-trip 41 | * to the server completes. 42 | */ 43 | export function track(name: string, dimensions: Record): Promise { 44 | name = name || ''; 45 | name = name.replace(/^\s*/, ''); 46 | name = name.replace(/\s*$/, ''); 47 | if (name.length === 0) { 48 | throw new TypeError('A name for the custom event must be provided'); 49 | } 50 | 51 | for (const key in dimensions) { 52 | if (typeof key !== 'string' || typeof dimensions[key] !== 'string') { 53 | throw new TypeError('track() dimensions expects keys and values of type "string".'); 54 | } 55 | } 56 | 57 | return CoreManager.getAnalyticsController().track(name, dimensions); 58 | } 59 | 60 | const DefaultController = { 61 | track(name: string, dimensions: Record) { 62 | const path = 'events/' + name; 63 | const RESTController = CoreManager.getRESTController(); 64 | return RESTController.request('POST', path, { dimensions }); 65 | }, 66 | }; 67 | 68 | CoreManager.setAnalyticsController(DefaultController); 69 | -------------------------------------------------------------------------------- /src/AnonymousUtils.ts: -------------------------------------------------------------------------------- 1 | import ParseUser from './ParseUser'; 2 | import type { RequestOptions } from './RESTController'; 3 | import uuidv4 from './uuid'; 4 | 5 | let registered = false; 6 | 7 | /** 8 | * Provides utility functions for working with Anonymously logged-in users.
9 | * Anonymous users have some unique characteristics: 10 | *
    11 | *
  • Anonymous users don't need a user name or password.
  • 12 | *
      13 | *
    • Once logged out, an anonymous user cannot be recovered.
    • 14 | *
    15 | *
  • signUp converts an anonymous user to a standard user with the given username and password.
  • 16 | *
      17 | *
    • Data associated with the anonymous user is retained.
    • 18 | *
    19 | *
  • logIn switches users without converting the anonymous user.
  • 20 | *
      21 | *
    • Data associated with the anonymous user will be lost.
    • 22 | *
    23 | *
  • Service logIn (e.g. Facebook, Twitter) will attempt to convert 24 | * the anonymous user into a standard user by linking it to the service.
  • 25 | *
      26 | *
    • If a user already exists that is linked to the service, it will instead switch to the existing user.
    • 27 | *
    28 | *
  • Service linking (e.g. Facebook, Twitter) will convert the anonymous user 29 | * into a standard user by linking it to the service.
  • 30 | *
31 | * 32 | * @class Parse.AnonymousUtils 33 | * @static 34 | */ 35 | const AnonymousUtils = { 36 | /** 37 | * Gets whether the user has their account linked to anonymous user. 38 | * 39 | * @function isLinked 40 | * @name Parse.AnonymousUtils.isLinked 41 | * @param {Parse.User} user User to check for. 42 | * The user must be logged in on this device. 43 | * @returns {boolean} true if the user has their account 44 | * linked to an anonymous user. 45 | * @static 46 | */ 47 | isLinked(user: ParseUser): boolean { 48 | const provider = this._getAuthProvider(); 49 | return user._isLinked(provider.getAuthType()); 50 | }, 51 | 52 | /** 53 | * Logs in a user Anonymously. 54 | * 55 | * @function logIn 56 | * @name Parse.AnonymousUtils.logIn 57 | * @param {object} options MasterKey / SessionToken. 58 | * @returns {Promise} Logged in user 59 | * @static 60 | */ 61 | logIn(options?: RequestOptions): Promise { 62 | const provider = this._getAuthProvider(); 63 | return ParseUser.logInWith(provider.getAuthType(), provider.getAuthData(), options); 64 | }, 65 | 66 | /** 67 | * Links Anonymous User to an existing PFUser. 68 | * 69 | * @function link 70 | * @name Parse.AnonymousUtils.link 71 | * @param {Parse.User} user User to link. This must be the current user. 72 | * @param {object} options MasterKey / SessionToken. 73 | * @returns {Promise} Linked with User 74 | * @static 75 | */ 76 | link(user: ParseUser, options?: RequestOptions): Promise { 77 | const provider = this._getAuthProvider(); 78 | return user.linkWith(provider.getAuthType(), provider.getAuthData(), options); 79 | }, 80 | 81 | /** 82 | * Returns true if Authentication Provider has been registered for use. 83 | * 84 | * @function isRegistered 85 | * @name Parse.AnonymousUtils.isRegistered 86 | * @returns {boolean} 87 | * @static 88 | */ 89 | isRegistered(): boolean { 90 | return registered; 91 | }, 92 | 93 | _getAuthProvider() { 94 | const provider = { 95 | restoreAuthentication() { 96 | return true; 97 | }, 98 | 99 | getAuthType() { 100 | return 'anonymous'; 101 | }, 102 | 103 | getAuthData() { 104 | return { 105 | authData: { 106 | id: uuidv4(), 107 | }, 108 | }; 109 | }, 110 | }; 111 | if (!registered) { 112 | ParseUser._registerAuthenticationProvider(provider); 113 | registered = true; 114 | } 115 | return provider; 116 | }, 117 | }; 118 | 119 | export default AnonymousUtils; 120 | -------------------------------------------------------------------------------- /src/CryptoController.ts: -------------------------------------------------------------------------------- 1 | let AES: any; 2 | let ENC: any; 3 | 4 | if (process.env.PARSE_BUILD === 'react-native') { 5 | const CryptoJS = require('react-native-crypto-js'); 6 | AES = CryptoJS.AES; 7 | ENC = CryptoJS.enc.Utf8; 8 | } else { 9 | AES = require('crypto-js/aes'); 10 | ENC = require('crypto-js/enc-utf8'); 11 | } 12 | 13 | const CryptoController = { 14 | encrypt(obj: any, secretKey: string): string { 15 | const encrypted = AES.encrypt(JSON.stringify(obj), secretKey); 16 | return encrypted.toString(); 17 | }, 18 | 19 | decrypt(encryptedText: string, secretKey: string): string { 20 | const decryptedStr = AES.decrypt(encryptedText, secretKey).toString(ENC); 21 | return decryptedStr; 22 | }, 23 | }; 24 | 25 | export default CryptoController; 26 | -------------------------------------------------------------------------------- /src/EventEmitter.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This is a simple wrapper to unify EventEmitter implementations across platforms. 3 | */ 4 | 5 | let EventEmitter: any; 6 | 7 | try { 8 | if (process.env.PARSE_BUILD === 'react-native') { 9 | EventEmitter = require('react-native/Libraries/vendor/emitter/EventEmitter'); 10 | if (EventEmitter.default) { 11 | EventEmitter = EventEmitter.default; 12 | } 13 | EventEmitter.prototype.on = EventEmitter.prototype.addListener; 14 | } else { 15 | EventEmitter = require('events').EventEmitter; 16 | } 17 | } catch (_) { 18 | // EventEmitter unavailable 19 | } 20 | 21 | export default EventEmitter; 22 | -------------------------------------------------------------------------------- /src/IndexedDBStorageController.ts: -------------------------------------------------------------------------------- 1 | /* global window */ 2 | 3 | import { createStore, del, set, get, clear, keys } from 'idb-keyval'; 4 | 5 | let IndexedDBStorageController: any; 6 | 7 | if (typeof window !== 'undefined' && window.indexedDB) { 8 | try { 9 | const ParseStore = createStore('parseDB', 'parseStore'); 10 | 11 | IndexedDBStorageController = { 12 | async: 1, 13 | getItemAsync(path: string) { 14 | return get(path, ParseStore); 15 | }, 16 | setItemAsync(path: string, value: string) { 17 | return set(path, value, ParseStore); 18 | }, 19 | removeItemAsync(path: string) { 20 | return del(path, ParseStore); 21 | }, 22 | getAllKeysAsync() { 23 | return keys(ParseStore); 24 | }, 25 | clear() { 26 | return clear(ParseStore); 27 | }, 28 | }; 29 | } catch (_) { 30 | // IndexedDB not accessible 31 | IndexedDBStorageController = undefined; 32 | } 33 | } else { 34 | // IndexedDB not supported 35 | IndexedDBStorageController = undefined; 36 | } 37 | 38 | export default IndexedDBStorageController; 39 | -------------------------------------------------------------------------------- /src/InstallationController.ts: -------------------------------------------------------------------------------- 1 | import CoreManager from './CoreManager'; 2 | import Storage from './Storage'; 3 | import ParseInstallation from './ParseInstallation'; 4 | import uuidv4 from './uuid'; 5 | 6 | const CURRENT_INSTALLATION_KEY = 'currentInstallation'; 7 | const CURRENT_INSTALLATION_ID_KEY = 'currentInstallationId'; 8 | 9 | let iidCache: string | null = null; 10 | let currentInstallationCache: ParseInstallation | null = null; 11 | let currentInstallationCacheMatchesDisk = false; 12 | 13 | const InstallationController = { 14 | async updateInstallationOnDisk(installation: ParseInstallation): Promise { 15 | const path = Storage.generatePath(CURRENT_INSTALLATION_KEY); 16 | await Storage.setItemAsync(path, JSON.stringify(installation.toJSON())); 17 | this._setCurrentInstallationCache(installation); 18 | }, 19 | 20 | async currentInstallationId(): Promise { 21 | if (typeof iidCache === 'string') { 22 | return iidCache; 23 | } 24 | const path = Storage.generatePath(CURRENT_INSTALLATION_ID_KEY); 25 | let iid = await Storage.getItemAsync(path); 26 | if (!iid) { 27 | iid = uuidv4(); 28 | return Storage.setItemAsync(path, iid).then(() => { 29 | iidCache = iid; 30 | return iid; 31 | }); 32 | } 33 | iidCache = iid; 34 | return iid; 35 | }, 36 | 37 | async currentInstallation(): Promise { 38 | if (currentInstallationCache) { 39 | return currentInstallationCache; 40 | } 41 | if (currentInstallationCacheMatchesDisk) { 42 | return null; 43 | } 44 | const path = Storage.generatePath(CURRENT_INSTALLATION_KEY); 45 | let installationData: any = await Storage.getItemAsync(path); 46 | currentInstallationCacheMatchesDisk = true; 47 | if (installationData) { 48 | installationData = JSON.parse(installationData); 49 | installationData.className = '_Installation'; 50 | const current = ParseInstallation.fromJSON(installationData) as ParseInstallation; 51 | currentInstallationCache = current; 52 | return current as ParseInstallation; 53 | } 54 | const installationId = await this.currentInstallationId(); 55 | const installation = new ParseInstallation(); 56 | installation.set('deviceType', ParseInstallation.DEVICE_TYPES.WEB); 57 | installation.set('installationId', installationId); 58 | installation.set('parseVersion', CoreManager.get('VERSION')); 59 | currentInstallationCache = installation; 60 | await Storage.setItemAsync(path, JSON.stringify(installation.toJSON())); 61 | return installation; 62 | }, 63 | 64 | _clearCache() { 65 | iidCache = null; 66 | currentInstallationCache = null; 67 | currentInstallationCacheMatchesDisk = false; 68 | }, 69 | 70 | _setInstallationIdCache(iid: string) { 71 | iidCache = iid; 72 | }, 73 | 74 | _setCurrentInstallationCache(installation: ParseInstallation, matchesDisk = true) { 75 | currentInstallationCache = installation; 76 | currentInstallationCacheMatchesDisk = matchesDisk; 77 | }, 78 | }; 79 | 80 | export default InstallationController; 81 | -------------------------------------------------------------------------------- /src/LocalDatastoreController.default.ts: -------------------------------------------------------------------------------- 1 | import { isLocalDatastoreKey } from './LocalDatastoreUtils'; 2 | import Storage from './Storage'; 3 | 4 | const LocalDatastoreController = { 5 | async fromPinWithName(name: string): Promise { 6 | const values = await Storage.getItemAsync(name); 7 | if (!values) { 8 | return []; 9 | } 10 | const objects = JSON.parse(values); 11 | return objects; 12 | }, 13 | 14 | pinWithName(name: string, value: any) { 15 | const values = JSON.stringify(value); 16 | return Storage.setItemAsync(name, values); 17 | }, 18 | 19 | unPinWithName(name: string) { 20 | return Storage.removeItemAsync(name); 21 | }, 22 | 23 | async getAllContents(): Promise { 24 | const keys = await Storage.getAllKeysAsync(); 25 | return keys.reduce(async (previousPromise, key) => { 26 | const LDS = await previousPromise; 27 | if (isLocalDatastoreKey(key)) { 28 | const value = await Storage.getItemAsync(key); 29 | try { 30 | LDS[key] = JSON.parse(value); 31 | } catch (error) { 32 | console.error('Error getAllContents: ', error); 33 | } 34 | } 35 | return LDS; 36 | }, Promise.resolve({})); 37 | }, 38 | 39 | // Used for testing 40 | async getRawStorage(): Promise { 41 | const keys = await Storage.getAllKeysAsync(); 42 | return keys.reduce(async (previousPromise, key) => { 43 | const LDS = await previousPromise; 44 | const value = await Storage.getItemAsync(key); 45 | LDS[key] = value; 46 | return LDS; 47 | }, Promise.resolve({})); 48 | }, 49 | 50 | async clear(): Promise { 51 | const keys = await Storage.getAllKeysAsync(); 52 | 53 | const toRemove: string[] = []; 54 | for (const key of keys) { 55 | if (isLocalDatastoreKey(key)) { 56 | toRemove.push(key); 57 | } 58 | } 59 | const promises = toRemove.map(this.unPinWithName); 60 | return Promise.all(promises); 61 | }, 62 | }; 63 | 64 | export default LocalDatastoreController; 65 | -------------------------------------------------------------------------------- /src/LocalDatastoreController.react-native.ts: -------------------------------------------------------------------------------- 1 | import { isLocalDatastoreKey } from './LocalDatastoreUtils'; 2 | import RNStorage from './StorageController.react-native'; 3 | 4 | const LocalDatastoreController = { 5 | async fromPinWithName(name: string): Promise { 6 | const values = await RNStorage.getItemAsync(name); 7 | if (!values) { 8 | return []; 9 | } 10 | const objects = JSON.parse(values); 11 | return objects; 12 | }, 13 | 14 | async pinWithName(name: string, value: any): Promise { 15 | try { 16 | const values = JSON.stringify(value); 17 | await RNStorage.setItemAsync(name, values); 18 | } catch (e) { 19 | // Quota exceeded, possibly due to Safari Private Browsing mode 20 | console.error(e.message); 21 | } 22 | }, 23 | 24 | unPinWithName(name: string): Promise { 25 | return RNStorage.removeItemAsync(name); 26 | }, 27 | 28 | async getAllContents(): Promise { 29 | const keys = await RNStorage.getAllKeysAsync(); 30 | const batch: string[] = []; 31 | for (let i = 0; i < keys.length; i += 1) { 32 | const key = keys[i]; 33 | if (isLocalDatastoreKey(key)) { 34 | batch.push(key); 35 | } 36 | } 37 | const LDS = {}; 38 | let results: any = []; 39 | try { 40 | results = await RNStorage.multiGet(batch); 41 | } catch (error) { 42 | console.error('Error getAllContents: ', error); 43 | return {}; 44 | } 45 | results.forEach(pair => { 46 | const [key, value] = pair; 47 | try { 48 | LDS[key] = JSON.parse(value); 49 | } catch (_) { 50 | LDS[key] = null; 51 | } 52 | }); 53 | return LDS; 54 | }, 55 | 56 | // Used for testing 57 | async getRawStorage(): Promise { 58 | const keys = await RNStorage.getAllKeysAsync(); 59 | const storage = {}; 60 | const results = await RNStorage.multiGet(keys as string[]); 61 | results!.map(pair => { 62 | const [key, value] = pair; 63 | storage[key] = value; 64 | }); 65 | return storage; 66 | }, 67 | 68 | async clear(): Promise { 69 | const keys = await RNStorage.getAllKeysAsync(); 70 | const batch: string[] = []; 71 | for (let i = 0; i < keys.length; i += 1) { 72 | const key = keys[i]; 73 | if (isLocalDatastoreKey(key)) { 74 | batch.push(key); 75 | } 76 | } 77 | await RNStorage.multiRemove(batch).catch(error => 78 | console.error('Error clearing local datastore: ', error) 79 | ); 80 | }, 81 | }; 82 | 83 | export default LocalDatastoreController; 84 | -------------------------------------------------------------------------------- /src/LocalDatastoreController.ts: -------------------------------------------------------------------------------- 1 | import RNLocalDatastoreController from './LocalDatastoreController.react-native'; 2 | import DefaultLocalDatastoreController from './LocalDatastoreController.default'; 3 | 4 | let LocalDatastoreController: any = DefaultLocalDatastoreController; 5 | 6 | if (process.env.PARSE_BUILD === 'react-native') { 7 | LocalDatastoreController = RNLocalDatastoreController; 8 | } 9 | 10 | export default LocalDatastoreController; 11 | -------------------------------------------------------------------------------- /src/LocalDatastoreUtils.ts: -------------------------------------------------------------------------------- 1 | const DEFAULT_PIN = '_default'; 2 | const PIN_PREFIX = 'parsePin_'; 3 | const OBJECT_PREFIX = 'Parse_LDS_'; 4 | 5 | function isLocalDatastoreKey(key: string): boolean { 6 | return !!( 7 | key && 8 | (key === DEFAULT_PIN || key.startsWith(PIN_PREFIX) || key.startsWith(OBJECT_PREFIX)) 9 | ); 10 | } 11 | 12 | export { DEFAULT_PIN, PIN_PREFIX, OBJECT_PREFIX, isLocalDatastoreKey }; 13 | -------------------------------------------------------------------------------- /src/ParseSession.ts: -------------------------------------------------------------------------------- 1 | import CoreManager from './CoreManager'; 2 | import isRevocableSession from './isRevocableSession'; 3 | import ParseObject, { Attributes } from './ParseObject'; 4 | import ParseUser from './ParseUser'; 5 | 6 | import type { AttributeKey } from './ParseObject'; 7 | import type { RequestOptions, FullOptions } from './RESTController'; 8 | 9 | /** 10 | *

A Parse.Session object is a local representation of a revocable session. 11 | * This class is a subclass of a Parse.Object, and retains the same 12 | * functionality of a Parse.Object.

13 | * 14 | * @alias Parse.Session 15 | * @augments Parse.Object 16 | */ 17 | class ParseSession extends ParseObject { 18 | /** 19 | * @param {object} attributes The initial set of data to store in the user. 20 | */ 21 | constructor(attributes?: T) { 22 | super('_Session'); 23 | if (attributes && typeof attributes === 'object') { 24 | try { 25 | this.set((attributes || {}) as any); 26 | } catch (_) { 27 | throw new Error("Can't create an invalid Session"); 28 | } 29 | } 30 | } 31 | 32 | /** 33 | * Returns the session token string. 34 | * 35 | * @returns {string} 36 | */ 37 | getSessionToken(): string { 38 | const token = this.get('sessionToken' as AttributeKey); 39 | if (typeof token === 'string') { 40 | return token; 41 | } 42 | return ''; 43 | } 44 | 45 | static readOnlyAttributes(): string[] { 46 | return ['createdWith', 'expiresAt', 'installationId', 'restricted', 'sessionToken', 'user']; 47 | } 48 | 49 | /** 50 | * Retrieves the Session object for the currently logged in session. 51 | * 52 | * @param {object} options useMasterKey 53 | * @static 54 | * @returns {Promise} A promise that is resolved with the Parse.Session 55 | * object after it has been fetched. If there is no current user, the 56 | * promise will be rejected. 57 | */ 58 | static current(options?: FullOptions): Promise { 59 | const controller = CoreManager.getSessionController(); 60 | const sessionOptions = ParseObject._getRequestOptions(options); 61 | 62 | return ParseUser.currentAsync().then(user => { 63 | if (!user) { 64 | return Promise.reject('There is no current user.'); 65 | } 66 | sessionOptions.sessionToken = user.getSessionToken(); 67 | return controller.getSession(sessionOptions) as Promise; 68 | }); 69 | } 70 | 71 | /** 72 | * Determines whether the current session token is revocable. 73 | * This method is useful for migrating Express.js or Node.js web apps to 74 | * use revocable sessions. If you are migrating an app that uses the Parse 75 | * SDK in the browser only, please use Parse.User.enableRevocableSession() 76 | * instead, so that sessions can be automatically upgraded. 77 | * 78 | * @static 79 | * @returns {boolean} 80 | */ 81 | static isCurrentSessionRevocable(): boolean { 82 | const currentUser = ParseUser.current(); 83 | if (currentUser) { 84 | return isRevocableSession(currentUser.getSessionToken() || ''); 85 | } 86 | return false; 87 | } 88 | } 89 | 90 | ParseObject.registerSubclass('_Session', ParseSession); 91 | 92 | const DefaultController = { 93 | getSession(options?: RequestOptions): Promise { 94 | const RESTController = CoreManager.getRESTController(); 95 | const session = new ParseSession(); 96 | 97 | return RESTController.request('GET', 'sessions/me', {}, options).then(sessionData => { 98 | session._finishFetch(sessionData); 99 | session._setExisted(true); 100 | return session; 101 | }); 102 | }, 103 | }; 104 | 105 | CoreManager.setSessionController(DefaultController); 106 | 107 | export default ParseSession; 108 | -------------------------------------------------------------------------------- /src/Push.ts: -------------------------------------------------------------------------------- 1 | import CoreManager from './CoreManager'; 2 | import ParseQuery from './ParseQuery'; 3 | 4 | import type ParseObject from './ParseObject'; 5 | import type { WhereClause } from './ParseQuery'; 6 | import type { FullOptions } from './RESTController'; 7 | 8 | export interface PushData { 9 | where?: WhereClause | ParseQuery; 10 | push_time?: Date | string; 11 | expiration_time?: Date | string; 12 | expiration_interval?: number; 13 | data?: any; 14 | channels?: string[]; 15 | } 16 | 17 | /** 18 | * Contains functions to deal with Push in Parse. 19 | * 20 | * @class Parse.Push 21 | * @static 22 | * @hideconstructor 23 | */ 24 | 25 | /** 26 | * Sends a push notification. 27 | * **Available in Cloud Code only.** 28 | * 29 | * See {@link https://docs.parseplatform.org/js/guide/#push-notifications Push Notification Guide} 30 | * 31 | * @function send 32 | * @name Parse.Push.send 33 | * @param {object} data - The data of the push notification. Valid fields 34 | * are: 35 | *
    36 | *
  1. channels - An Array of channels to push to.
  2. 37 | *
  3. push_time - A Date object for when to send the push.
  4. 38 | *
  5. expiration_time - A Date object for when to expire 39 | * the push.
  6. 40 | *
  7. expiration_interval - The seconds from now to expire the push.
  8. 41 | *
  9. where - A Parse.Query over Parse.Installation that is used to match 42 | * a set of installations to push to.
  10. 43 | *
  11. data - The data to send as part of the push.
  12. 44 | *
      45 | * @param {object} options Valid options 46 | * are:
        47 | *
      • useMasterKey: In Cloud Code and Node only, causes the Master Key to 48 | * be used for this request. 49 | *
      50 | * @returns {Promise} A promise that is fulfilled when the push request 51 | * completes and returns `pushStatusId`. 52 | */ 53 | export function send(data: PushData, options: FullOptions = {}): Promise { 54 | if (data.where && data.where instanceof ParseQuery) { 55 | data.where = data.where.toJSON().where; 56 | } 57 | 58 | if (data.push_time && typeof data.push_time === 'object') { 59 | data.push_time = data.push_time.toJSON(); 60 | } 61 | 62 | if (data.expiration_time && typeof data.expiration_time === 'object') { 63 | data.expiration_time = data.expiration_time.toJSON(); 64 | } 65 | 66 | if (data.expiration_time && data.expiration_interval) { 67 | throw new Error('expiration_time and expiration_interval cannot both be set.'); 68 | } 69 | 70 | const pushOptions: FullOptions = { useMasterKey: true }; 71 | if (Object.hasOwn(options, 'useMasterKey')) { 72 | pushOptions.useMasterKey = options.useMasterKey; 73 | } 74 | 75 | return CoreManager.getPushController().send(data, pushOptions); 76 | } 77 | 78 | /** 79 | * Gets push status by Id 80 | * 81 | * @function getPushStatus 82 | * @name Parse.Push.getPushStatus 83 | * @param {string} pushStatusId The Id of Push Status. 84 | * @param {object} options Valid options 85 | * are:
        86 | *
      • useMasterKey: In Cloud Code and Node only, causes the Master Key to 87 | * be used for this request. 88 | *
      89 | * @returns {Parse.Object} Status of Push. 90 | */ 91 | export function getPushStatus( 92 | pushStatusId: string, 93 | options: FullOptions = {} 94 | ): Promise { 95 | const pushOptions: FullOptions = { useMasterKey: true }; 96 | if (Object.hasOwn(options, 'useMasterKey')) { 97 | pushOptions.useMasterKey = options.useMasterKey; 98 | } 99 | const query = new ParseQuery('_PushStatus'); 100 | return query.get(pushStatusId, pushOptions); 101 | } 102 | 103 | const DefaultController = { 104 | async send(data: PushData, options?: FullOptions & { returnStatus?: boolean }) { 105 | options!.returnStatus = true; 106 | const response = await CoreManager.getRESTController().request('POST', 'push', data, options); 107 | return response._headers?.['X-Parse-Push-Status-Id']; 108 | }, 109 | }; 110 | 111 | CoreManager.setPushController(DefaultController); 112 | -------------------------------------------------------------------------------- /src/SingleInstanceStateController.ts: -------------------------------------------------------------------------------- 1 | import * as ObjectStateMutations from './ObjectStateMutations'; 2 | 3 | import type { Op } from './ParseOp'; 4 | import type ParseObject from './ParseObject'; 5 | import type { AttributeMap, ObjectCache, OpsMap, State } from './ObjectStateMutations'; 6 | 7 | let objectState: Record> = {}; 8 | 9 | export function getState(obj: ParseObject): State | null { 10 | const classData = objectState[obj.className]; 11 | if (classData) { 12 | return classData[obj.id!] || null; 13 | } 14 | return null; 15 | } 16 | 17 | export function initializeState(obj: ParseObject, initial?: State): State { 18 | let state = getState(obj); 19 | if (state) { 20 | return state; 21 | } 22 | if (!objectState[obj.className]) { 23 | objectState[obj.className] = {}; 24 | } 25 | if (!initial) { 26 | initial = ObjectStateMutations.defaultState(); 27 | } 28 | state = objectState[obj.className][obj.id!] = initial; 29 | return state; 30 | } 31 | 32 | export function removeState(obj: ParseObject): State | null { 33 | const state = getState(obj); 34 | if (state === null) { 35 | return null; 36 | } 37 | delete objectState[obj.className][obj.id!]; 38 | return state; 39 | } 40 | 41 | export function getServerData(obj: ParseObject): AttributeMap { 42 | const state = getState(obj); 43 | if (state) { 44 | return state.serverData; 45 | } 46 | return {}; 47 | } 48 | 49 | export function setServerData(obj: ParseObject, attributes: AttributeMap) { 50 | const serverData = initializeState(obj).serverData; 51 | ObjectStateMutations.setServerData(serverData, attributes); 52 | } 53 | 54 | export function getPendingOps(obj: ParseObject): OpsMap[] { 55 | const state = getState(obj); 56 | if (state) { 57 | return state.pendingOps; 58 | } 59 | return [{}]; 60 | } 61 | 62 | export function setPendingOp(obj: ParseObject, attr: string, op?: Op) { 63 | const pendingOps = initializeState(obj).pendingOps; 64 | ObjectStateMutations.setPendingOp(pendingOps, attr, op); 65 | } 66 | 67 | export function pushPendingState(obj: ParseObject) { 68 | const pendingOps = initializeState(obj).pendingOps; 69 | ObjectStateMutations.pushPendingState(pendingOps); 70 | } 71 | 72 | export function popPendingState(obj: ParseObject): OpsMap { 73 | const pendingOps = initializeState(obj).pendingOps; 74 | return ObjectStateMutations.popPendingState(pendingOps); 75 | } 76 | 77 | export function mergeFirstPendingState(obj: ParseObject) { 78 | const pendingOps = getPendingOps(obj); 79 | ObjectStateMutations.mergeFirstPendingState(pendingOps); 80 | } 81 | 82 | export function getObjectCache(obj: ParseObject): ObjectCache { 83 | const state = getState(obj); 84 | if (state) { 85 | return state.objectCache; 86 | } 87 | return {}; 88 | } 89 | 90 | export function estimateAttribute(obj: ParseObject, attr: string): any { 91 | const serverData = getServerData(obj); 92 | const pendingOps = getPendingOps(obj); 93 | return ObjectStateMutations.estimateAttribute(serverData, pendingOps, obj, attr); 94 | } 95 | 96 | export function estimateAttributes(obj: ParseObject): AttributeMap { 97 | const serverData = getServerData(obj); 98 | const pendingOps = getPendingOps(obj); 99 | return ObjectStateMutations.estimateAttributes(serverData, pendingOps, obj); 100 | } 101 | 102 | export function commitServerChanges(obj: ParseObject, changes: AttributeMap) { 103 | const state = initializeState(obj); 104 | ObjectStateMutations.commitServerChanges(state.serverData, state.objectCache, changes); 105 | } 106 | 107 | export function enqueueTask(obj: ParseObject, task: () => Promise): Promise { 108 | const state = initializeState(obj); 109 | return state.tasks.enqueue(task); 110 | } 111 | 112 | export function clearAllState() { 113 | objectState = {}; 114 | } 115 | 116 | export function duplicateState(source: { id: string }, dest: { id: string }) { 117 | dest.id = source.id; 118 | } 119 | -------------------------------------------------------------------------------- /src/Socket.weapp.ts: -------------------------------------------------------------------------------- 1 | class SocketWeapp { 2 | onopen: () => void; 3 | onmessage: () => void; 4 | onclose: () => void; 5 | onerror: () => void; 6 | 7 | constructor(serverURL) { 8 | this.onopen = () => {}; 9 | this.onmessage = () => {}; 10 | this.onclose = () => {}; 11 | this.onerror = () => {}; 12 | 13 | // @ts-ignore 14 | wx.onSocketOpen(() => { 15 | this.onopen(); 16 | }); 17 | 18 | // @ts-ignore 19 | wx.onSocketMessage(msg => { 20 | // @ts-ignore 21 | this.onmessage(msg); 22 | }); 23 | 24 | // @ts-ignore 25 | wx.onSocketClose(event => { 26 | // @ts-ignore 27 | this.onclose(event); 28 | }); 29 | 30 | // @ts-ignore 31 | wx.onSocketError(error => { 32 | // @ts-ignore 33 | this.onerror(error); 34 | }); 35 | 36 | // @ts-ignore 37 | wx.connectSocket({ 38 | url: serverURL, 39 | }); 40 | } 41 | 42 | send(data) { 43 | // @ts-ignore 44 | wx.sendSocketMessage({ data }); 45 | } 46 | 47 | close() { 48 | // @ts-ignore 49 | wx.closeSocket(); 50 | } 51 | } 52 | 53 | export default SocketWeapp; 54 | -------------------------------------------------------------------------------- /src/Storage.ts: -------------------------------------------------------------------------------- 1 | import CoreManager from './CoreManager'; 2 | 3 | const Storage = { 4 | async(): boolean { 5 | const controller = CoreManager.getStorageController(); 6 | return !!controller.async; 7 | }, 8 | 9 | getItem(path: string): string | null { 10 | const controller = CoreManager.getStorageController(); 11 | if (controller.async === 1) { 12 | throw new Error('Synchronous storage is not supported by the current storage controller'); 13 | } 14 | return controller.getItem(path); 15 | }, 16 | 17 | getItemAsync(path: string): Promise { 18 | const controller = CoreManager.getStorageController(); 19 | if (controller.async === 1) { 20 | return controller.getItemAsync(path); 21 | } 22 | return Promise.resolve(controller.getItem(path)); 23 | }, 24 | 25 | setItem(path: string, value: string): void { 26 | const controller = CoreManager.getStorageController(); 27 | if (controller.async === 1) { 28 | throw new Error('Synchronous storage is not supported by the current storage controller'); 29 | } 30 | return controller.setItem(path, value); 31 | }, 32 | 33 | setItemAsync(path: string, value: string): Promise { 34 | const controller = CoreManager.getStorageController(); 35 | if (controller.async === 1) { 36 | return controller.setItemAsync(path, value); 37 | } 38 | return Promise.resolve(controller.setItem(path, value)); 39 | }, 40 | 41 | removeItem(path: string): void { 42 | const controller = CoreManager.getStorageController(); 43 | if (controller.async === 1) { 44 | throw new Error('Synchronous storage is not supported by the current storage controller'); 45 | } 46 | return controller.removeItem(path); 47 | }, 48 | 49 | removeItemAsync(path: string): Promise { 50 | const controller = CoreManager.getStorageController(); 51 | if (controller.async === 1) { 52 | return controller.removeItemAsync(path); 53 | } 54 | return Promise.resolve(controller.removeItem(path)); 55 | }, 56 | 57 | getAllKeys(): string[] { 58 | const controller = CoreManager.getStorageController(); 59 | if (controller.async === 1) { 60 | throw new Error('Synchronous storage is not supported by the current storage controller'); 61 | } 62 | return controller.getAllKeys!(); 63 | }, 64 | 65 | getAllKeysAsync(): Promise { 66 | const controller = CoreManager.getStorageController(); 67 | if (controller.async === 1) { 68 | return controller.getAllKeysAsync!(); 69 | } 70 | return Promise.resolve(controller.getAllKeys!()); 71 | }, 72 | 73 | generatePath(path: string): string { 74 | if (!CoreManager.get('APPLICATION_ID')) { 75 | throw new Error('You need to call Parse.initialize before using Parse.'); 76 | } 77 | if (typeof path !== 'string') { 78 | throw new Error('Tried to get a Storage path that was not a String.'); 79 | } 80 | if (path[0] === '/') { 81 | path = path.substr(1); 82 | } 83 | return 'Parse/' + CoreManager.get('APPLICATION_ID') + '/' + path; 84 | }, 85 | 86 | _clear() { 87 | const controller = CoreManager.getStorageController(); 88 | if (Object.hasOwn(controller, 'clear')) { 89 | controller.clear(); 90 | } 91 | }, 92 | }; 93 | 94 | export default Storage; 95 | -------------------------------------------------------------------------------- /src/StorageController.browser.ts: -------------------------------------------------------------------------------- 1 | /* global localStorage */ 2 | 3 | const StorageController = { 4 | async: 0, 5 | 6 | getItem(path: string): string | null { 7 | return localStorage.getItem(path); 8 | }, 9 | 10 | setItem(path: string, value: string) { 11 | try { 12 | localStorage.setItem(path, value); 13 | } catch (e) { 14 | // Quota exceeded, possibly due to Safari Private Browsing mode 15 | console.log(e.message); 16 | } 17 | }, 18 | 19 | removeItem(path: string) { 20 | localStorage.removeItem(path); 21 | }, 22 | 23 | getAllKeys() { 24 | const keys: string[] = []; 25 | for (let i = 0; i < localStorage.length; i += 1) { 26 | keys.push(localStorage.key(i) as string); 27 | } 28 | return keys; 29 | }, 30 | 31 | clear() { 32 | localStorage.clear(); 33 | }, 34 | }; 35 | 36 | export default StorageController; 37 | -------------------------------------------------------------------------------- /src/StorageController.default.ts: -------------------------------------------------------------------------------- 1 | // When there is no native storage interface, we default to an in-memory map 2 | const memMap = {}; 3 | const StorageController = { 4 | async: 0, 5 | 6 | getItem(path: string): string | null { 7 | if (Object.hasOwn(memMap, path)) { 8 | return memMap[path]; 9 | } 10 | return null; 11 | }, 12 | 13 | setItem(path: string, value: string) { 14 | memMap[path] = String(value); 15 | }, 16 | 17 | removeItem(path: string) { 18 | delete memMap[path]; 19 | }, 20 | 21 | getAllKeys() { 22 | return Object.keys(memMap); 23 | }, 24 | 25 | clear() { 26 | for (const key in memMap) { 27 | if (Object.hasOwn(memMap, key)) { 28 | delete memMap[key]; 29 | } 30 | } 31 | }, 32 | }; 33 | 34 | export default StorageController; 35 | -------------------------------------------------------------------------------- /src/StorageController.react-native.ts: -------------------------------------------------------------------------------- 1 | import CoreManager from './CoreManager'; 2 | 3 | const StorageController = { 4 | async: 1, 5 | 6 | getItemAsync(path: string): Promise { 7 | return new Promise((resolve, reject) => { 8 | CoreManager.getAsyncStorage()!.getItem(path, (err, value) => { 9 | if (err) { 10 | reject(err); 11 | } else { 12 | resolve(value || null); 13 | } 14 | }); 15 | }); 16 | }, 17 | 18 | setItemAsync(path: string, value: string): Promise { 19 | return new Promise((resolve, reject) => { 20 | CoreManager.getAsyncStorage()!.setItem(path, value, err => { 21 | if (err) { 22 | reject(err); 23 | } else { 24 | resolve(); 25 | } 26 | }); 27 | }); 28 | }, 29 | 30 | removeItemAsync(path: string): Promise { 31 | return new Promise((resolve, reject) => { 32 | CoreManager.getAsyncStorage()!.removeItem(path, err => { 33 | if (err) { 34 | reject(err); 35 | } else { 36 | resolve(); 37 | } 38 | }); 39 | }); 40 | }, 41 | 42 | getAllKeysAsync(): Promise { 43 | return new Promise((resolve, reject) => { 44 | CoreManager.getAsyncStorage()!.getAllKeys((err, keys) => { 45 | if (err) { 46 | reject(err); 47 | } else { 48 | resolve(keys || []); 49 | } 50 | }); 51 | }); 52 | }, 53 | 54 | multiGet(keys: string[]): Promise { 55 | return new Promise((resolve, reject) => { 56 | CoreManager.getAsyncStorage()!.multiGet(keys, (err, result) => { 57 | if (err) { 58 | reject(err); 59 | } else { 60 | resolve(result || null); 61 | } 62 | }); 63 | }); 64 | }, 65 | 66 | multiRemove(keys: string[]): Promise { 67 | return new Promise((resolve, reject) => { 68 | CoreManager.getAsyncStorage()!.multiRemove(keys, err => { 69 | if (err) { 70 | reject(err); 71 | } else { 72 | resolve(keys); 73 | } 74 | }); 75 | }); 76 | }, 77 | 78 | clear() { 79 | return CoreManager.getAsyncStorage()!.clear(); 80 | }, 81 | }; 82 | 83 | export default StorageController; 84 | -------------------------------------------------------------------------------- /src/StorageController.ts: -------------------------------------------------------------------------------- 1 | import RNStorageController from './StorageController.react-native'; 2 | import BrowserStorageController from './StorageController.browser'; 3 | import WeappStorageController from './StorageController.weapp'; 4 | import DefaultStorageController from './StorageController.default'; 5 | 6 | let StorageController: any = DefaultStorageController; 7 | 8 | if (process.env.PARSE_BUILD === 'react-native') { 9 | StorageController = RNStorageController; 10 | } else if (process.env.PARSE_BUILD === 'browser') { 11 | StorageController = BrowserStorageController; 12 | } else if (process.env.PARSE_BUILD === 'weapp') { 13 | StorageController = WeappStorageController; 14 | } 15 | 16 | export default StorageController; 17 | -------------------------------------------------------------------------------- /src/StorageController.weapp.ts: -------------------------------------------------------------------------------- 1 | const StorageController = { 2 | async: 0, 3 | 4 | getItem(path: string): string | null { 5 | // @ts-ignore 6 | return wx.getStorageSync(path); 7 | }, 8 | 9 | setItem(path: string, value: string) { 10 | try { 11 | // @ts-ignore 12 | wx.setStorageSync(path, value); 13 | } catch (_) { 14 | // Quota exceeded 15 | } 16 | }, 17 | 18 | removeItem(path: string) { 19 | // @ts-ignore 20 | wx.removeStorageSync(path); 21 | }, 22 | 23 | getAllKeys() { 24 | // @ts-ignore 25 | const res = wx.getStorageInfoSync(); 26 | return res.keys; 27 | }, 28 | 29 | clear() { 30 | // @ts-ignore 31 | wx.clearStorageSync(); 32 | }, 33 | }; 34 | 35 | export default StorageController; 36 | -------------------------------------------------------------------------------- /src/TaskQueue.ts: -------------------------------------------------------------------------------- 1 | import { resolvingPromise } from './promiseUtils'; 2 | 3 | interface Task { 4 | task: () => Promise; 5 | _completion: any; 6 | } 7 | 8 | class TaskQueue { 9 | queue: Task[]; 10 | 11 | constructor() { 12 | this.queue = []; 13 | } 14 | 15 | enqueue(task: () => Promise): Promise { 16 | // eslint-disable-next-line 17 | const taskComplete = resolvingPromise(); 18 | this.queue.push({ 19 | task: task, 20 | _completion: taskComplete, 21 | }); 22 | if (this.queue.length === 1) { 23 | task().then( 24 | () => { 25 | this._dequeue(); 26 | taskComplete.resolve(); 27 | }, 28 | error => { 29 | this._dequeue(); 30 | taskComplete.reject(error); 31 | } 32 | ); 33 | } 34 | return taskComplete; 35 | } 36 | 37 | _dequeue() { 38 | this.queue.shift(); 39 | if (this.queue.length) { 40 | const next = this.queue[0]; 41 | next.task().then( 42 | () => { 43 | this._dequeue(); 44 | next._completion.resolve(); 45 | }, 46 | error => { 47 | this._dequeue(); 48 | next._completion.reject(error); 49 | } 50 | ); 51 | } 52 | } 53 | } 54 | 55 | export default TaskQueue; 56 | -------------------------------------------------------------------------------- /src/WebSocketController.ts: -------------------------------------------------------------------------------- 1 | /* global WebSocket */ 2 | import ws from 'ws'; 3 | import SocketWeapp from './Socket.weapp'; 4 | 5 | let WebSocketController; 6 | 7 | try { 8 | if (process.env.PARSE_BUILD === 'browser') { 9 | WebSocketController = 10 | typeof WebSocket === 'function' || typeof WebSocket === 'object' ? WebSocket : null; 11 | } else if (process.env.PARSE_BUILD === 'node') { 12 | WebSocketController = ws; 13 | } else if (process.env.PARSE_BUILD === 'weapp') { 14 | WebSocketController = SocketWeapp; 15 | } else if (process.env.PARSE_BUILD === 'react-native') { 16 | WebSocketController = WebSocket; 17 | } 18 | } catch (_) { 19 | // WebSocket unavailable 20 | } 21 | 22 | export default WebSocketController; 23 | -------------------------------------------------------------------------------- /src/Xhr.weapp.ts: -------------------------------------------------------------------------------- 1 | /* istanbul ignore file */ 2 | 3 | // @ts-ignore 4 | function parseResponse(res: wx.RequestSuccessCallbackResult) { 5 | let headers = res.header || {}; 6 | headers = Object.keys(headers).reduce((map, key) => { 7 | map[key.toLowerCase()] = headers[key]; 8 | return map; 9 | }, {}); 10 | 11 | return { 12 | status: res.statusCode, 13 | json: () => { 14 | if (typeof res.data === 'object') { 15 | return Promise.resolve(res.data); 16 | } 17 | let json = {}; 18 | try { 19 | json = JSON.parse(res.data); 20 | } catch (err) { 21 | console.error(err); 22 | } 23 | return Promise.resolve(json); 24 | }, 25 | headers: { 26 | keys: () => Object.keys(headers), 27 | get: (n: string) => headers[n.toLowerCase()], 28 | has: (n: string) => n.toLowerCase() in headers, 29 | entries: () => { 30 | const all = []; 31 | for (const key in headers) { 32 | if (headers[key]) { 33 | all.push([key, headers[key]]); 34 | } 35 | } 36 | return all; 37 | }, 38 | }, 39 | }; 40 | } 41 | 42 | export function polyfillFetch() { 43 | const typedGlobal = global as any; 44 | if (typeof typedGlobal.fetch !== 'function') { 45 | typedGlobal.fetch = (url: string, options: any) => { 46 | const TEXT_FILE_EXTS = /\.(txt|json|html|txt|csv)/; 47 | const dataType = url.match(TEXT_FILE_EXTS) ? 'text' : 'arraybuffer'; 48 | return new Promise((resolve, reject) => { 49 | // @ts-ignore 50 | wx.request({ 51 | url, 52 | method: options.method || 'GET', 53 | data: options.body, 54 | header: options.headers, 55 | dataType, 56 | responseType: dataType, 57 | success: response => resolve(parseResponse(response)), 58 | fail: error => reject(error), 59 | }); 60 | }); 61 | }; 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/__tests__/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "jest": true 4 | }, 5 | "globals": { 6 | 7 | }, 8 | "rules": { 9 | "no-console": [0], 10 | "no-var": "error", 11 | "jsdoc/check-alignment": 0, 12 | "jsdoc/check-examples": 0, 13 | "jsdoc/check-indentation": 0, 14 | "jsdoc/check-param-names": 0, 15 | "jsdoc/check-syntax": 0, 16 | "jsdoc/check-tag-names": 0, 17 | "jsdoc/check-types": 0, 18 | "jsdoc/implements-on-classes": 0, 19 | "jsdoc/match-description": 0, 20 | "jsdoc/newline-after-description": 0, 21 | "jsdoc/no-types": 0, 22 | "jsdoc/no-undefined-types": 0, 23 | "jsdoc/require-description": 0, 24 | "jsdoc/require-description-complete-sentence": 0, 25 | "jsdoc/require-example": 0, 26 | "jsdoc/require-hyphen-before-param-description": 0, 27 | "jsdoc/require-jsdoc": 0, 28 | "jsdoc/require-param": 0, 29 | "jsdoc/require-param-description": 0, 30 | "jsdoc/require-param-name": 0, 31 | "jsdoc/require-param-type": 0, 32 | "jsdoc/require-returns": 0, 33 | "jsdoc/require-returns-check": 0, 34 | "jsdoc/require-returns-description": 0, 35 | "jsdoc/require-returns-type": 0, 36 | "jsdoc/valid-types": 0 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/__tests__/Analytics-test.js: -------------------------------------------------------------------------------- 1 | jest.dontMock('../Analytics'); 2 | jest.dontMock('../CoreManager'); 3 | 4 | const Analytics = require('../Analytics'); 5 | const CoreManager = require('../CoreManager').default; 6 | 7 | const defaultController = CoreManager.getAnalyticsController(); 8 | 9 | describe('Analytics', () => { 10 | beforeEach(() => { 11 | const track = jest.fn(); 12 | track.mockReturnValue(Promise.resolve()); 13 | CoreManager.setAnalyticsController({ track: track }); 14 | }); 15 | 16 | it('throws when no event name is provided', () => { 17 | expect(Analytics.track).toThrow('A name for the custom event must be provided'); 18 | 19 | expect(Analytics.track.bind(null, '')).toThrow('A name for the custom event must be provided'); 20 | }); 21 | 22 | it('trims whitespace from event names', () => { 23 | Analytics.track(' before', {}); 24 | expect(CoreManager.getAnalyticsController().track.mock.calls[0]).toEqual(['before', {}]); 25 | 26 | Analytics.track('after ', {}); 27 | expect(CoreManager.getAnalyticsController().track.mock.calls[1]).toEqual(['after', {}]); 28 | 29 | Analytics.track(' both ', {}); 30 | expect(CoreManager.getAnalyticsController().track.mock.calls[2]).toEqual(['both', {}]); 31 | }); 32 | 33 | it('passes along event names and dimensions', () => { 34 | Analytics.track('myEvent', { value: 'a' }); 35 | expect(CoreManager.getAnalyticsController().track.mock.calls[0]).toEqual([ 36 | 'myEvent', 37 | { value: 'a' }, 38 | ]); 39 | }); 40 | 41 | it('throws when invalid dimensions are provided', () => { 42 | expect(Analytics.track.bind(null, 'event', { number: 12 })).toThrow( 43 | 'track() dimensions expects keys and values of type "string".' 44 | ); 45 | 46 | expect(Analytics.track.bind(null, 'event', { null: null })).toThrow( 47 | 'track() dimensions expects keys and values of type "string".' 48 | ); 49 | }); 50 | }); 51 | 52 | describe('AnalyticsController', () => { 53 | beforeEach(() => { 54 | CoreManager.setAnalyticsController(defaultController); 55 | const request = jest.fn(); 56 | request.mockReturnValue( 57 | Promise.resolve({ 58 | success: true, 59 | result: {}, 60 | }) 61 | ); 62 | const ajax = jest.fn(); 63 | CoreManager.setRESTController({ request: request, ajax: ajax }); 64 | }); 65 | 66 | it('passes dimensions along to the appropriate endpoint', () => { 67 | Analytics.track('click', { x: '12', y: '40' }); 68 | 69 | expect(CoreManager.getRESTController().request.mock.calls[0]).toEqual([ 70 | 'POST', 71 | 'events/click', 72 | { dimensions: { x: '12', y: '40' } }, 73 | ]); 74 | }); 75 | }); 76 | -------------------------------------------------------------------------------- /src/__tests__/AnonymousUtils-test.js: -------------------------------------------------------------------------------- 1 | jest.dontMock('../AnonymousUtils'); 2 | 3 | class MockUser { 4 | constructor() { 5 | this.className = '_User'; 6 | this.attributes = {}; 7 | } 8 | _isLinked() {} 9 | linkWith() {} 10 | static _registerAuthenticationProvider() {} 11 | static logInWith() {} 12 | } 13 | 14 | jest.setMock('../ParseUser', MockUser); 15 | 16 | const mockProvider = { 17 | restoreAuthentication() { 18 | return true; 19 | }, 20 | 21 | getAuthType() { 22 | return 'anonymous'; 23 | }, 24 | 25 | getAuthData() { 26 | return { 27 | authData: { 28 | id: '1234', 29 | }, 30 | }; 31 | }, 32 | }; 33 | 34 | const AnonymousUtils = require('../AnonymousUtils').default; 35 | 36 | describe('AnonymousUtils', () => { 37 | beforeEach(() => { 38 | jest.clearAllMocks(); 39 | jest.spyOn(AnonymousUtils, '_getAuthProvider').mockImplementation(() => mockProvider); 40 | }); 41 | 42 | it('can register provider', () => { 43 | AnonymousUtils._getAuthProvider.mockRestore(); 44 | jest.spyOn(MockUser, '_registerAuthenticationProvider'); 45 | AnonymousUtils._getAuthProvider(); 46 | AnonymousUtils._getAuthProvider(); 47 | expect(MockUser._registerAuthenticationProvider).toHaveBeenCalledTimes(1); 48 | }); 49 | 50 | it('can check user isLinked', () => { 51 | const user = new MockUser(); 52 | jest.spyOn(user, '_isLinked'); 53 | AnonymousUtils.isLinked(user); 54 | expect(user._isLinked).toHaveBeenCalledTimes(1); 55 | expect(user._isLinked).toHaveBeenCalledWith('anonymous'); 56 | expect(AnonymousUtils._getAuthProvider).toHaveBeenCalledTimes(1); 57 | }); 58 | 59 | it('can link user', () => { 60 | const user = new MockUser(); 61 | jest.spyOn(user, 'linkWith'); 62 | AnonymousUtils.link(user); 63 | expect(user.linkWith).toHaveBeenCalledTimes(1); 64 | expect(user.linkWith).toHaveBeenCalledWith('anonymous', mockProvider.getAuthData(), undefined); 65 | expect(AnonymousUtils._getAuthProvider).toHaveBeenCalledTimes(1); 66 | }); 67 | 68 | it('can login user', () => { 69 | jest.spyOn(MockUser, 'logInWith'); 70 | AnonymousUtils.logIn(); 71 | expect(MockUser.logInWith).toHaveBeenCalledTimes(1); 72 | expect(MockUser.logInWith).toHaveBeenCalledWith( 73 | 'anonymous', 74 | mockProvider.getAuthData(), 75 | undefined 76 | ); 77 | expect(AnonymousUtils._getAuthProvider).toHaveBeenCalledTimes(1); 78 | }); 79 | }); 80 | -------------------------------------------------------------------------------- /src/__tests__/InstallationController-test.js: -------------------------------------------------------------------------------- 1 | jest.dontMock('../CoreManager'); 2 | jest.dontMock('../decode'); 3 | jest.dontMock('../encode'); 4 | jest.dontMock('../InstallationController'); 5 | jest.dontMock('../ObjectStateMutations'); 6 | jest.dontMock('../ParseInstallation'); 7 | jest.dontMock('../ParseObject'); 8 | jest.dontMock('../ParseOp'); 9 | jest.dontMock('../Storage'); 10 | jest.dontMock('../StorageController.default'); 11 | jest.dontMock('../SingleInstanceStateController'); 12 | jest.dontMock('../UniqueInstanceStateController'); 13 | jest.mock('../uuid', () => { 14 | let value = 0; 15 | return () => value++ + ''; 16 | }); 17 | 18 | const CoreManager = require('../CoreManager').default; 19 | const ParseInstallation = require('../ParseInstallation').default; 20 | const InstallationController = require('../InstallationController').default; 21 | const Storage = require('../Storage').default; 22 | 23 | CoreManager.setStorageController(require('../StorageController.default').default); 24 | 25 | describe('InstallationController', () => { 26 | beforeEach(() => { 27 | CoreManager.set('APPLICATION_ID', 'A'); 28 | CoreManager.set('JAVASCRIPT_KEY', 'B'); 29 | Storage._clear(); 30 | InstallationController._clearCache(); 31 | }); 32 | 33 | it('generates a new installation id when there is none', async () => { 34 | const iid = await InstallationController.currentInstallationId(); 35 | expect(typeof iid).toBe('string'); 36 | expect(iid.length).toBeGreaterThan(0); 37 | }); 38 | 39 | it('caches the installation id', async () => { 40 | const iid = await InstallationController.currentInstallationId(); 41 | Storage._clear(); 42 | const i = await InstallationController.currentInstallationId(); 43 | expect(i).toBe(iid); 44 | }); 45 | 46 | it('permanently stores the installation id', async () => { 47 | const iid = await InstallationController.currentInstallationId(); 48 | InstallationController._clearCache(); 49 | const i = await InstallationController.currentInstallationId(); 50 | expect(i).toBe(iid); 51 | }); 52 | 53 | it('can set installation id', async () => { 54 | const iid = '12345678'; 55 | InstallationController._setInstallationIdCache(iid); 56 | const i = await InstallationController.currentInstallationId(); 57 | expect(i).toBe(iid); 58 | }); 59 | 60 | it('generates a new installation when there is none', async () => { 61 | const installation = await InstallationController.currentInstallation(); 62 | expect(installation instanceof ParseInstallation).toBe(true); 63 | expect(installation.deviceType).toBe('web'); 64 | expect(installation.installationId).toBeDefined(); 65 | }); 66 | 67 | it('caches the current installation', async () => { 68 | const iid = 'cached-installation-id'; 69 | InstallationController._setInstallationIdCache(iid); 70 | const installation = await InstallationController.currentInstallation(); 71 | Storage._clear(); 72 | const i = await InstallationController.currentInstallation(); 73 | expect(i.installationId).toEqual(installation.installationId); 74 | }); 75 | 76 | it('permanently stores the current installation', async () => { 77 | const iid = 'stored-installation-id'; 78 | InstallationController._setInstallationIdCache(iid); 79 | const installation = await InstallationController.currentInstallation(); 80 | InstallationController._clearCache(); 81 | const i = await InstallationController.currentInstallation(); 82 | expect(i.installationId).toEqual(installation.installationId); 83 | }); 84 | 85 | it('can update installation on disk', async () => { 86 | const installationId = 'new-installation-id'; 87 | const installation = new ParseInstallation({ installationId }); 88 | InstallationController.updateInstallationOnDisk(installation); 89 | const i = await InstallationController.currentInstallation(); 90 | expect(i.installationId).toBe(installationId); 91 | }); 92 | 93 | it('can handle cache not matching disk', async () => { 94 | InstallationController._setCurrentInstallationCache(null, true); 95 | const i = await InstallationController.currentInstallation(); 96 | expect(i).toBeNull(); 97 | }); 98 | }); 99 | -------------------------------------------------------------------------------- /src/__tests__/ParseError-test.js: -------------------------------------------------------------------------------- 1 | jest.dontMock('../ParseError'); 2 | jest.dontMock('../CoreManager'); 3 | 4 | const ParseError = require('../ParseError').default; 5 | const CoreManager = require('../CoreManager').default; 6 | 7 | describe('ParseError', () => { 8 | it('have sensible string representation', () => { 9 | const error = new ParseError(123, 'some error message'); 10 | 11 | expect(error.toString()).toMatch('ParseError'); 12 | expect(error.toString()).toMatch('123'); 13 | expect(error.toString()).toMatch('some error message'); 14 | }); 15 | 16 | it('has a proper json representation', () => { 17 | const error = new ParseError(123, 'some error message'); 18 | expect(JSON.parse(JSON.stringify(error))).toEqual({ 19 | message: 'some error message', 20 | code: 123, 21 | }); 22 | }); 23 | 24 | it('can override message', () => { 25 | CoreManager.set('PARSE_ERRORS', [{ code: 123, message: 'Oops.' }]); 26 | const error = new ParseError(123, 'some error message'); 27 | expect(JSON.parse(JSON.stringify(error))).toEqual({ 28 | message: 'Oops.', 29 | code: 123, 30 | }); 31 | CoreManager.set('PARSE_ERRORS', []); 32 | }); 33 | }); 34 | -------------------------------------------------------------------------------- /src/__tests__/ParsePolygon-test.js: -------------------------------------------------------------------------------- 1 | jest.autoMockOff(); 2 | 3 | const ParseGeoPoint = require('../ParseGeoPoint').default; 4 | const ParsePolygon = require('../ParsePolygon').default; 5 | 6 | const points = [ 7 | [0, 0], 8 | [0, 1], 9 | [1, 1], 10 | [1, 0], 11 | [0, 0], 12 | ]; 13 | 14 | describe('Polygon', () => { 15 | it('can initialize with points', () => { 16 | const polygon = new ParsePolygon(points); 17 | expect(polygon.coordinates).toEqual(points); 18 | }); 19 | 20 | it('can initialize with geopoints', () => { 21 | const geopoints = [ 22 | new ParseGeoPoint(0, 0), 23 | new ParseGeoPoint(0, 1), 24 | new ParseGeoPoint(1, 1), 25 | new ParseGeoPoint(1, 0), 26 | new ParseGeoPoint(0, 0), 27 | ]; 28 | const polygon = new ParsePolygon(geopoints); 29 | expect(polygon.coordinates).toEqual(points); 30 | }); 31 | 32 | it('can set points', () => { 33 | const newPoints = [ 34 | [0, 0], 35 | [0, 10], 36 | [10, 10], 37 | [10, 0], 38 | [0, 0], 39 | ]; 40 | 41 | const polygon = new ParsePolygon(points); 42 | expect(polygon.coordinates).toEqual(points); 43 | 44 | polygon.coordinates = newPoints; 45 | expect(polygon.coordinates).toEqual(newPoints); 46 | }); 47 | 48 | it('toJSON', () => { 49 | const polygon = new ParsePolygon(points); 50 | expect(polygon.toJSON()).toEqual({ 51 | __type: 'Polygon', 52 | coordinates: points, 53 | }); 54 | }); 55 | 56 | it('equals', () => { 57 | const polygon1 = new ParsePolygon(points); 58 | const polygon2 = new ParsePolygon(points); 59 | const geopoint = new ParseGeoPoint(0, 0); 60 | 61 | expect(polygon1.equals(polygon2)).toBe(true); 62 | expect(polygon1.equals(geopoint)).toBe(false); 63 | 64 | const newPoints = [ 65 | [0, 0], 66 | [0, 10], 67 | [10, 10], 68 | [10, 0], 69 | [0, 0], 70 | ]; 71 | polygon1.coordinates = newPoints; 72 | expect(polygon1.equals(polygon2)).toBe(false); 73 | }); 74 | 75 | it('containsPoint', () => { 76 | const polygon = new ParsePolygon(points); 77 | const outside = new ParseGeoPoint(10, 10); 78 | const inside = new ParseGeoPoint(0.5, 0.5); 79 | 80 | expect(polygon.containsPoint(inside)).toBe(true); 81 | expect(polygon.containsPoint(outside)).toBe(false); 82 | }); 83 | 84 | it('throws error on invalid input', () => { 85 | expect(() => { 86 | new ParsePolygon(); 87 | }).toThrow('Coordinates must be an Array'); 88 | 89 | expect(() => { 90 | new ParsePolygon([]); 91 | }).toThrow('Polygon must have at least 3 GeoPoints or Points'); 92 | 93 | expect(() => { 94 | new ParsePolygon([1, 2, 3]); 95 | }).toThrow('Coordinates must be an Array of GeoPoints or Points'); 96 | }); 97 | }); 98 | -------------------------------------------------------------------------------- /src/__tests__/ParseRole-test.js: -------------------------------------------------------------------------------- 1 | jest.dontMock('../CoreManager'); 2 | jest.dontMock('../decode'); 3 | jest.dontMock('../ObjectStateMutations'); 4 | jest.dontMock('../ParseError'); 5 | jest.dontMock('../ParseObject'); 6 | jest.dontMock('../ParseOp'); 7 | jest.dontMock('../ParseRole'); 8 | jest.dontMock('../SingleInstanceStateController'); 9 | jest.dontMock('../UniqueInstanceStateController'); 10 | 11 | const ParseACL = require('../ParseACL').default; 12 | const ParseError = require('../ParseError').default; 13 | const ParseObject = require('../ParseObject').default; 14 | const ParseRelation = require('../ParseRelation').default; 15 | const ParseRole = require('../ParseRole').default; 16 | 17 | describe('ParseRole', () => { 18 | beforeEach(() => { 19 | ParseObject.disableSingleInstance(); 20 | }); 21 | 22 | it('can create Roles', () => { 23 | let role = new ParseRole(); 24 | expect(role.getName()).toBe(undefined); 25 | expect(role.getACL()).toBe(null); 26 | 27 | const acl = new ParseACL({ aUserId: { read: true, write: true } }); 28 | role = new ParseRole('admin', acl); 29 | expect(role.getName()).toBe('admin'); 30 | expect(role.getACL()).toBe(acl); 31 | }); 32 | 33 | it('handle non string name', () => { 34 | const role = new ParseRole(); 35 | role.get = () => 1234; 36 | expect(role.getName()).toBe(''); 37 | }); 38 | 39 | it('should throw error string with invalid name', () => { 40 | expect(() => new ParseRole('invalid:name', new ParseACL())).toThrow( 41 | new ParseError( 42 | ParseError.OTHER_CAUSE, 43 | "A role's name can be only contain alphanumeric characters, _, " + '-, and spaces.' 44 | ) 45 | ); 46 | }); 47 | 48 | it('can validate attributes', () => { 49 | const acl = new ParseACL({ aUserId: { read: true, write: true } }); 50 | const role = new ParseRole('admin', acl); 51 | role.id = '101'; 52 | expect( 53 | role.validate({ 54 | name: 'author', 55 | }) 56 | ).toEqual( 57 | new ParseError( 58 | ParseError.OTHER_CAUSE, 59 | "A role's name can only be set before it has been saved." 60 | ) 61 | ); 62 | 63 | role.id = undefined; 64 | expect( 65 | role.validate({ 66 | name: 12, 67 | }) 68 | ).toEqual(new ParseError(ParseError.OTHER_CAUSE, "A role's name must be a String.")); 69 | 70 | expect( 71 | role.validate({ 72 | name: '$$$', 73 | }) 74 | ).toEqual( 75 | new ParseError( 76 | ParseError.OTHER_CAUSE, 77 | "A role's name can be only contain alphanumeric characters, _, " + '-, and spaces.' 78 | ) 79 | ); 80 | 81 | expect( 82 | role.validate({ 83 | name: 'admin', 84 | }) 85 | ).toBe(false); 86 | const result = role.validate({ 87 | 'invalid#field': 'admin', 88 | }); 89 | expect(result.code).toBe(ParseError.INVALID_KEY_NAME); 90 | }); 91 | 92 | it('can be constructed from JSON', () => { 93 | const role = ParseObject.fromJSON({ 94 | className: '_Role', 95 | objectId: '102', 96 | name: 'admin', 97 | }); 98 | expect(role instanceof ParseObject).toBe(true); 99 | expect(role instanceof ParseRole).toBe(true); 100 | expect(role.getName()).toBe('admin'); 101 | }); 102 | 103 | it('can get relations', () => { 104 | const role = new ParseRole(); 105 | expect(role.getUsers() instanceof ParseRelation).toBe(true); 106 | expect(role.getRoles() instanceof ParseRelation).toBe(true); 107 | }); 108 | }); 109 | -------------------------------------------------------------------------------- /src/__tests__/Push-test.js: -------------------------------------------------------------------------------- 1 | jest.dontMock('../CoreManager'); 2 | jest.dontMock('../Push'); 3 | jest.dontMock('./test_helpers/asyncHelper'); 4 | 5 | const mockQuery = function () { 6 | this.where = {}; 7 | }; 8 | mockQuery.prototype = { 9 | toJSON() { 10 | return { 11 | where: this.where, 12 | }; 13 | }, 14 | }; 15 | jest.setMock('../ParseQuery', { 16 | __esModule: true, 17 | default: mockQuery, 18 | }); 19 | 20 | const CoreManager = require('../CoreManager').default; 21 | const ParseQuery = require('../ParseQuery').default; 22 | const Push = require('../Push'); 23 | 24 | const defaultController = CoreManager.getPushController(); 25 | 26 | describe('Push', () => { 27 | beforeEach(() => { 28 | CoreManager.setPushController({ 29 | send(data) { 30 | // Pipe data through so we can test it 31 | return Promise.resolve(data); 32 | }, 33 | }); 34 | }); 35 | 36 | it('can be sent with a where clause', done => { 37 | const q = new ParseQuery(); 38 | q.where = { 39 | installationId: '123', 40 | }; 41 | 42 | Push.send({ 43 | where: q, 44 | }).then(data => { 45 | expect(data.where).toEqual({ 46 | installationId: '123', 47 | }); 48 | done(); 49 | }); 50 | }); 51 | 52 | it('can specify a push time with a Date', done => { 53 | Push.send({ 54 | push_time: new Date(Date.UTC(2015, 1, 1)), 55 | }).then(data => { 56 | expect(data.push_time).toBe('2015-02-01T00:00:00.000Z'); 57 | done(); 58 | }); 59 | }); 60 | 61 | it('can specify a push time with a string', done => { 62 | Push.send({ 63 | // Local timezone push 64 | push_time: '2015-02-01T00:00:00.000', 65 | }).then(data => { 66 | expect(data.push_time).toBe('2015-02-01T00:00:00.000'); 67 | done(); 68 | }); 69 | }); 70 | 71 | it('can specify an expiration time', done => { 72 | Push.send({ 73 | expiration_time: new Date(Date.UTC(2015, 1, 1)), 74 | }).then(data => { 75 | expect(data.expiration_time).toBe('2015-02-01T00:00:00.000Z'); 76 | done(); 77 | }); 78 | }); 79 | 80 | it('cannot specify both an expiration time and an expiration interval', () => { 81 | expect( 82 | Push.send.bind(null, { 83 | expiration_time: new Date(), 84 | expiration_interval: 518400, 85 | }) 86 | ).toThrow('expiration_time and expiration_interval cannot both be set.'); 87 | }); 88 | }); 89 | 90 | describe('PushController', () => { 91 | it('forwards data along', () => { 92 | CoreManager.setPushController(defaultController); 93 | const request = jest.fn().mockReturnValue({ _headers: {} }); 94 | CoreManager.setRESTController({ 95 | request: request, 96 | ajax: function () {}, 97 | }); 98 | 99 | Push.send( 100 | { 101 | push_time: new Date(Date.UTC(2015, 1, 1)), 102 | }, 103 | { 104 | useMasterKey: true, 105 | } 106 | ); 107 | expect(CoreManager.getRESTController().request.mock.calls[0]).toEqual([ 108 | 'POST', 109 | 'push', 110 | { push_time: '2015-02-01T00:00:00.000Z' }, 111 | { returnStatus: true, useMasterKey: true }, 112 | ]); 113 | }); 114 | }); 115 | -------------------------------------------------------------------------------- /src/__tests__/arrayContainsObject-test.js: -------------------------------------------------------------------------------- 1 | jest.dontMock('../arrayContainsObject'); 2 | 3 | let localCount = 0; 4 | const mockObject = function (className, id) { 5 | this.className = className; 6 | this.id = id; 7 | if (!id) { 8 | this._localId = 'local' + localCount++; 9 | } 10 | }; 11 | mockObject.prototype._getId = function () { 12 | return this.id || this._localId; 13 | }; 14 | jest.setMock('../ParseObject', { 15 | __esModule: true, 16 | default: mockObject, 17 | }); 18 | 19 | const arrayContainsObject = require('../arrayContainsObject').default; 20 | const ParseObject = require('../ParseObject').default; 21 | const CoreManager = require('../CoreManager').default; 22 | jest 23 | .spyOn(CoreManager, 'getParseObject') 24 | .mockImplementation(() => require('../ParseObject').default); 25 | 26 | describe('arrayContainsObject', () => { 27 | it('detects objects by their id', () => { 28 | const o = new ParseObject('Item'); 29 | expect(arrayContainsObject([], o)).toBe(false); 30 | expect(arrayContainsObject([1, 'string'], o)).toBe(false); 31 | expect(arrayContainsObject([o], o)).toBe(true); 32 | expect(arrayContainsObject([new ParseObject('Item')], new ParseObject('Item'))).toBe(false); 33 | expect( 34 | arrayContainsObject( 35 | [new ParseObject('Item', 'a'), new ParseObject('Item', 'b')], 36 | new ParseObject('Item', 'a') 37 | ) 38 | ).toBe(true); 39 | }); 40 | }); 41 | -------------------------------------------------------------------------------- /src/__tests__/canBeSerialized-test.js: -------------------------------------------------------------------------------- 1 | jest.dontMock('../canBeSerialized'); 2 | jest.dontMock('../CoreManager'); 3 | 4 | function mockObject(id, attributes) { 5 | this.id = id; 6 | this.attributes = attributes; 7 | } 8 | mockObject.registerSubclass = function () {}; 9 | jest.setMock('../ParseObject', { 10 | __esModule: true, 11 | default: mockObject, 12 | }); 13 | const CoreManager = require('../CoreManager').default; 14 | CoreManager.setParseObject(mockObject); 15 | 16 | function mockFile(url) { 17 | this._url = url; 18 | } 19 | mockFile.prototype.url = function () { 20 | return this._url; 21 | }; 22 | jest.setMock('../ParseFile', { 23 | __esModule: true, 24 | default: mockFile, 25 | }); 26 | 27 | const canBeSerialized = require('../canBeSerialized').default; 28 | const ParseFile = require('../ParseFile').default; 29 | const ParseObject = require('../ParseObject').default; 30 | const ParseRelation = require('../ParseRelation').default; 31 | 32 | describe('canBeSerialized', () => { 33 | it('returns true for anything that is not a ParseObject', () => { 34 | expect(canBeSerialized(12)).toBe(true); 35 | expect(canBeSerialized('string')).toBe(true); 36 | expect(canBeSerialized(false)).toBe(true); 37 | expect(canBeSerialized([])).toBe(true); 38 | expect(canBeSerialized({})).toBe(true); 39 | }); 40 | 41 | it('validates primitives', () => { 42 | const o = new ParseObject('oid', { 43 | a: 12, 44 | b: 'string', 45 | c: false, 46 | }); 47 | expect(canBeSerialized(o)).toBe(true); 48 | }); 49 | 50 | it('returns false when a child is an unsaved object or file', () => { 51 | let o = new ParseObject('oid', { 52 | a: new ParseObject(), 53 | }); 54 | expect(canBeSerialized(o)).toBe(false); 55 | 56 | o = new ParseObject('oid', { 57 | a: new ParseObject('oid2', {}), 58 | }); 59 | expect(canBeSerialized(o)).toBe(true); 60 | 61 | o = new ParseObject('oid', { 62 | a: new ParseFile(), 63 | }); 64 | expect(canBeSerialized(o)).toBe(false); 65 | 66 | o = new ParseObject('oid', { 67 | a: new ParseFile('http://files.parsetfss.com/a/parse.txt'), 68 | }); 69 | expect(canBeSerialized(o)).toBe(true); 70 | }); 71 | 72 | it('returns true when all children have an id', () => { 73 | const child = new ParseObject('child', {}); 74 | const parent = new ParseObject(undefined, { 75 | child: child, 76 | }); 77 | child.attributes.parent = parent; 78 | expect(canBeSerialized(parent)).toBe(true); 79 | expect(canBeSerialized(child)).toBe(false); 80 | }); 81 | 82 | it('returns true for relations', () => { 83 | const relation = new ParseRelation(null, null); 84 | const parent = new ParseObject(undefined, { 85 | child: relation, 86 | }); 87 | expect(canBeSerialized(parent)).toBe(true); 88 | }); 89 | 90 | it('traverses nested arrays and objects', () => { 91 | let o = new ParseObject('oid', { 92 | a: { 93 | a: { 94 | a: { 95 | b: new ParseObject(), 96 | }, 97 | }, 98 | }, 99 | }); 100 | expect(canBeSerialized(o)).toBe(false); 101 | 102 | o = new ParseObject('oid', { 103 | a: { 104 | a: { 105 | a: { 106 | b: new ParseObject('oid2'), 107 | }, 108 | }, 109 | }, 110 | }); 111 | expect(canBeSerialized(o)).toBe(true); 112 | 113 | o = new ParseObject('oid', { 114 | a: [ 115 | 1, 116 | 2, 117 | 3, 118 | { 119 | b: new ParseObject(), 120 | }, 121 | ], 122 | }); 123 | expect(canBeSerialized(o)).toBe(false); 124 | 125 | o = new ParseObject('oid', { 126 | a: [ 127 | 1, 128 | 2, 129 | 3, 130 | { 131 | b: new ParseObject('oid2'), 132 | }, 133 | ], 134 | }); 135 | expect(canBeSerialized(o)).toBe(true); 136 | }); 137 | }); 138 | -------------------------------------------------------------------------------- /src/__tests__/decode-test.js: -------------------------------------------------------------------------------- 1 | jest.dontMock('../decode'); 2 | jest.dontMock('../CoreManager'); 3 | jest.dontMock('../ParseFile'); 4 | jest.dontMock('../ParseGeoPoint'); 5 | jest.dontMock('../ParseObject'); 6 | jest.dontMock('../ParsePolygon'); 7 | 8 | const decode = require('../decode').default; 9 | 10 | const ParseFile = require('../ParseFile').default; 11 | const ParseGeoPoint = require('../ParseGeoPoint').default; 12 | const ParseObject = require('../ParseObject').default; 13 | const ParsePolygon = require('../ParsePolygon').default; 14 | 15 | describe('decode', () => { 16 | it('ignores primitives', () => { 17 | expect(decode(undefined)).toBe(undefined); 18 | expect(decode(null)).toBe(null); 19 | expect(decode(true)).toBe(true); 20 | expect(decode(12)).toBe(12); 21 | expect(decode('string')).toBe('string'); 22 | }); 23 | 24 | it('decodes dates', () => { 25 | expect( 26 | decode({ 27 | __type: 'Date', 28 | iso: '2015-02-01T00:00:00.000Z', 29 | }) 30 | ).toEqual(new Date(Date.UTC(2015, 1))); 31 | }); 32 | 33 | it('decodes GeoPoints', () => { 34 | const point = decode({ 35 | __type: 'GeoPoint', 36 | latitude: 40.5, 37 | longitude: 50.4, 38 | }); 39 | expect(point instanceof ParseGeoPoint).toBe(true); 40 | expect(point.latitude).toBe(40.5); 41 | expect(point.longitude).toBe(50.4); 42 | }); 43 | 44 | it('decodes Polygons', () => { 45 | const points = [ 46 | [0, 0], 47 | [0, 1], 48 | [1, 1], 49 | [1, 0], 50 | [0, 0], 51 | ]; 52 | const polygon = decode({ 53 | __type: 'Polygon', 54 | coordinates: points, 55 | }); 56 | expect(polygon instanceof ParsePolygon).toBe(true); 57 | expect(polygon.coordinates).toEqual(points); 58 | }); 59 | 60 | it('decodes Files', () => { 61 | const file = decode({ 62 | __type: 'File', 63 | name: 'parse.txt', 64 | url: 'https://files.parsetfss.com/a/parse.txt', 65 | }); 66 | expect(file instanceof ParseFile).toBe(true); 67 | expect(file.name()).toBe('parse.txt'); 68 | expect(file.url()).toBe('https://files.parsetfss.com/a/parse.txt'); 69 | }); 70 | 71 | it('decodes Relations', () => { 72 | const obj = decode({ 73 | __type: 'Relation', 74 | className: 'Delivery', 75 | }); 76 | expect(obj.constructor.mock.calls[0]).toEqual([null, null]); 77 | expect(obj.targetClassName).toBe('Delivery'); 78 | }); 79 | 80 | it('decodes Pointers', () => { 81 | const spy = jest.spyOn(ParseObject, 'fromJSON'); 82 | const data = { 83 | __type: 'Pointer', 84 | className: 'Item', 85 | objectId: '1001', 86 | }; 87 | decode(data); 88 | expect(spy.mock.calls[0][0]).toEqual(data); 89 | }); 90 | 91 | it('decodes ParseObjects', () => { 92 | const spy = jest.spyOn(ParseObject, 'fromJSON'); 93 | const data = { 94 | __type: 'Object', 95 | className: 'Item', 96 | objectId: '1001', 97 | }; 98 | decode(data); 99 | expect(spy.mock.calls[1][0]).toEqual(data); 100 | }); 101 | 102 | it('iterates over arrays', () => { 103 | expect(decode([{ __type: 'Date', iso: '2015-02-01T00:00:00.000Z' }, 12, 'string'])).toEqual([ 104 | new Date(Date.UTC(2015, 1)), 105 | 12, 106 | 'string', 107 | ]); 108 | }); 109 | 110 | it('iterates over objects', () => { 111 | expect( 112 | decode({ 113 | empty: null, 114 | when: { __type: 'Date', iso: '2015-04-01T00:00:00.000Z' }, 115 | count: 15, 116 | }) 117 | ).toEqual({ 118 | empty: null, 119 | when: new Date(Date.UTC(2015, 3)), 120 | count: 15, 121 | }); 122 | }); 123 | }); 124 | -------------------------------------------------------------------------------- /src/__tests__/escape-test.js: -------------------------------------------------------------------------------- 1 | jest.autoMockOff(); 2 | 3 | const escape = require('../escape').default; 4 | 5 | describe('escape', () => { 6 | it('escapes special HTML characters', () => { 7 | expect(escape('&')).toBe('&'); 8 | expect(escape('<')).toBe('<'); 9 | expect(escape('>')).toBe('>'); 10 | expect(escape("'")).toBe('''); 11 | expect(escape('"')).toBe('"'); 12 | expect(escape('/')).toBe('/'); 13 | 14 | // globally escapes 15 | expect(escape('

      left & right

      ')).toBe('<p>left & right</p>'); 16 | }); 17 | }); 18 | -------------------------------------------------------------------------------- /src/__tests__/parseDate-test.js: -------------------------------------------------------------------------------- 1 | jest.autoMockOff(); 2 | 3 | const parseDate = require('../parseDate').default; 4 | 5 | describe('parseDate', () => { 6 | it('returns a Date for valid strings', () => { 7 | expect(Number(parseDate('2013-12-14T04:51:19.582Z'))).toBe( 8 | Number(new Date(Date.UTC(2013, 11, 14, 4, 51, 19, 582))) 9 | ); 10 | expect(Number(parseDate('2013-12-14T04:51:19Z'))).toBe( 11 | Number(new Date(Date.UTC(2013, 11, 14, 4, 51, 19))) 12 | ); 13 | }); 14 | 15 | it('returns null for invalid strings', () => { 16 | expect(parseDate('asdf')).toBe(null); 17 | }); 18 | }); 19 | -------------------------------------------------------------------------------- /src/__tests__/promiseUtils-test.js: -------------------------------------------------------------------------------- 1 | jest.autoMockOff(); 2 | 3 | const { when } = require('../promiseUtils'); 4 | 5 | describe('promiseUtils', () => { 6 | it('when', async () => { 7 | const promise1 = Promise.resolve(1); 8 | const promise2 = Promise.resolve(2); 9 | 10 | let result = await when([]); 11 | expect(result).toEqual([[]]); 12 | 13 | result = await when(promise1, promise2); 14 | expect(result).toEqual([1, 2]); 15 | 16 | result = await when(promise1, 'not a promise'); 17 | expect(result).toEqual([1, 'not a promise']); 18 | }); 19 | }); 20 | -------------------------------------------------------------------------------- /src/__tests__/react-native-test.js: -------------------------------------------------------------------------------- 1 | /* global WebSocket */ 2 | jest.dontMock('../CoreManager'); 3 | jest.dontMock('../CryptoController'); 4 | jest.dontMock('../decode'); 5 | jest.dontMock('../encode'); 6 | jest.dontMock('../EventEmitter'); 7 | jest.dontMock('../LiveQueryClient'); 8 | jest.dontMock('../LocalDatastore'); 9 | jest.dontMock('../ParseObject'); 10 | jest.dontMock('../Storage'); 11 | jest.dontMock('../LocalDatastoreController'); 12 | jest.dontMock('../WebSocketController'); 13 | jest.mock( 14 | 'react-native/Libraries/vendor/emitter/EventEmitter', 15 | () => { 16 | return { 17 | default: { 18 | prototype: { 19 | addListener: new (require('events').EventEmitter)(), 20 | }, 21 | }, 22 | }; 23 | }, 24 | { virtual: true } 25 | ); 26 | 27 | const mockEmitter = require('react-native/Libraries/vendor/emitter/EventEmitter').default; 28 | const CoreManager = require('../CoreManager').default; 29 | 30 | describe('React Native', () => { 31 | beforeEach(() => { 32 | process.env.PARSE_BUILD = 'react-native'; 33 | }); 34 | 35 | afterEach(() => { 36 | process.env.PARSE_BUILD = 'node'; 37 | }); 38 | 39 | it('load EventEmitter', () => { 40 | const EventEmitter = require('../EventEmitter').default; 41 | expect(EventEmitter).toEqual(mockEmitter); 42 | }); 43 | 44 | it('load CryptoController', () => { 45 | const CryptoJS = require('react-native-crypto-js'); 46 | jest.spyOn(CryptoJS.AES, 'encrypt').mockImplementation(() => { 47 | return { 48 | toString: () => 'World', 49 | }; 50 | }); 51 | const CryptoController = require('../CryptoController').default; 52 | const phrase = CryptoController.encrypt({}, 'salt'); 53 | expect(phrase).toBe('World'); 54 | expect(CryptoJS.AES.encrypt).toHaveBeenCalled(); 55 | }); 56 | 57 | it('load LocalDatastoreController', () => { 58 | const LocalDatastoreController = require('../LocalDatastoreController').default; 59 | require('../LocalDatastore'); 60 | const LDC = CoreManager.getLocalDatastoreController(); 61 | expect(LocalDatastoreController).toEqual(LDC); 62 | }); 63 | 64 | it('load StorageController', () => { 65 | const StorageController = require('../StorageController').default; 66 | CoreManager.setStorageController(StorageController); 67 | 68 | jest.spyOn(StorageController, 'setItemAsync'); 69 | const storage = require('../Storage').default; 70 | storage.setItemAsync('key', 'value'); 71 | expect(StorageController.setItemAsync).toHaveBeenCalledTimes(1); 72 | }); 73 | 74 | it('load WebSocketController', () => { 75 | const WebSocketController = require('../WebSocketController').default; 76 | CoreManager.setWebSocketController(WebSocketController); 77 | 78 | jest.mock('../EventEmitter', () => { 79 | return require('events').EventEmitter; 80 | }); 81 | const socket = WebSocket; 82 | require('../LiveQueryClient'); 83 | const websocket = CoreManager.getWebSocketController(); 84 | expect(websocket).toEqual(socket); 85 | }); 86 | }); 87 | -------------------------------------------------------------------------------- /src/__tests__/test_helpers/asyncHelper.js: -------------------------------------------------------------------------------- 1 | /* global runs waitsFor */ 2 | // We need this until Jest finishes upgrading to Jasmine 2.0 3 | module.exports = function asyncHelper(fn) { 4 | let finished = false; 5 | const done = function () { 6 | finished = true; 7 | }; 8 | 9 | return function () { 10 | runs(function () { 11 | fn(done); 12 | }); 13 | 14 | waitsFor(function () { 15 | return finished; 16 | }); 17 | }; 18 | }; 19 | -------------------------------------------------------------------------------- /src/__tests__/test_helpers/flushPromises.js: -------------------------------------------------------------------------------- 1 | const { setImmediate } = require('timers'); 2 | /** 3 | * Wait for all asynchronous code to finish executing 4 | */ 5 | function flushPromises() { 6 | return new Promise(resolve => setImmediate(resolve)); 7 | } 8 | 9 | module.exports = flushPromises; 10 | -------------------------------------------------------------------------------- /src/__tests__/test_helpers/mockAsyncStorage.js: -------------------------------------------------------------------------------- 1 | let mockStorage = {}; 2 | const mockAsyncStorage = { 3 | async: 1, 4 | async getItemAsync(path) { 5 | return mockStorage[path]; 6 | }, 7 | 8 | async setItemAsync(path, value) { 9 | mockStorage[path] = value; 10 | }, 11 | 12 | async removeItemAsync(path) { 13 | delete mockStorage[path]; 14 | }, 15 | 16 | async getAllKeysAsync() { 17 | return Object.keys(mockStorage); 18 | }, 19 | 20 | clear() { 21 | mockStorage = {}; 22 | }, 23 | }; 24 | 25 | module.exports = mockAsyncStorage; 26 | -------------------------------------------------------------------------------- /src/__tests__/test_helpers/mockFetch.js: -------------------------------------------------------------------------------- 1 | const { TextEncoder } = require('util'); 2 | /** 3 | * Mock fetch by pre-defining the statuses and results that it 4 | * return. 5 | * `results` is an array of objects of the form: 6 | * { status: ..., response: ... } 7 | * where status is a HTTP status number and result is a JSON object to pass 8 | * alongside it. 9 | * `upload`. 10 | * @ignore 11 | */ 12 | function mockFetch(results, headers = {}, error) { 13 | let attempts = -1; 14 | let didRead = false; 15 | global.fetch = jest.fn(async () => { 16 | attempts++; 17 | if (error) { 18 | return Promise.reject(error); 19 | } 20 | return Promise.resolve({ 21 | status: results[attempts].status, 22 | json: () => { 23 | const { response } = results[attempts]; 24 | return Promise.resolve(response); 25 | }, 26 | headers: { 27 | get: header => headers[header], 28 | has: header => headers[header] !== undefined, 29 | }, 30 | body: { 31 | getReader: () => ({ 32 | read: () => { 33 | if (didRead) { 34 | return Promise.resolve({ done: true }); 35 | } 36 | let { response } = results[attempts]; 37 | if (typeof response !== 'string') { 38 | response = JSON.stringify(response); 39 | } 40 | didRead = true; 41 | return Promise.resolve({ 42 | done: false, 43 | value: new TextEncoder().encode(response), 44 | }); 45 | }, 46 | }), 47 | }, 48 | }); 49 | }); 50 | } 51 | 52 | module.exports = mockFetch; 53 | -------------------------------------------------------------------------------- /src/__tests__/test_helpers/mockIndexedDB.js: -------------------------------------------------------------------------------- 1 | let mockStorage = {}; 2 | const mockStorageInterface = { 3 | createStore() {}, 4 | 5 | async get(path) { 6 | return mockStorage[path] || null; 7 | }, 8 | 9 | async set(path, value) { 10 | mockStorage[path] = value; 11 | }, 12 | 13 | async del(path) { 14 | delete mockStorage[path]; 15 | }, 16 | 17 | async keys() { 18 | return Object.keys(mockStorage); 19 | }, 20 | 21 | async clear() { 22 | mockStorage = {}; 23 | }, 24 | }; 25 | 26 | module.exports = mockStorageInterface; 27 | -------------------------------------------------------------------------------- /src/__tests__/test_helpers/mockRNStorage.js: -------------------------------------------------------------------------------- 1 | let mockStorage = {}; 2 | const mockRNStorage = { 3 | getItem(path, cb) { 4 | cb(undefined, mockStorage[path] || null); 5 | }, 6 | 7 | setItem(path, value, cb) { 8 | mockStorage[path] = value; 9 | cb(); 10 | }, 11 | 12 | removeItem(path, cb) { 13 | delete mockStorage[path]; 14 | cb(); 15 | }, 16 | 17 | getAllKeys(cb) { 18 | cb(undefined, Object.keys(mockStorage)); 19 | }, 20 | 21 | multiGet(keys, cb) { 22 | const objects = keys.map(key => [key, mockStorage[key]]); 23 | cb(undefined, objects); 24 | }, 25 | 26 | multiRemove(keys, cb) { 27 | keys.map(key => delete mockStorage[key]); 28 | cb(undefined); 29 | }, 30 | 31 | clear() { 32 | mockStorage = {}; 33 | }, 34 | }; 35 | 36 | module.exports = mockRNStorage; 37 | -------------------------------------------------------------------------------- /src/__tests__/test_helpers/mockWeChat.js: -------------------------------------------------------------------------------- 1 | let mockStorage = {}; 2 | let progressCallback = () => {}; 3 | let socketOpenCallback = () => {}; 4 | let socketMessageCallback = () => {}; 5 | let socketCloseCallback = () => {}; 6 | let SocketErrorCallback = () => {}; 7 | 8 | const mockWeChat = { 9 | getStorageSync(path) { 10 | return mockStorage[path]; 11 | }, 12 | 13 | setStorageSync(path, value) { 14 | mockStorage[path] = value; 15 | }, 16 | 17 | removeStorageSync(path) { 18 | delete mockStorage[path]; 19 | }, 20 | 21 | getStorageInfoSync() { 22 | return { 23 | keys: Object.keys(mockStorage), 24 | }; 25 | }, 26 | 27 | clearStorageSync() { 28 | mockStorage = {}; 29 | }, 30 | 31 | request(options) { 32 | return { 33 | onProgressUpdate: cb => { 34 | progressCallback = cb; 35 | }, 36 | abort: () => { 37 | progressCallback({ 38 | totalBytesExpectedToWrite: 0, 39 | totalBytesWritten: 0, 40 | }); 41 | options.success({ 42 | statusCode: 0, 43 | data: {}, 44 | }); 45 | options.fail(); 46 | }, 47 | }; 48 | }, 49 | 50 | connectSocket() {}, 51 | 52 | onSocketOpen(cb) { 53 | socketOpenCallback = cb; 54 | }, 55 | 56 | onSocketMessage(cb) { 57 | socketMessageCallback = cb; 58 | }, 59 | 60 | onSocketClose(cb) { 61 | socketCloseCallback = cb; 62 | }, 63 | 64 | onSocketError(cb) { 65 | SocketErrorCallback = cb; 66 | }, 67 | 68 | sendSocketMessage(data) { 69 | socketOpenCallback(); 70 | socketMessageCallback(data); 71 | }, 72 | 73 | closeSocket() { 74 | socketCloseCallback(); 75 | SocketErrorCallback(); 76 | }, 77 | }; 78 | 79 | module.exports = mockWeChat; 80 | -------------------------------------------------------------------------------- /src/__tests__/unique-test.js: -------------------------------------------------------------------------------- 1 | jest.dontMock('../arrayContainsObject'); 2 | jest.dontMock('../unique'); 3 | 4 | let localCount = 0; 5 | const mockObject = function (className, id) { 6 | this.className = className; 7 | this.id = id; 8 | if (!id) { 9 | this._localId = 'local' + localCount++; 10 | } 11 | }; 12 | mockObject.prototype._getId = function () { 13 | return this.id || this._localId; 14 | }; 15 | jest.setMock('../ParseObject', { 16 | __esModule: true, 17 | default: mockObject, 18 | }); 19 | 20 | const unique = require('../unique').default; 21 | const ParseObject = require('../ParseObject').default; 22 | const CoreManager = require('../CoreManager').default; 23 | jest 24 | .spyOn(CoreManager, 'getParseObject') 25 | .mockImplementation(() => require('../ParseObject').default); 26 | 27 | describe('unique', () => { 28 | it('produces an array with unique elements', () => { 29 | expect(unique([])).toEqual([]); 30 | expect(unique([1])).toEqual([1]); 31 | expect(unique([3, 4, 1])).toEqual([3, 4, 1]); 32 | expect(unique([3, 4, 3, 1])).toEqual([3, 4, 1]); 33 | expect(unique([2, 2, 2, 2, 2, 2, 2])).toEqual([2]); 34 | expect(unique(['a', 'b', 'c', 'a', 'd'])).toEqual(['a', 'b', 'c', 'd']); 35 | }); 36 | 37 | it('dedups objects by their id', () => { 38 | const o = new ParseObject('Item'); 39 | expect(unique([o, o, o])).toEqual([o]); 40 | expect(unique([new ParseObject('Item'), new ParseObject('Item')]).length).toBe(2); 41 | expect( 42 | unique([ 43 | new ParseObject('Item', 'a'), 44 | new ParseObject('Item', 'b'), 45 | new ParseObject('Item', 'a'), 46 | ]) 47 | ).toEqual([new ParseObject('Item', 'a'), new ParseObject('Item', 'b')]); 48 | expect( 49 | unique([ 50 | new ParseObject('Item', 'a'), 51 | new ParseObject('Item', 'b'), 52 | new ParseObject('Item', 'b'), 53 | new ParseObject('Item', 'a'), 54 | ]) 55 | ).toEqual([new ParseObject('Item', 'a'), new ParseObject('Item', 'b')]); 56 | }); 57 | }); 58 | -------------------------------------------------------------------------------- /src/__tests__/weapp-test.js: -------------------------------------------------------------------------------- 1 | jest.dontMock('../CoreManager'); 2 | jest.dontMock('../CryptoController'); 3 | jest.dontMock('../decode'); 4 | jest.dontMock('../encode'); 5 | jest.dontMock('../EventEmitter'); 6 | jest.dontMock('../LiveQueryClient'); 7 | jest.dontMock('../Parse'); 8 | jest.dontMock('../ParseFile'); 9 | jest.dontMock('../ParseObject'); 10 | jest.dontMock('../RESTController'); 11 | jest.dontMock('../Socket.weapp'); 12 | jest.dontMock('../Storage'); 13 | jest.dontMock('../StorageController.weapp'); 14 | jest.dontMock('../uuid'); 15 | jest.dontMock('crypto-js/aes'); 16 | jest.dontMock('./test_helpers/mockWeChat'); 17 | 18 | const CoreManager = require('../CoreManager').default; 19 | const mockWeChat = require('./test_helpers/mockWeChat'); 20 | 21 | global.wx = mockWeChat; 22 | 23 | jest.mock('uuid', () => { 24 | return () => 0; 25 | }); 26 | 27 | describe('WeChat', () => { 28 | beforeEach(() => { 29 | process.env.PARSE_BUILD = 'weapp'; 30 | }); 31 | 32 | afterEach(() => { 33 | process.env.PARSE_BUILD = 'node'; 34 | }); 35 | 36 | it('load StorageController', () => { 37 | const StorageController = require('../StorageController').default; 38 | CoreManager.setStorageController(StorageController); 39 | jest.spyOn(StorageController, 'setItem'); 40 | const storage = require('../Storage').default; 41 | storage.setItem('key', 'value'); 42 | expect(StorageController.setItem).toHaveBeenCalledTimes(1); 43 | }); 44 | 45 | it('load RESTController', () => { 46 | const XHR = require('../Xhr.weapp'); 47 | jest.spyOn(XHR, 'polyfillFetch'); 48 | const RESTController = require('../RESTController').default; 49 | expect(XHR.polyfillFetch).toHaveBeenCalled(); 50 | expect(RESTController).toBeDefined(); 51 | }); 52 | 53 | it('load WebSocketController', () => { 54 | const socket = require('../WebSocketController'); 55 | CoreManager.setWebSocketController(socket); 56 | 57 | require('../LiveQueryClient'); 58 | const websocket = CoreManager.getWebSocketController(); 59 | expect(websocket).toEqual(socket); 60 | }); 61 | 62 | it('load uuid module', () => { 63 | const uuidv4 = require('../uuid').default; 64 | expect(uuidv4()).not.toEqual(0); 65 | expect(uuidv4()).not.toEqual(uuidv4()); 66 | }); 67 | 68 | describe('Socket', () => { 69 | it('send', () => { 70 | const Websocket = require('../Socket.weapp').default; 71 | jest.spyOn(mockWeChat, 'connectSocket'); 72 | const socket = new Websocket('wss://examples.com'); 73 | socket.onopen(); 74 | socket.onmessage(); 75 | socket.onclose(); 76 | socket.onerror(); 77 | socket.onopen = jest.fn(); 78 | socket.onmessage = jest.fn(); 79 | socket.onclose = jest.fn(); 80 | socket.onerror = jest.fn(); 81 | 82 | expect(mockWeChat.connectSocket).toHaveBeenCalled(); 83 | 84 | socket.send('{}'); 85 | expect(socket.onopen).toHaveBeenCalled(); 86 | expect(socket.onmessage).toHaveBeenCalled(); 87 | 88 | socket.close(); 89 | expect(socket.onclose).toHaveBeenCalled(); 90 | expect(socket.onerror).toHaveBeenCalled(); 91 | }); 92 | }); 93 | }); 94 | -------------------------------------------------------------------------------- /src/arrayContainsObject.ts: -------------------------------------------------------------------------------- 1 | import CoreManager from './CoreManager'; 2 | import type ParseObject from './ParseObject'; 3 | 4 | export default function arrayContainsObject(array: any[], object: ParseObject): boolean { 5 | if (array.indexOf(object) > -1) { 6 | return true; 7 | } 8 | const ParseObject = CoreManager.getParseObject(); 9 | for (let i = 0; i < array.length; i++) { 10 | if ( 11 | array[i] instanceof ParseObject && 12 | array[i].className === object.className && 13 | array[i]._getId() === object._getId() 14 | ) { 15 | return true; 16 | } 17 | } 18 | return false; 19 | } 20 | -------------------------------------------------------------------------------- /src/canBeSerialized.ts: -------------------------------------------------------------------------------- 1 | import CoreManager from './CoreManager'; 2 | import ParseFile from './ParseFile'; 3 | import type ParseObject from './ParseObject'; 4 | import ParseRelation from './ParseRelation'; 5 | 6 | export default function canBeSerialized(obj: ParseObject): boolean { 7 | const ParseObject = CoreManager.getParseObject(); 8 | if (!(obj instanceof ParseObject)) { 9 | return true; 10 | } 11 | const attributes = obj.attributes; 12 | for (const attr in attributes) { 13 | const val = attributes[attr]; 14 | if (!canBeSerializedHelper(val)) { 15 | return false; 16 | } 17 | } 18 | return true; 19 | } 20 | 21 | function canBeSerializedHelper(value: any): boolean { 22 | if (typeof value !== 'object') { 23 | return true; 24 | } 25 | if (value instanceof ParseRelation) { 26 | return true; 27 | } 28 | const ParseObject = CoreManager.getParseObject(); 29 | if (value instanceof ParseObject) { 30 | return !!value.id; 31 | } 32 | if (value instanceof ParseFile) { 33 | if (value.url()) { 34 | return true; 35 | } 36 | return false; 37 | } 38 | if (Array.isArray(value)) { 39 | for (let i = 0; i < value.length; i++) { 40 | if (!canBeSerializedHelper(value[i])) { 41 | return false; 42 | } 43 | } 44 | return true; 45 | } 46 | for (const k in value) { 47 | if (!canBeSerializedHelper(value[k])) { 48 | return false; 49 | } 50 | } 51 | return true; 52 | } 53 | -------------------------------------------------------------------------------- /src/decode.ts: -------------------------------------------------------------------------------- 1 | import CoreManager from './CoreManager'; 2 | import ParseFile from './ParseFile'; 3 | import ParseGeoPoint from './ParseGeoPoint'; 4 | import ParsePolygon from './ParsePolygon'; 5 | import ParseRelation from './ParseRelation'; 6 | 7 | export default function decode(value: any): any { 8 | if (value === null || typeof value !== 'object' || value instanceof Date) { 9 | return value; 10 | } 11 | if (Array.isArray(value)) { 12 | const dup = []; 13 | value.forEach((v, i) => { 14 | dup[i] = decode(v); 15 | }); 16 | return dup; 17 | } 18 | if (typeof value.__op === 'string') { 19 | const { opFromJSON } = CoreManager.getParseOp(); 20 | return opFromJSON(value); 21 | } 22 | const ParseObject = CoreManager.getParseObject(); 23 | if (value.__type === 'Pointer' && value.className) { 24 | return ParseObject.fromJSON(value); 25 | } 26 | if (value.__type === 'Object' && value.className) { 27 | return ParseObject.fromJSON(value); 28 | } 29 | if (value.__type === 'Relation') { 30 | // The parent and key fields will be populated by the parent 31 | const relation = new ParseRelation(null, null); 32 | relation.targetClassName = value.className; 33 | return relation; 34 | } 35 | if (value.__type === 'Date') { 36 | return new Date(value.iso); 37 | } 38 | if (value.__type === 'File') { 39 | return ParseFile.fromJSON(value); 40 | } 41 | if (value.__type === 'GeoPoint') { 42 | return new ParseGeoPoint({ 43 | latitude: value.latitude, 44 | longitude: value.longitude, 45 | }); 46 | } 47 | if (value.__type === 'Polygon') { 48 | return new ParsePolygon(value.coordinates); 49 | } 50 | const copy = {}; 51 | for (const k in value) { 52 | copy[k] = decode(value[k]); 53 | } 54 | return copy; 55 | } 56 | -------------------------------------------------------------------------------- /src/encode.ts: -------------------------------------------------------------------------------- 1 | import CoreManager from './CoreManager'; 2 | import ParseACL from './ParseACL'; 3 | import ParseFile from './ParseFile'; 4 | import ParseGeoPoint from './ParseGeoPoint'; 5 | import ParsePolygon from './ParsePolygon'; 6 | import ParseRelation from './ParseRelation'; 7 | 8 | function encode( 9 | value: any, 10 | disallowObjects: boolean, 11 | forcePointers: boolean, 12 | seen: any[], 13 | offline: boolean 14 | ): any { 15 | const ParseObject = CoreManager.getParseObject(); 16 | if (value instanceof ParseObject) { 17 | if (disallowObjects) { 18 | throw new Error('Parse Objects not allowed here'); 19 | } 20 | const seenEntry = value.id ? value.className + ':' + value.id : value; 21 | if ( 22 | forcePointers || 23 | !seen || 24 | seen.indexOf(seenEntry) > -1 || 25 | value.dirty() || 26 | Object.keys(value._getServerData()).length < 1 27 | ) { 28 | if (offline && value._getId().startsWith('local')) { 29 | return value.toOfflinePointer(); 30 | } 31 | return value.toPointer(); 32 | } 33 | seen = seen.concat(seenEntry); 34 | return value._toFullJSON(seen, offline); 35 | } 36 | const { Op } = CoreManager.getParseOp(); 37 | if ( 38 | value instanceof Op || 39 | value instanceof ParseACL || 40 | value instanceof ParseGeoPoint || 41 | value instanceof ParsePolygon || 42 | value instanceof ParseRelation 43 | ) { 44 | return value.toJSON(); 45 | } 46 | if (value instanceof ParseFile) { 47 | if (!value.url()) { 48 | throw new Error('Tried to encode an unsaved file.'); 49 | } 50 | return value.toJSON(); 51 | } 52 | if (Object.prototype.toString.call(value) === '[object Date]') { 53 | if (isNaN(value)) { 54 | throw new Error('Tried to encode an invalid date.'); 55 | } 56 | return { __type: 'Date', iso: (value as Date).toJSON() }; 57 | } 58 | if ( 59 | Object.prototype.toString.call(value) === '[object RegExp]' && 60 | typeof value.source === 'string' 61 | ) { 62 | return value.source; 63 | } 64 | 65 | if (Array.isArray(value)) { 66 | return value.map(v => { 67 | return encode(v, disallowObjects, forcePointers, seen, offline); 68 | }); 69 | } 70 | 71 | if (value && typeof value === 'object') { 72 | const output = {}; 73 | for (const k in value) { 74 | output[k] = encode(value[k], disallowObjects, forcePointers, seen, offline); 75 | } 76 | return output; 77 | } 78 | 79 | return value; 80 | } 81 | 82 | export default function ( 83 | value: any, 84 | disallowObjects?: boolean, 85 | forcePointers?: boolean, 86 | seen?: any[], 87 | offline?: boolean 88 | ): any { 89 | return encode(value, !!disallowObjects, !!forcePointers, seen || [], offline); 90 | } 91 | -------------------------------------------------------------------------------- /src/equals.ts: -------------------------------------------------------------------------------- 1 | import CoreManager from './CoreManager'; 2 | import ParseACL from './ParseACL'; 3 | import ParseFile from './ParseFile'; 4 | import ParseGeoPoint from './ParseGeoPoint'; 5 | 6 | export default function equals(a: any, b: any): boolean { 7 | const toString = Object.prototype.toString; 8 | if (toString.call(a) === '[object Date]' || toString.call(b) === '[object Date]') { 9 | const dateA = new Date(a); 10 | const dateB = new Date(b); 11 | return +dateA === +dateB; 12 | } 13 | 14 | if (typeof a !== typeof b) { 15 | return false; 16 | } 17 | 18 | if (!a || typeof a !== 'object') { 19 | // a is a primitive 20 | return a === b; 21 | } 22 | 23 | if (Array.isArray(a) || Array.isArray(b)) { 24 | if (!Array.isArray(a) || !Array.isArray(b)) { 25 | return false; 26 | } 27 | if (a.length !== b.length) { 28 | return false; 29 | } 30 | for (let i = 0; i < a.length; i += 1) { 31 | if (!equals(a[i], b[i])) { 32 | return false; 33 | } 34 | } 35 | return true; 36 | } 37 | const ParseObject = CoreManager.getParseObject(); 38 | if ( 39 | a instanceof ParseACL || 40 | a instanceof ParseFile || 41 | a instanceof ParseGeoPoint || 42 | a instanceof ParseObject 43 | ) { 44 | return a.equals(b); 45 | } 46 | if (b instanceof ParseObject) { 47 | if (a.__type === 'Object' || a.__type === 'Pointer') { 48 | return a.objectId === b.id && a.className === b.className; 49 | } 50 | } 51 | if (Object.keys(a).length !== Object.keys(b).length) { 52 | return false; 53 | } 54 | for (const k in a) { 55 | if (!equals(a[k], b[k])) { 56 | return false; 57 | } 58 | } 59 | return true; 60 | } 61 | -------------------------------------------------------------------------------- /src/escape.ts: -------------------------------------------------------------------------------- 1 | const encoded = { 2 | '&': '&', 3 | '<': '<', 4 | '>': '>', 5 | '/': '/', 6 | "'": ''', 7 | '"': '"', 8 | }; 9 | 10 | export default function escape(str: string): string { 11 | return str.replace(/[&<>\/'"]/g, function (char) { 12 | return encoded[char]; 13 | }); 14 | } 15 | -------------------------------------------------------------------------------- /src/interfaces/AuthProvider.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/no-unused-vars */ 2 | /** 3 | * Interface declaration for Authentication Providers 4 | * 5 | * @interface AuthProvider 6 | */ 7 | export class AuthProvider { 8 | /** 9 | * Called when _linkWith isn't passed authData. 10 | * Handle your own authentication here. 11 | * 12 | * @param {object} options options.success(provider, authData) or options.error(provider, error) on completion 13 | */ 14 | authenticate(options: any): void {} 15 | 16 | /** 17 | * (Optional) Called when service is unlinked. 18 | * Handle any cleanup here. 19 | */ 20 | deauthenticate(): void {} 21 | 22 | /** 23 | * Unique identifier for this Auth Provider. 24 | * 25 | * @returns {string} identifier 26 | */ 27 | getAuthType(): string {} 28 | 29 | /** 30 | * Called when auth data is syncronized. 31 | * Can be used to determine if authData is still valid 32 | * 33 | * @param {object} authData Data used when register provider 34 | * @returns {boolean} Indicate if service should continue to be linked 35 | */ 36 | restoreAuthentication(authData: any): boolean {} 37 | } 38 | -------------------------------------------------------------------------------- /src/interfaces/react-native.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Interface declaration for React Native modules 3 | */ 4 | declare module 'react-native' { 5 | declare class AsyncStorage { 6 | static getItem(path: string, cb: (err: string, value: string) => void): void; 7 | static setItem(path: string, value: string, cb: (err: string, value: string) => void): void; 8 | static removeItem(path: string, cb: (err: string, value: string) => void): void; 9 | static getAllKeys(cb: (err: string, keys: string[]) => void): void; 10 | static clear(): void; 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/interfaces/xmlhttprequest.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | declare module xmlhttprequest { 3 | declare var XMLHttpRequest: () => XMLHttpRequest; 4 | } 5 | -------------------------------------------------------------------------------- /src/isRevocableSession.ts: -------------------------------------------------------------------------------- 1 | export default function isRevocableSession(token: string): boolean { 2 | return token.indexOf('r:') > -1; 3 | } 4 | -------------------------------------------------------------------------------- /src/parseDate.ts: -------------------------------------------------------------------------------- 1 | export default function parseDate(iso8601: string): Date | null { 2 | const regexp = new RegExp( 3 | '^([0-9]{1,4})-([0-9]{1,2})-([0-9]{1,2})' + 4 | 'T' + 5 | '([0-9]{1,2}):([0-9]{1,2}):([0-9]{1,2})' + 6 | '(.([0-9]+))?' + 7 | 'Z$' 8 | ); 9 | const match = regexp.exec(iso8601); 10 | if (!match) { 11 | return null; 12 | } 13 | 14 | const year = parseInt(match[1]) || 0; 15 | const month = (parseInt(match[2]) || 1) - 1; 16 | const day = parseInt(match[3]) || 0; 17 | const hour = parseInt(match[4]) || 0; 18 | const minute = parseInt(match[5]) || 0; 19 | const second = parseInt(match[6]) || 0; 20 | const milli = parseInt(match[8]) || 0; 21 | 22 | return new Date(Date.UTC(year, month, day, hour, minute, second, milli)); 23 | } 24 | -------------------------------------------------------------------------------- /src/promiseUtils.ts: -------------------------------------------------------------------------------- 1 | // Create Deferred Promise 2 | export function resolvingPromise() { 3 | let res: (value: T) => void; 4 | let rej: (error: T) => void; 5 | const promise = new Promise((resolve, reject) => { 6 | res = resolve; 7 | rej = reject; 8 | }); 9 | const defer: typeof promise & { resolve: (res: T) => void; reject: (err: any) => void } = 10 | promise as any; 11 | defer.resolve = res!; 12 | defer.reject = rej!; 13 | return defer; 14 | } 15 | 16 | export function when(promises: any) { 17 | let objects; 18 | const arrayArgument = Array.isArray(promises); 19 | if (arrayArgument) { 20 | objects = promises; 21 | } else { 22 | objects = arguments; 23 | } 24 | 25 | let total = objects.length; 26 | let hadError = false; 27 | const results = []; 28 | const returnValue = arrayArgument ? [results] : results; 29 | const errors = []; 30 | results.length = objects.length; 31 | errors.length = objects.length; 32 | 33 | if (total === 0) { 34 | return Promise.resolve(returnValue); 35 | } 36 | 37 | const promise = resolvingPromise(); 38 | 39 | const resolveOne = function () { 40 | total--; 41 | if (total <= 0) { 42 | if (hadError) { 43 | promise.reject(errors); 44 | } else { 45 | promise.resolve(returnValue); 46 | } 47 | } 48 | }; 49 | 50 | const chain = function (object: Promise, index: number) { 51 | if (object && typeof object.then === 'function') { 52 | object.then( 53 | function (result) { 54 | results[index] = result; 55 | resolveOne(); 56 | }, 57 | function (error) { 58 | errors[index] = error; 59 | hadError = true; 60 | resolveOne(); 61 | } 62 | ); 63 | } else { 64 | results[index] = object; 65 | resolveOne(); 66 | } 67 | }; 68 | for (let i = 0; i < objects.length; i++) { 69 | chain(objects[i], i); 70 | } 71 | 72 | return promise; 73 | } 74 | 75 | export function continueWhile(test: () => any, emitter: () => Promise) { 76 | if (test()) { 77 | return emitter().then(() => { 78 | return continueWhile(test, emitter); 79 | }); 80 | } 81 | return Promise.resolve(); 82 | } 83 | -------------------------------------------------------------------------------- /src/unique.ts: -------------------------------------------------------------------------------- 1 | import arrayContainsObject from './arrayContainsObject'; 2 | import CoreManager from './CoreManager'; 3 | 4 | export default function unique(arr: T[]): T[] { 5 | const uniques: T[] = []; 6 | arr.forEach(value => { 7 | const ParseObject = CoreManager.getParseObject(); 8 | if (value instanceof ParseObject) { 9 | if (!arrayContainsObject(uniques, value as typeof ParseObject)) { 10 | uniques.push(value); 11 | } 12 | } else { 13 | if (uniques.indexOf(value) < 0) { 14 | uniques.push(value); 15 | } 16 | } 17 | }); 18 | return uniques; 19 | } 20 | -------------------------------------------------------------------------------- /src/unsavedChildren.ts: -------------------------------------------------------------------------------- 1 | import CoreManager from './CoreManager'; 2 | import ParseFile from './ParseFile'; 3 | import type ParseObject from './ParseObject'; 4 | import ParseRelation from './ParseRelation'; 5 | 6 | interface EncounterMap { 7 | objects: Record; 8 | files: ParseFile[]; 9 | } 10 | 11 | /** 12 | * Return an array of unsaved children, which are either Parse Objects or Files. 13 | * If it encounters any dirty Objects without Ids, it will throw an exception. 14 | * 15 | * @param {Parse.Object} obj 16 | * @param {boolean} allowDeepUnsaved 17 | * @returns {Array} 18 | */ 19 | export default function unsavedChildren( 20 | obj: ParseObject, 21 | allowDeepUnsaved?: boolean 22 | ): (ParseFile | ParseObject)[] { 23 | const encountered = { 24 | objects: {}, 25 | files: [], 26 | }; 27 | const identifier = obj.className + ':' + obj._getId(); 28 | encountered.objects[identifier] = obj.dirty() ? obj : true; 29 | const attributes = obj.attributes; 30 | for (const attr in attributes) { 31 | if (typeof attributes[attr] === 'object') { 32 | traverse(attributes[attr], encountered, false, !!allowDeepUnsaved); 33 | } 34 | } 35 | const unsaved = []; 36 | for (const id in encountered.objects) { 37 | if (id !== identifier && encountered.objects[id] !== true) { 38 | unsaved.push(encountered.objects[id]); 39 | } 40 | } 41 | return unsaved.concat(encountered.files); 42 | } 43 | 44 | function traverse( 45 | obj: ParseObject, 46 | encountered: EncounterMap, 47 | shouldThrow: boolean, 48 | allowDeepUnsaved: boolean 49 | ) { 50 | const ParseObject = CoreManager.getParseObject(); 51 | if (obj instanceof ParseObject) { 52 | if (!obj.id && shouldThrow) { 53 | throw new Error('Cannot create a pointer to an unsaved Object.'); 54 | } 55 | const identifier = obj.className + ':' + obj._getId(); 56 | if (!encountered.objects[identifier]) { 57 | encountered.objects[identifier] = obj.dirty() ? obj : true; 58 | const attributes = obj.attributes; 59 | for (const attr in attributes) { 60 | if (typeof attributes[attr] === 'object') { 61 | traverse(attributes[attr], encountered, !allowDeepUnsaved, allowDeepUnsaved); 62 | } 63 | } 64 | } 65 | return; 66 | } 67 | if (obj instanceof ParseFile) { 68 | if (!(obj as ParseFile).url() && encountered.files.indexOf(obj) < 0) { 69 | encountered.files.push(obj); 70 | } 71 | return; 72 | } 73 | if (obj instanceof ParseRelation) { 74 | return; 75 | } 76 | if (Array.isArray(obj)) { 77 | obj.forEach(el => { 78 | if (typeof el === 'object') { 79 | traverse(el, encountered, shouldThrow, allowDeepUnsaved); 80 | } 81 | }); 82 | } 83 | for (const k in obj) { 84 | if (typeof obj[k] === 'object') { 85 | traverse(obj[k], encountered, shouldThrow, allowDeepUnsaved); 86 | } 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /src/uuid.ts: -------------------------------------------------------------------------------- 1 | import { v4 } from 'uuid'; 2 | 3 | let uuid: () => string; 4 | 5 | if (process.env.PARSE_BUILD === 'weapp') { 6 | uuid = function () { 7 | const s: string[] = []; 8 | const hexDigits = '0123456789abcdef'; 9 | 10 | for (let i = 0; i < 36; i++) { 11 | s[i] = hexDigits.substr(Math.floor(Math.random() * 0x10), 1); 12 | } 13 | 14 | s[14] = '4'; // bits 12-15 of the time_hi_and_version field to 0010 15 | s[19] = hexDigits.substr((Number(s[19]) & 0x3) | 0x8, 1); // bits 6-7 of the clock_seq_hi_and_reserved to 01 16 | s[8] = s[13] = s[18] = s[23] = '-'; 17 | return s.join(''); 18 | }; 19 | } else { 20 | uuid = v4; 21 | } 22 | 23 | export default uuid; 24 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "target": "es2015", 5 | "lib": ["es2022", "dom"], 6 | "declaration": true, 7 | "emitDeclarationOnly": true, 8 | "outDir": "types", 9 | "noImplicitAny": false, 10 | "allowJs": false 11 | }, 12 | "include": [ 13 | "src/*.ts" 14 | ] 15 | } 16 | -------------------------------------------------------------------------------- /types/Analytics.d.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Parse.Analytics provides an interface to Parse's logging and analytics 3 | * backend. 4 | * 5 | * @class Parse.Analytics 6 | * @static 7 | * @hideconstructor 8 | */ 9 | /** 10 | * Tracks the occurrence of a custom event with additional dimensions. 11 | * Parse will store a data point at the time of invocation with the given 12 | * event name. 13 | * 14 | * Dimensions will allow segmentation of the occurrences of this custom 15 | * event. Keys and values should be {@code String}s, and will throw 16 | * otherwise. 17 | * 18 | * To track a user signup along with additional metadata, consider the 19 | * following: 20 | *
      21 |  * var dimensions = {
      22 |  *  gender: 'm',
      23 |  *  source: 'web',
      24 |  *  dayType: 'weekend'
      25 |  * };
      26 |  * Parse.Analytics.track('signup', dimensions);
      27 |  * 
      28 | * 29 | * There is a default limit of 8 dimensions per event tracked. 30 | * 31 | * @function track 32 | * @name Parse.Analytics.track 33 | * @param {string} name The name of the custom event to report to Parse as 34 | * having happened. 35 | * @param {object} dimensions The dictionary of information by which to 36 | * segment this event. 37 | * @returns {Promise} A promise that is resolved when the round-trip 38 | * to the server completes. 39 | */ 40 | export declare function track(name: string, dimensions: Record): Promise; 41 | -------------------------------------------------------------------------------- /types/AnonymousUtils.d.ts: -------------------------------------------------------------------------------- 1 | import ParseUser from './ParseUser'; 2 | import type { RequestOptions } from './RESTController'; 3 | /** 4 | * Provides utility functions for working with Anonymously logged-in users.
      5 | * Anonymous users have some unique characteristics: 6 | *
        7 | *
      • Anonymous users don't need a user name or password.
      • 8 | *
          9 | *
        • Once logged out, an anonymous user cannot be recovered.
        • 10 | *
        11 | *
      • signUp converts an anonymous user to a standard user with the given username and password.
      • 12 | *
          13 | *
        • Data associated with the anonymous user is retained.
        • 14 | *
        15 | *
      • logIn switches users without converting the anonymous user.
      • 16 | *
          17 | *
        • Data associated with the anonymous user will be lost.
        • 18 | *
        19 | *
      • Service logIn (e.g. Facebook, Twitter) will attempt to convert 20 | * the anonymous user into a standard user by linking it to the service.
      • 21 | *
          22 | *
        • If a user already exists that is linked to the service, it will instead switch to the existing user.
        • 23 | *
        24 | *
      • Service linking (e.g. Facebook, Twitter) will convert the anonymous user 25 | * into a standard user by linking it to the service.
      • 26 | *
      27 | * 28 | * @class Parse.AnonymousUtils 29 | * @static 30 | */ 31 | declare const AnonymousUtils: { 32 | /** 33 | * Gets whether the user has their account linked to anonymous user. 34 | * 35 | * @function isLinked 36 | * @name Parse.AnonymousUtils.isLinked 37 | * @param {Parse.User} user User to check for. 38 | * The user must be logged in on this device. 39 | * @returns {boolean} true if the user has their account 40 | * linked to an anonymous user. 41 | * @static 42 | */ 43 | isLinked(user: ParseUser): boolean; 44 | /** 45 | * Logs in a user Anonymously. 46 | * 47 | * @function logIn 48 | * @name Parse.AnonymousUtils.logIn 49 | * @param {object} options MasterKey / SessionToken. 50 | * @returns {Promise} Logged in user 51 | * @static 52 | */ 53 | logIn(options?: RequestOptions): Promise; 54 | /** 55 | * Links Anonymous User to an existing PFUser. 56 | * 57 | * @function link 58 | * @name Parse.AnonymousUtils.link 59 | * @param {Parse.User} user User to link. This must be the current user. 60 | * @param {object} options MasterKey / SessionToken. 61 | * @returns {Promise} Linked with User 62 | * @static 63 | */ 64 | link(user: ParseUser, options?: RequestOptions): Promise; 65 | /** 66 | * Returns true if Authentication Provider has been registered for use. 67 | * 68 | * @function isRegistered 69 | * @name Parse.AnonymousUtils.isRegistered 70 | * @returns {boolean} 71 | * @static 72 | */ 73 | isRegistered(): boolean; 74 | _getAuthProvider(): { 75 | restoreAuthentication(): boolean; 76 | getAuthType(): string; 77 | getAuthData(): { 78 | authData: { 79 | id: string; 80 | }; 81 | }; 82 | }; 83 | }; 84 | export default AnonymousUtils; 85 | -------------------------------------------------------------------------------- /types/Cloud.d.ts: -------------------------------------------------------------------------------- 1 | import ParseObject from './ParseObject'; 2 | import type { RequestOptions } from './RESTController'; 3 | /** 4 | * Contains functions for calling and declaring 5 | * cloud functions. 6 | *

      7 | * Some functions are only available from Cloud Code. 8 | *

      9 | * 10 | * @class Parse.Cloud 11 | * @static 12 | * @hideconstructor 13 | */ 14 | /** 15 | * Makes a call to a cloud function. 16 | * 17 | * @function run 18 | * @name Parse.Cloud.run 19 | * @param {string} name The function name. 20 | * @param {object} data The parameters to send to the cloud function. 21 | * @param {object} options 22 | * Valid options are:
        23 | *
      • useMasterKey: In Cloud Code and Node only, causes the Master Key to 24 | * be used for this request. 25 | *
      • sessionToken: A valid session token, used for making a request on 26 | * behalf of a specific user. 27 | *
      • installationId: the installationId which made the request 28 | *
      • context: A dictionary that is accessible in Cloud Code triggers. 29 | *
      30 | * @returns {Promise} A promise that will be resolved with the result 31 | * of the function. 32 | */ 33 | export declare function run any>(name: string, data?: null, options?: RequestOptions): Promise>; 34 | export declare function run[0]]: Parameters[0][P]; 36 | }) => any>(name: string, data: Parameters[0], options?: RequestOptions): Promise>; 37 | /** 38 | * Gets data for the current set of cloud jobs. 39 | * 40 | * @function getJobsData 41 | * @name Parse.Cloud.getJobsData 42 | * @returns {Promise} A promise that will be resolved with the result 43 | * of the function. 44 | */ 45 | export declare function getJobsData(): Promise; 46 | /** 47 | * Starts a given cloud job, which will process asynchronously. 48 | * 49 | * @function startJob 50 | * @name Parse.Cloud.startJob 51 | * @param {string} name The function name. 52 | * @param {object} data The parameters to send to the cloud function. 53 | * @returns {Promise} A promise that will be resolved with the jobStatusId 54 | * of the job. 55 | */ 56 | export declare function startJob(name: string, data: any): Promise; 57 | /** 58 | * Gets job status by Id 59 | * 60 | * @function getJobStatus 61 | * @name Parse.Cloud.getJobStatus 62 | * @param {string} jobStatusId The Id of Job Status. 63 | * @returns {Parse.Object} Status of Job. 64 | */ 65 | export declare function getJobStatus(jobStatusId: string): Promise; 66 | -------------------------------------------------------------------------------- /types/CryptoController.d.ts: -------------------------------------------------------------------------------- 1 | declare const CryptoController: { 2 | encrypt(obj: any, secretKey: string): string; 3 | decrypt(encryptedText: string, secretKey: string): string; 4 | }; 5 | export default CryptoController; 6 | -------------------------------------------------------------------------------- /types/EventEmitter.d.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This is a simple wrapper to unify EventEmitter implementations across platforms. 3 | */ 4 | declare let EventEmitter: any; 5 | export default EventEmitter; 6 | -------------------------------------------------------------------------------- /types/IndexedDBStorageController.d.ts: -------------------------------------------------------------------------------- 1 | declare let IndexedDBStorageController: any; 2 | export default IndexedDBStorageController; 3 | -------------------------------------------------------------------------------- /types/InstallationController.d.ts: -------------------------------------------------------------------------------- 1 | import ParseInstallation from './ParseInstallation'; 2 | declare const InstallationController: { 3 | updateInstallationOnDisk(installation: ParseInstallation): Promise; 4 | currentInstallationId(): Promise; 5 | currentInstallation(): Promise; 6 | _clearCache(): void; 7 | _setInstallationIdCache(iid: string): void; 8 | _setCurrentInstallationCache(installation: ParseInstallation, matchesDisk?: boolean): void; 9 | }; 10 | export default InstallationController; 11 | -------------------------------------------------------------------------------- /types/LiveQuerySubscription.d.ts: -------------------------------------------------------------------------------- 1 | import type ParseQuery from './ParseQuery'; 2 | import type { EventEmitter } from 'events'; 3 | /** 4 | * Creates a new LiveQuery Subscription. 5 | * cloud functions. 6 | * 7 | *

      Response Object - Contains data from the client that made the request 8 | *

        9 | *
      • clientId
      • 10 | *
      • installationId - requires Parse Server 4.0.0+
      • 11 | *
      12 | *

      13 | * 14 | *

      Open Event - When you call query.subscribe(), we send a subscribe request to 15 | * the LiveQuery server, when we get the confirmation from the LiveQuery server, 16 | * this event will be emitted. When the client loses WebSocket connection to the 17 | * LiveQuery server, we will try to auto reconnect the LiveQuery server. If we 18 | * reconnect the LiveQuery server and successfully resubscribe the ParseQuery, 19 | * you'll also get this event. 20 | * 21 | *

       22 |  * subscription.on('open', (response) => {
       23 |  *
       24 |  * });

      25 | * 26 | *

      Create Event - When a new ParseObject is created and it fulfills the ParseQuery you subscribe, 27 | * you'll get this event. The object is the ParseObject which is created. 28 | * 29 | *

       30 |  * subscription.on('create', (object, response) => {
       31 |  *
       32 |  * });

      33 | * 34 | *

      Update Event - When an existing ParseObject (original) which fulfills the ParseQuery you subscribe 35 | * is updated (The ParseObject fulfills the ParseQuery before and after changes), 36 | * you'll get this event. The object is the ParseObject which is updated. 37 | * Its content is the latest value of the ParseObject. 38 | * 39 | * Parse-Server 3.1.3+ Required for original object parameter 40 | * 41 | *

       42 |  * subscription.on('update', (object, original, response) => {
       43 |  *
       44 |  * });

      45 | * 46 | *

      Enter Event - When an existing ParseObject's (original) old value doesn't fulfill the ParseQuery 47 | * but its new value fulfills the ParseQuery, you'll get this event. The object is the 48 | * ParseObject which enters the ParseQuery. Its content is the latest value of the ParseObject. 49 | * 50 | * Parse-Server 3.1.3+ Required for original object parameter 51 | * 52 | *

       53 |  * subscription.on('enter', (object, original, response) => {
       54 |  *
       55 |  * });

      56 | * 57 | * 58 | *

      Update Event - When an existing ParseObject's old value fulfills the ParseQuery but its new value 59 | * doesn't fulfill the ParseQuery, you'll get this event. The object is the ParseObject 60 | * which leaves the ParseQuery. Its content is the latest value of the ParseObject. 61 | * 62 | *

       63 |  * subscription.on('leave', (object, response) => {
       64 |  *
       65 |  * });

      66 | * 67 | * 68 | *

      Delete Event - When an existing ParseObject which fulfills the ParseQuery is deleted, you'll 69 | * get this event. The object is the ParseObject which is deleted. 70 | * 71 | *

       72 |  * subscription.on('delete', (object, response) => {
       73 |  *
       74 |  * });

      75 | * 76 | * 77 | *

      Close Event - When the client loses the WebSocket connection to the LiveQuery 78 | * server and we stop receiving events, you'll get this event. 79 | * 80 | *

       81 |  * subscription.on('close', () => {
       82 |  *
       83 |  * });

      84 | */ 85 | declare class LiveQuerySubscription { 86 | id: string | number; 87 | query: ParseQuery; 88 | sessionToken?: string; 89 | subscribePromise: any; 90 | unsubscribePromise: any; 91 | subscribed: boolean; 92 | emitter: EventEmitter; 93 | on: EventEmitter['on']; 94 | emit: EventEmitter['emit']; 95 | constructor(id: string | number, query: ParseQuery, sessionToken?: string); 96 | /** 97 | * Close the subscription 98 | * 99 | * @returns {Promise} 100 | */ 101 | unsubscribe(): Promise; 102 | } 103 | export default LiveQuerySubscription; 104 | -------------------------------------------------------------------------------- /types/LocalDatastore.d.ts: -------------------------------------------------------------------------------- 1 | import type ParseObject from './ParseObject'; 2 | /** 3 | * Provides a local datastore which can be used to store and retrieve Parse.Object.
      4 | * To enable this functionality, call Parse.enableLocalDatastore(). 5 | * 6 | * Pin object to add to local datastore 7 | * 8 | *
      await object.pin();
      9 | *
      await object.pinWithName('pinName');
      10 | * 11 | * Query pinned objects 12 | * 13 | *
      query.fromLocalDatastore();
      14 | *
      query.fromPin();
      15 | *
      query.fromPinWithName();
      16 | * 17 | *
      const localObjects = await query.find();
      18 | * 19 | * @class Parse.LocalDatastore 20 | * @static 21 | */ 22 | declare const LocalDatastore: { 23 | isEnabled: boolean; 24 | isSyncing: boolean; 25 | fromPinWithName(name: string): Promise; 26 | pinWithName(name: string, value: any): Promise; 27 | unPinWithName(name: string): Promise; 28 | _getAllContents(): Promise; 29 | _getRawStorage(): Promise; 30 | _clear(): Promise; 31 | _handlePinAllWithName(name: string, objects: ParseObject[]): Promise; 32 | _handleUnPinAllWithName(name: string, objects: ParseObject[]): Promise; 33 | _getChildren(object: ParseObject): any; 34 | _traverse(object: any, encountered: any): void; 35 | _serializeObjectsFromPinName(name: string): Promise; 36 | _serializeObject(objectKey: string, localDatastore: any): Promise; 37 | _updateObjectIfPinned(object: ParseObject): Promise; 38 | _destroyObjectIfPinned(object: ParseObject): Promise; 39 | _updateLocalIdForObject(localId: string, object: ParseObject): Promise; 40 | /** 41 | * Updates Local Datastore from Server 42 | * 43 | *
      44 |      * await Parse.LocalDatastore.updateFromServer();
      45 |      * 
      46 | * 47 | * @function updateFromServer 48 | * @name Parse.LocalDatastore.updateFromServer 49 | * @static 50 | */ 51 | updateFromServer(): Promise; 52 | getKeyForObject(object: any): string; 53 | getPinName(pinName?: string): string; 54 | checkIfEnabled(): any; 55 | }; 56 | export default LocalDatastore; 57 | -------------------------------------------------------------------------------- /types/LocalDatastoreController.d.ts: -------------------------------------------------------------------------------- 1 | declare let LocalDatastoreController: any; 2 | export default LocalDatastoreController; 3 | -------------------------------------------------------------------------------- /types/LocalDatastoreController.default.d.ts: -------------------------------------------------------------------------------- 1 | declare const LocalDatastoreController: { 2 | fromPinWithName(name: string): Promise; 3 | pinWithName(name: string, value: any): Promise; 4 | unPinWithName(name: string): Promise; 5 | getAllContents(): Promise; 6 | getRawStorage(): Promise; 7 | clear(): Promise; 8 | }; 9 | export default LocalDatastoreController; 10 | -------------------------------------------------------------------------------- /types/LocalDatastoreController.react-native.d.ts: -------------------------------------------------------------------------------- 1 | declare const LocalDatastoreController: { 2 | fromPinWithName(name: string): Promise; 3 | pinWithName(name: string, value: any): Promise; 4 | unPinWithName(name: string): Promise; 5 | getAllContents(): Promise; 6 | getRawStorage(): Promise; 7 | clear(): Promise; 8 | }; 9 | export default LocalDatastoreController; 10 | -------------------------------------------------------------------------------- /types/LocalDatastoreUtils.d.ts: -------------------------------------------------------------------------------- 1 | declare const DEFAULT_PIN = "_default"; 2 | declare const PIN_PREFIX = "parsePin_"; 3 | declare const OBJECT_PREFIX = "Parse_LDS_"; 4 | declare function isLocalDatastoreKey(key: string): boolean; 5 | export { DEFAULT_PIN, PIN_PREFIX, OBJECT_PREFIX, isLocalDatastoreKey }; 6 | -------------------------------------------------------------------------------- /types/ObjectStateMutations.d.ts: -------------------------------------------------------------------------------- 1 | import TaskQueue from './TaskQueue'; 2 | import type { Op } from './ParseOp'; 3 | import type ParseObject from './ParseObject'; 4 | export type AttributeMap = Record; 5 | export type OpsMap = Record; 6 | export type ObjectCache = Record; 7 | export interface State { 8 | serverData: AttributeMap; 9 | pendingOps: OpsMap[]; 10 | objectCache: ObjectCache; 11 | tasks: TaskQueue; 12 | existed: boolean; 13 | } 14 | export declare function defaultState(): State; 15 | export declare function setServerData(serverData: AttributeMap, attributes: AttributeMap): void; 16 | export declare function setPendingOp(pendingOps: OpsMap[], attr: string, op?: Op): void; 17 | export declare function pushPendingState(pendingOps: OpsMap[]): void; 18 | export declare function popPendingState(pendingOps: OpsMap[]): OpsMap; 19 | export declare function mergeFirstPendingState(pendingOps: OpsMap[]): void; 20 | export declare function estimateAttribute(serverData: AttributeMap, pendingOps: OpsMap[], object: ParseObject, attr: string): any; 21 | export declare function estimateAttributes(serverData: AttributeMap, pendingOps: OpsMap[], object: ParseObject): AttributeMap; 22 | export declare function commitServerChanges(serverData: AttributeMap, objectCache: ObjectCache, changes: AttributeMap): void; 23 | -------------------------------------------------------------------------------- /types/OfflineQuery.d.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * matchesQuery -- Determines if an object would be returned by a Parse Query 3 | * It's a lightweight, where-clause only implementation of a full query engine. 4 | * Since we find queries that match objects, rather than objects that match 5 | * queries, we can avoid building a full-blown query tool. 6 | * 7 | * @param className 8 | * @param object 9 | * @param objects 10 | * @param query 11 | * @private 12 | * @returns {boolean} 13 | */ 14 | declare function matchesQuery(className: any, object: any, objects: any, query: any): boolean; 15 | declare function validateQuery(query: any): void; 16 | declare const OfflineQuery: { 17 | matchesQuery: typeof matchesQuery; 18 | validateQuery: typeof validateQuery; 19 | }; 20 | export default OfflineQuery; 21 | -------------------------------------------------------------------------------- /types/ParseConfig.d.ts: -------------------------------------------------------------------------------- 1 | import type { RequestOptions } from './RESTController'; 2 | /** 3 | * Parse.Config is a local representation of configuration data that 4 | * can be set from the Parse dashboard. 5 | * 6 | * @alias Parse.Config 7 | */ 8 | declare class ParseConfig { 9 | attributes: Record; 10 | _escapedAttributes: Record; 11 | constructor(); 12 | /** 13 | * Gets the value of an attribute. 14 | * 15 | * @param {string} attr The name of an attribute. 16 | * @returns {*} 17 | */ 18 | get(attr: string): any; 19 | /** 20 | * Gets the HTML-escaped value of an attribute. 21 | * 22 | * @param {string} attr The name of an attribute. 23 | * @returns {string} 24 | */ 25 | escape(attr: string): string; 26 | /** 27 | * Retrieves the most recently-fetched configuration object, either from 28 | * memory or from local storage if necessary. 29 | * 30 | * @static 31 | * @returns {Parse.Config} The most recently-fetched Parse.Config if it 32 | * exists, else an empty Parse.Config. 33 | */ 34 | static current(): ParseConfig | Promise; 35 | /** 36 | * Gets a new configuration object from the server. 37 | * 38 | * @static 39 | * @param {object} options 40 | * Valid options are:
        41 | *
      • useMasterKey: In Cloud Code and Node only, causes the Master Key to 42 | * be used for this request. 43 | *
      44 | * @returns {Promise} A promise that is resolved with a newly-created 45 | * configuration object when the get completes. 46 | */ 47 | static get(options?: RequestOptions): Promise; 48 | /** 49 | * Save value keys to the server. 50 | * 51 | * @static 52 | * @param {object} attrs The config parameters and values. 53 | * @param {object} masterKeyOnlyFlags The flags that define whether config parameters listed 54 | * in `attrs` should be retrievable only by using the master key. 55 | * For example: `param1: true` makes `param1` only retrievable by using the master key. 56 | * If a parameter is not provided or set to `false`, it can be retrieved without 57 | * using the master key. 58 | * @returns {Promise} A promise that is resolved with a newly-created 59 | * configuration object or with the current with the update. 60 | */ 61 | static save(attrs: Record, masterKeyOnlyFlags: Record): Promise; 62 | /** 63 | * Used for testing 64 | * 65 | * @private 66 | */ 67 | static _clearCache(): void; 68 | } 69 | export default ParseConfig; 70 | -------------------------------------------------------------------------------- /types/ParseHooks.d.ts: -------------------------------------------------------------------------------- 1 | export type HookDeclaration = { 2 | functionName: string; 3 | url: string; 4 | } | { 5 | className: string; 6 | triggerName: string; 7 | url: string; 8 | }; 9 | export type HookDeleteArg = { 10 | functionName: string; 11 | } | { 12 | className: string; 13 | triggerName: string; 14 | }; 15 | export declare function getFunctions(): Promise; 16 | export declare function getTriggers(): Promise; 17 | export declare function getFunction(name: string): Promise; 18 | export declare function getTrigger(className: string, triggerName: string): Promise; 19 | export declare function createFunction(functionName: string, url: string): Promise; 20 | export declare function createTrigger(className: string, triggerName: string, url: string): Promise; 21 | export declare function create(hook: HookDeclaration): Promise; 22 | export declare function updateFunction(functionName: string, url: string): Promise; 23 | export declare function updateTrigger(className: string, triggerName: string, url: string): Promise; 24 | export declare function update(hook: HookDeclaration): Promise; 25 | export declare function removeFunction(functionName: string): Promise; 26 | export declare function removeTrigger(className: string, triggerName: string): Promise; 27 | export declare function remove(hook: HookDeleteArg): Promise; 28 | -------------------------------------------------------------------------------- /types/ParseLiveQuery.d.ts: -------------------------------------------------------------------------------- 1 | import type { EventEmitter } from 'events'; 2 | /** 3 | * We expose three events to help you monitor the status of the WebSocket connection: 4 | * 5 | *

      Open - When we establish the WebSocket connection to the LiveQuery server, you'll get this event. 6 | * 7 | *

       8 |  * Parse.LiveQuery.on('open', () => {
       9 |  *
      10 |  * });

      11 | * 12 | *

      Close - When we lose the WebSocket connection to the LiveQuery server, you'll get this event. 13 | * 14 | *

      15 |  * Parse.LiveQuery.on('close', () => {
      16 |  *
      17 |  * });

      18 | * 19 | *

      Error - When some network error or LiveQuery server error happens, you'll get this event. 20 | * 21 | *

      22 |  * Parse.LiveQuery.on('error', (error) => {
      23 |  *
      24 |  * });

      25 | * 26 | * @class Parse.LiveQuery 27 | * @static 28 | */ 29 | declare class LiveQuery { 30 | emitter: EventEmitter; 31 | on: EventEmitter['on']; 32 | emit: EventEmitter['emit']; 33 | constructor(); 34 | /** 35 | * After open is called, the LiveQuery will try to send a connect request 36 | * to the LiveQuery server. 37 | */ 38 | open(): Promise; 39 | /** 40 | * When you're done using LiveQuery, you can call Parse.LiveQuery.close(). 41 | * This function will close the WebSocket connection to the LiveQuery server, 42 | * cancel the auto reconnect, and unsubscribe all subscriptions based on it. 43 | * If you call query.subscribe() after this, we'll create a new WebSocket 44 | * connection to the LiveQuery server. 45 | */ 46 | close(): Promise; 47 | } 48 | export default LiveQuery; 49 | -------------------------------------------------------------------------------- /types/ParseOp.d.ts: -------------------------------------------------------------------------------- 1 | import type ParseObject from './ParseObject'; 2 | import ParseRelation from './ParseRelation'; 3 | export declare function opFromJSON(json: Record): Op | null; 4 | export declare class Op { 5 | applyTo(value: any): any; 6 | mergeWith(previous: Op): Op | void; 7 | toJSON(offline?: boolean): any; 8 | } 9 | export declare class SetOp extends Op { 10 | _value: any; 11 | constructor(value: any); 12 | applyTo(): any; 13 | mergeWith(): SetOp; 14 | toJSON(offline?: boolean): any; 15 | } 16 | export declare class UnsetOp extends Op { 17 | applyTo(): any; 18 | mergeWith(): UnsetOp; 19 | toJSON(): { 20 | __op: string; 21 | }; 22 | } 23 | export declare class IncrementOp extends Op { 24 | _amount: number; 25 | constructor(amount: number); 26 | applyTo(value: any): number; 27 | mergeWith(previous: Op): Op; 28 | toJSON(): { 29 | __op: string; 30 | amount: number; 31 | }; 32 | } 33 | export declare class AddOp extends Op { 34 | _value: any[]; 35 | constructor(value: any | any[]); 36 | applyTo(value: any): any[]; 37 | mergeWith(previous: Op): Op; 38 | toJSON(): { 39 | __op: string; 40 | objects: any; 41 | }; 42 | } 43 | export declare class AddUniqueOp extends Op { 44 | _value: any[]; 45 | constructor(value: any | any[]); 46 | applyTo(value: any | any[]): any[]; 47 | mergeWith(previous: Op): Op; 48 | toJSON(): { 49 | __op: string; 50 | objects: any; 51 | }; 52 | } 53 | export declare class RemoveOp extends Op { 54 | _value: any[]; 55 | constructor(value: any | any[]); 56 | applyTo(value: any | any[]): any[]; 57 | mergeWith(previous: Op): Op; 58 | toJSON(): { 59 | __op: string; 60 | objects: any; 61 | }; 62 | } 63 | export declare class RelationOp extends Op { 64 | _targetClassName: string | null; 65 | relationsToAdd: string[]; 66 | relationsToRemove: string[]; 67 | constructor(adds: (ParseObject | string)[], removes: (ParseObject | string)[]); 68 | _extractId(obj: string | ParseObject): string; 69 | applyTo(value: any, parent?: ParseObject, key?: string): ParseRelation; 70 | mergeWith(previous: Op): Op; 71 | toJSON(): { 72 | __op?: string; 73 | objects?: any; 74 | ops?: any; 75 | }; 76 | } 77 | -------------------------------------------------------------------------------- /types/ParsePolygon.d.ts: -------------------------------------------------------------------------------- 1 | import ParseGeoPoint from './ParseGeoPoint'; 2 | type Coordinate = [number, number]; 3 | type Coordinates = Coordinate[]; 4 | /** 5 | * Creates a new Polygon with any of the following forms:
      6 | *
       7 |  *   new Polygon([[0,0],[0,1],[1,1],[1,0]])
       8 |  *   new Polygon([GeoPoint, GeoPoint, GeoPoint])
       9 |  *   
      10 | * 11 | *

      Represents a coordinates that may be associated 12 | * with a key in a ParseObject or used as a reference point for geo queries. 13 | * This allows proximity-based queries on the key.

      14 | * 15 | *

      Example:

      16 |  *   var polygon = new Parse.Polygon([[0,0],[0,1],[1,1],[1,0]]);
      17 |  *   var object = new Parse.Object("PlaceObject");
      18 |  *   object.set("area", polygon);
      19 |  *   object.save();

      20 | * 21 | * @alias Parse.Polygon 22 | */ 23 | declare class ParsePolygon { 24 | _coordinates: Coordinates; 25 | /** 26 | * @param {(Coordinates | Parse.GeoPoint[])} coordinates An Array of coordinate pairs 27 | */ 28 | constructor(coordinates: Coordinates | ParseGeoPoint[]); 29 | /** 30 | * Coordinates value for this Polygon. 31 | * Throws an exception if not valid type. 32 | * 33 | * @property {(Coordinates | Parse.GeoPoint[])} coordinates list of coordinates 34 | * @returns {Coordinates} 35 | */ 36 | get coordinates(): Coordinates; 37 | set coordinates(coords: Coordinates | ParseGeoPoint[]); 38 | /** 39 | * Returns a JSON representation of the Polygon, suitable for Parse. 40 | * 41 | * @returns {object} 42 | */ 43 | toJSON(): { 44 | __type: string; 45 | coordinates: Coordinates; 46 | }; 47 | /** 48 | * Checks if two polygons are equal 49 | * 50 | * @param {(Parse.Polygon | object)} other 51 | * @returns {boolean} 52 | */ 53 | equals(other: ParsePolygon | any): boolean; 54 | /** 55 | * 56 | * @param {Parse.GeoPoint} point 57 | * @returns {boolean} Returns if the point is contained in the polygon 58 | */ 59 | containsPoint(point: ParseGeoPoint): boolean; 60 | /** 61 | * Validates that the list of coordinates can form a valid polygon 62 | * 63 | * @param {Array} coords the list of coordinates to validate as a polygon 64 | * @throws {TypeError} 65 | * @returns {number[][]} Array of coordinates if validated. 66 | */ 67 | static _validate(coords: Coordinates | ParseGeoPoint[]): Coordinates; 68 | } 69 | export default ParsePolygon; 70 | -------------------------------------------------------------------------------- /types/ParseRelation.d.ts: -------------------------------------------------------------------------------- 1 | import type ParseObject from './ParseObject'; 2 | import type ParseQuery from './ParseQuery'; 3 | /** 4 | * Creates a new Relation for the given parent object and key. This 5 | * constructor should rarely be used directly, but rather created by 6 | * Parse.Object.relation. 7 | * 8 | *

      9 | * A class that is used to access all of the children of a many-to-many 10 | * relationship. Each instance of Parse.Relation is associated with a 11 | * particular parent object and key. 12 | *

      13 | * 14 | * @alias Parse.Relation 15 | */ 16 | declare class ParseRelation { 17 | parent?: S; 18 | key?: string; 19 | targetClassName?: string | null; 20 | /** 21 | * @param {Parse.Object} parent The parent of this relation. 22 | * @param {string} key The key for this relation on the parent. 23 | */ 24 | constructor(parent?: S, key?: string); 25 | _ensureParentAndKey(parent: S, key: string): void; 26 | /** 27 | * Adds a Parse.Object or an array of Parse.Objects to the relation. 28 | * 29 | * @param {(Parse.Object|Array)} objects The item or items to add. 30 | * @returns {Parse.Object} The parent of the relation. 31 | */ 32 | add(objects: T | T[]): S; 33 | /** 34 | * Removes a Parse.Object or an array of Parse.Objects from this relation. 35 | * 36 | * @param {(Parse.Object|Array)} objects The item or items to remove. 37 | */ 38 | remove(objects: T | T[]): void; 39 | /** 40 | * Returns a JSON version of the object suitable for saving to disk. 41 | * 42 | * @returns {object} JSON representation of Relation 43 | */ 44 | toJSON(): { 45 | __type: 'Relation'; 46 | className: string | null; 47 | }; 48 | /** 49 | * Returns a Parse.Query that is limited to objects in this 50 | * relation. 51 | * 52 | * @returns {Parse.Query} Relation Query 53 | */ 54 | query(): ParseQuery; 55 | } 56 | export default ParseRelation; 57 | -------------------------------------------------------------------------------- /types/ParseRole.d.ts: -------------------------------------------------------------------------------- 1 | import ParseACL from './ParseACL'; 2 | import ParseError from './ParseError'; 3 | import ParseObject, { Attributes, SetOptions } from './ParseObject'; 4 | import type { AttributeMap } from './ObjectStateMutations'; 5 | import type ParseRelation from './ParseRelation'; 6 | import type ParseUser from './ParseUser'; 7 | /** 8 | * Represents a Role on the Parse server. Roles represent groupings of 9 | * Users for the purposes of granting permissions (e.g. specifying an ACL 10 | * for an Object). Roles are specified by their sets of child users and 11 | * child roles, all of which are granted any permissions that the parent 12 | * role has. 13 | * 14 | *

      Roles must have a name (which cannot be changed after creation of the 15 | * role), and must specify an ACL.

      16 | * 17 | * @alias Parse.Role 18 | * @augments Parse.Object 19 | */ 20 | declare class ParseRole extends ParseObject { 21 | /** 22 | * @param {string} name The name of the Role to create. 23 | * @param {Parse.ACL} acl The ACL for this role. Roles must have an ACL. 24 | * A Parse.Role is a local representation of a role persisted to the Parse 25 | * cloud. 26 | */ 27 | constructor(name: string, acl: ParseACL); 28 | /** 29 | * Gets the name of the role. You can alternatively call role.get("name") 30 | * 31 | * @returns {string} the name of the role. 32 | */ 33 | getName(): string | null; 34 | /** 35 | * Sets the name for a role. This value must be set before the role has 36 | * been saved to the server, and cannot be set once the role has been 37 | * saved. 38 | * 39 | *

      40 | * A role's name can only contain alphanumeric characters, _, -, and 41 | * spaces. 42 | *

      43 | * 44 | *

      This is equivalent to calling role.set("name", name)

      45 | * 46 | * @param {string} name The name of the role. 47 | * @param {object} options Standard options object with success and error 48 | * callbacks. 49 | * @returns {Parse.Object} Returns the object, so you can chain this call. 50 | */ 51 | setName(name: string, options?: SetOptions): this; 52 | /** 53 | * Gets the Parse.Relation for the Parse.Users that are direct 54 | * children of this role. These users are granted any privileges that this 55 | * role has been granted (e.g. read or write access through ACLs). You can 56 | * add or remove users from the role through this relation. 57 | * 58 | *

      This is equivalent to calling role.relation("users")

      59 | * 60 | * @returns {Parse.Relation} the relation for the users belonging to this 61 | * role. 62 | */ 63 | getUsers(): ParseRelation; 64 | /** 65 | * Gets the Parse.Relation for the Parse.Roles that are direct 66 | * children of this role. These roles' users are granted any privileges that 67 | * this role has been granted (e.g. read or write access through ACLs). You 68 | * can add or remove child roles from this role through this relation. 69 | * 70 | *

      This is equivalent to calling role.relation("roles")

      71 | * 72 | * @returns {Parse.Relation} the relation for the roles belonging to this 73 | * role. 74 | */ 75 | getRoles(): ParseRelation; 76 | _validateName(newName: any): void; 77 | validate(attrs: AttributeMap, options?: any): ParseError | boolean; 78 | } 79 | export default ParseRole; 80 | -------------------------------------------------------------------------------- /types/ParseSession.d.ts: -------------------------------------------------------------------------------- 1 | import ParseObject, { Attributes } from './ParseObject'; 2 | import type { FullOptions } from './RESTController'; 3 | /** 4 | *

      A Parse.Session object is a local representation of a revocable session. 5 | * This class is a subclass of a Parse.Object, and retains the same 6 | * functionality of a Parse.Object.

      7 | * 8 | * @alias Parse.Session 9 | * @augments Parse.Object 10 | */ 11 | declare class ParseSession extends ParseObject { 12 | /** 13 | * @param {object} attributes The initial set of data to store in the user. 14 | */ 15 | constructor(attributes?: T); 16 | /** 17 | * Returns the session token string. 18 | * 19 | * @returns {string} 20 | */ 21 | getSessionToken(): string; 22 | static readOnlyAttributes(): string[]; 23 | /** 24 | * Retrieves the Session object for the currently logged in session. 25 | * 26 | * @param {object} options useMasterKey 27 | * @static 28 | * @returns {Promise} A promise that is resolved with the Parse.Session 29 | * object after it has been fetched. If there is no current user, the 30 | * promise will be rejected. 31 | */ 32 | static current(options?: FullOptions): Promise; 33 | /** 34 | * Determines whether the current session token is revocable. 35 | * This method is useful for migrating Express.js or Node.js web apps to 36 | * use revocable sessions. If you are migrating an app that uses the Parse 37 | * SDK in the browser only, please use Parse.User.enableRevocableSession() 38 | * instead, so that sessions can be automatically upgraded. 39 | * 40 | * @static 41 | * @returns {boolean} 42 | */ 43 | static isCurrentSessionRevocable(): boolean; 44 | } 45 | export default ParseSession; 46 | -------------------------------------------------------------------------------- /types/Push.d.ts: -------------------------------------------------------------------------------- 1 | import ParseQuery from './ParseQuery'; 2 | import type ParseObject from './ParseObject'; 3 | import type { WhereClause } from './ParseQuery'; 4 | import type { FullOptions } from './RESTController'; 5 | export interface PushData { 6 | where?: WhereClause | ParseQuery; 7 | push_time?: Date | string; 8 | expiration_time?: Date | string; 9 | expiration_interval?: number; 10 | data?: any; 11 | channels?: string[]; 12 | } 13 | /** 14 | * Contains functions to deal with Push in Parse. 15 | * 16 | * @class Parse.Push 17 | * @static 18 | * @hideconstructor 19 | */ 20 | /** 21 | * Sends a push notification. 22 | * **Available in Cloud Code only.** 23 | * 24 | * See {@link https://docs.parseplatform.org/js/guide/#push-notifications Push Notification Guide} 25 | * 26 | * @function send 27 | * @name Parse.Push.send 28 | * @param {object} data - The data of the push notification. Valid fields 29 | * are: 30 | *
        31 | *
      1. channels - An Array of channels to push to.
      2. 32 | *
      3. push_time - A Date object for when to send the push.
      4. 33 | *
      5. expiration_time - A Date object for when to expire 34 | * the push.
      6. 35 | *
      7. expiration_interval - The seconds from now to expire the push.
      8. 36 | *
      9. where - A Parse.Query over Parse.Installation that is used to match 37 | * a set of installations to push to.
      10. 38 | *
      11. data - The data to send as part of the push.
      12. 39 | *
          40 | * @param {object} options Valid options 41 | * are:
            42 | *
          • useMasterKey: In Cloud Code and Node only, causes the Master Key to 43 | * be used for this request. 44 | *
          45 | * @returns {Promise} A promise that is fulfilled when the push request 46 | * completes and returns `pushStatusId`. 47 | */ 48 | export declare function send(data: PushData, options?: FullOptions): Promise; 49 | /** 50 | * Gets push status by Id 51 | * 52 | * @function getPushStatus 53 | * @name Parse.Push.getPushStatus 54 | * @param {string} pushStatusId The Id of Push Status. 55 | * @param {object} options Valid options 56 | * are:
            57 | *
          • useMasterKey: In Cloud Code and Node only, causes the Master Key to 58 | * be used for this request. 59 | *
          60 | * @returns {Parse.Object} Status of Push. 61 | */ 62 | export declare function getPushStatus(pushStatusId: string, options?: FullOptions): Promise; 63 | -------------------------------------------------------------------------------- /types/RESTController.d.ts: -------------------------------------------------------------------------------- 1 | export interface RequestOptions { 2 | useMasterKey?: boolean; 3 | useMaintenanceKey?: boolean; 4 | sessionToken?: string; 5 | installationId?: string; 6 | returnStatus?: boolean; 7 | batchSize?: number; 8 | include?: any; 9 | progress?: any; 10 | context?: any; 11 | usePost?: boolean; 12 | ignoreEmailVerification?: boolean; 13 | transaction?: boolean; 14 | } 15 | export interface FullOptions { 16 | success?: any; 17 | error?: any; 18 | useMasterKey?: boolean; 19 | useMaintenanceKey?: boolean; 20 | sessionToken?: string; 21 | installationId?: string; 22 | progress?: any; 23 | usePost?: boolean; 24 | } 25 | declare const RESTController: { 26 | ajax(method: string, url: string, data: any, headers?: any, options?: FullOptions): Promise; 27 | request(method: string, path: string, data: any, options?: RequestOptions): Promise; 28 | handleError(errorJSON: any): Promise; 29 | }; 30 | export default RESTController; 31 | -------------------------------------------------------------------------------- /types/SingleInstanceStateController.d.ts: -------------------------------------------------------------------------------- 1 | import type { Op } from './ParseOp'; 2 | import type ParseObject from './ParseObject'; 3 | import type { AttributeMap, ObjectCache, OpsMap, State } from './ObjectStateMutations'; 4 | export declare function getState(obj: ParseObject): State | null; 5 | export declare function initializeState(obj: ParseObject, initial?: State): State; 6 | export declare function removeState(obj: ParseObject): State | null; 7 | export declare function getServerData(obj: ParseObject): AttributeMap; 8 | export declare function setServerData(obj: ParseObject, attributes: AttributeMap): void; 9 | export declare function getPendingOps(obj: ParseObject): OpsMap[]; 10 | export declare function setPendingOp(obj: ParseObject, attr: string, op?: Op): void; 11 | export declare function pushPendingState(obj: ParseObject): void; 12 | export declare function popPendingState(obj: ParseObject): OpsMap; 13 | export declare function mergeFirstPendingState(obj: ParseObject): void; 14 | export declare function getObjectCache(obj: ParseObject): ObjectCache; 15 | export declare function estimateAttribute(obj: ParseObject, attr: string): any; 16 | export declare function estimateAttributes(obj: ParseObject): AttributeMap; 17 | export declare function commitServerChanges(obj: ParseObject, changes: AttributeMap): void; 18 | export declare function enqueueTask(obj: ParseObject, task: () => Promise): Promise; 19 | export declare function clearAllState(): void; 20 | export declare function duplicateState(source: { 21 | id: string; 22 | }, dest: { 23 | id: string; 24 | }): void; 25 | -------------------------------------------------------------------------------- /types/Socket.weapp.d.ts: -------------------------------------------------------------------------------- 1 | declare class SocketWeapp { 2 | onopen: () => void; 3 | onmessage: () => void; 4 | onclose: () => void; 5 | onerror: () => void; 6 | constructor(serverURL: any); 7 | send(data: any): void; 8 | close(): void; 9 | } 10 | export default SocketWeapp; 11 | -------------------------------------------------------------------------------- /types/Storage.d.ts: -------------------------------------------------------------------------------- 1 | declare const Storage: { 2 | async(): boolean; 3 | getItem(path: string): string | null; 4 | getItemAsync(path: string): Promise; 5 | setItem(path: string, value: string): void; 6 | setItemAsync(path: string, value: string): Promise; 7 | removeItem(path: string): void; 8 | removeItemAsync(path: string): Promise; 9 | getAllKeys(): string[]; 10 | getAllKeysAsync(): Promise; 11 | generatePath(path: string): string; 12 | _clear(): void; 13 | }; 14 | export default Storage; 15 | -------------------------------------------------------------------------------- /types/StorageController.browser.d.ts: -------------------------------------------------------------------------------- 1 | declare const StorageController: { 2 | async: number; 3 | getItem(path: string): string | null; 4 | setItem(path: string, value: string): void; 5 | removeItem(path: string): void; 6 | getAllKeys(): string[]; 7 | clear(): void; 8 | }; 9 | export default StorageController; 10 | -------------------------------------------------------------------------------- /types/StorageController.d.ts: -------------------------------------------------------------------------------- 1 | declare let StorageController: any; 2 | export default StorageController; 3 | -------------------------------------------------------------------------------- /types/StorageController.default.d.ts: -------------------------------------------------------------------------------- 1 | declare const StorageController: { 2 | async: number; 3 | getItem(path: string): string | null; 4 | setItem(path: string, value: string): void; 5 | removeItem(path: string): void; 6 | getAllKeys(): string[]; 7 | clear(): void; 8 | }; 9 | export default StorageController; 10 | -------------------------------------------------------------------------------- /types/StorageController.react-native.d.ts: -------------------------------------------------------------------------------- 1 | declare const StorageController: { 2 | async: number; 3 | getItemAsync(path: string): Promise; 4 | setItemAsync(path: string, value: string): Promise; 5 | removeItemAsync(path: string): Promise; 6 | getAllKeysAsync(): Promise; 7 | multiGet(keys: string[]): Promise; 8 | multiRemove(keys: string[]): Promise; 9 | clear(): Promise; 10 | }; 11 | export default StorageController; 12 | -------------------------------------------------------------------------------- /types/StorageController.weapp.d.ts: -------------------------------------------------------------------------------- 1 | declare const StorageController: { 2 | async: number; 3 | getItem(path: string): string | null; 4 | setItem(path: string, value: string): void; 5 | removeItem(path: string): void; 6 | getAllKeys(): any; 7 | clear(): void; 8 | }; 9 | export default StorageController; 10 | -------------------------------------------------------------------------------- /types/TaskQueue.d.ts: -------------------------------------------------------------------------------- 1 | interface Task { 2 | task: () => Promise; 3 | _completion: any; 4 | } 5 | declare class TaskQueue { 6 | queue: Task[]; 7 | constructor(); 8 | enqueue(task: () => Promise): Promise; 9 | _dequeue(): void; 10 | } 11 | export default TaskQueue; 12 | -------------------------------------------------------------------------------- /types/UniqueInstanceStateController.d.ts: -------------------------------------------------------------------------------- 1 | import type { Op } from './ParseOp'; 2 | import type ParseObject from './ParseObject'; 3 | import type { AttributeMap, ObjectCache, OpsMap, State } from './ObjectStateMutations'; 4 | export declare function getState(obj: ParseObject): State | null; 5 | export declare function initializeState(obj: ParseObject, initial?: State): State; 6 | export declare function removeState(obj: ParseObject): State | null; 7 | export declare function getServerData(obj: ParseObject): AttributeMap; 8 | export declare function setServerData(obj: ParseObject, attributes: AttributeMap): void; 9 | export declare function getPendingOps(obj: ParseObject): OpsMap[]; 10 | export declare function setPendingOp(obj: ParseObject, attr: string, op?: Op): void; 11 | export declare function pushPendingState(obj: ParseObject): void; 12 | export declare function popPendingState(obj: ParseObject): OpsMap; 13 | export declare function mergeFirstPendingState(obj: ParseObject): void; 14 | export declare function getObjectCache(obj: ParseObject): ObjectCache; 15 | export declare function estimateAttribute(obj: ParseObject, attr: string): any; 16 | export declare function estimateAttributes(obj: ParseObject): AttributeMap; 17 | export declare function commitServerChanges(obj: ParseObject, changes: AttributeMap): void; 18 | export declare function enqueueTask(obj: ParseObject, task: () => Promise): Promise; 19 | export declare function duplicateState(source: ParseObject, dest: ParseObject): void; 20 | export declare function clearAllState(): void; 21 | -------------------------------------------------------------------------------- /types/WebSocketController.d.ts: -------------------------------------------------------------------------------- 1 | declare let WebSocketController: any; 2 | export default WebSocketController; 3 | -------------------------------------------------------------------------------- /types/Xhr.weapp.d.ts: -------------------------------------------------------------------------------- 1 | export declare function polyfillFetch(): void; 2 | -------------------------------------------------------------------------------- /types/arrayContainsObject.d.ts: -------------------------------------------------------------------------------- 1 | import type ParseObject from './ParseObject'; 2 | export default function arrayContainsObject(array: any[], object: ParseObject): boolean; 3 | -------------------------------------------------------------------------------- /types/canBeSerialized.d.ts: -------------------------------------------------------------------------------- 1 | import type ParseObject from './ParseObject'; 2 | export default function canBeSerialized(obj: ParseObject): boolean; 3 | -------------------------------------------------------------------------------- /types/decode.d.ts: -------------------------------------------------------------------------------- 1 | export default function decode(value: any): any; 2 | -------------------------------------------------------------------------------- /types/encode.d.ts: -------------------------------------------------------------------------------- 1 | export default function (value: any, disallowObjects?: boolean, forcePointers?: boolean, seen?: any[], offline?: boolean): any; 2 | -------------------------------------------------------------------------------- /types/equals.d.ts: -------------------------------------------------------------------------------- 1 | export default function equals(a: any, b: any): boolean; 2 | -------------------------------------------------------------------------------- /types/escape.d.ts: -------------------------------------------------------------------------------- 1 | export default function escape(str: string): string; 2 | -------------------------------------------------------------------------------- /types/eslint.config.mjs: -------------------------------------------------------------------------------- 1 | import eslint from '@eslint/js'; 2 | import tseslint from 'typescript-eslint'; 3 | import expectType from 'eslint-plugin-expect-type/configs/recommended'; 4 | 5 | export default tseslint.config({ 6 | files: ['**/*.js', '**/*.ts'], 7 | extends: [ 8 | expectType, 9 | eslint.configs.recommended, 10 | ...tseslint.configs.recommended, 11 | ...tseslint.configs.recommendedTypeChecked, 12 | ], 13 | plugins: { 14 | '@typescript-eslint': tseslint.plugin, 15 | }, 16 | rules: { 17 | '@typescript-eslint/no-unused-vars': 'off', 18 | '@typescript-eslint/no-unused-expressions': 'off', 19 | '@typescript-eslint/no-unsafe-call': 'off', 20 | "@typescript-eslint/no-explicit-any": "off", 21 | "@typescript-eslint/no-unsafe-return": "off", 22 | }, 23 | languageOptions: { 24 | parser: tseslint.parser, 25 | parserOptions: { 26 | projectService: true, 27 | tsconfigRootDir: import.meta.dirname, 28 | }, 29 | }, 30 | }); 31 | -------------------------------------------------------------------------------- /types/index.d.ts: -------------------------------------------------------------------------------- 1 | // https://github.com/DefinitelyTyped/DefinitelyTyped/blob/b23a36e669fa127d1035e22ca93faab85b98e49f/types/parse/index.d.ts#L11 2 | 3 | import parse from './Parse'; 4 | export default parse; 5 | 6 | // All exports beyond this point will be included in the Parse namespace 7 | export as namespace Parse; 8 | import ACL from './ParseACL'; 9 | import * as Analytics from './Analytics'; 10 | import AnonymousUtils from './AnonymousUtils'; 11 | import * as Cloud from './Cloud'; 12 | import CLP from './ParseCLP'; 13 | import CoreManager from './CoreManager'; 14 | import Config from './ParseConfig'; 15 | import Error from './ParseError'; 16 | import FacebookUtils from './FacebookUtils'; 17 | import File from './ParseFile'; 18 | import GeoPoint from './ParseGeoPoint'; 19 | import * as Hooks from './ParseHooks'; 20 | import IndexedDB from './IndexedDBStorageController'; 21 | import Polygon from './ParsePolygon'; 22 | import Installation from './ParseInstallation'; 23 | import LiveQuery from './ParseLiveQuery'; 24 | import LiveQueryClient from './LiveQueryClient'; 25 | import LocalDatastore from './LocalDatastore'; 26 | import Object from './ParseObject'; 27 | import * as Push from './Push'; 28 | import Query from './ParseQuery'; 29 | import Relation from './ParseRelation'; 30 | import Role from './ParseRole'; 31 | import Schema from './ParseSchema'; 32 | import Session from './ParseSession'; 33 | import Storage from './Storage'; 34 | import User from './ParseUser'; 35 | 36 | export type { AuthProvider, AuthData } from './ParseUser'; 37 | export type { Pointer } from './ParseObject'; 38 | export { 39 | ACL, 40 | Analytics, 41 | AnonymousUtils, 42 | Cloud, 43 | CLP, 44 | CoreManager, 45 | Config, 46 | Error, 47 | FacebookUtils, 48 | File, 49 | GeoPoint, 50 | Polygon, 51 | Installation, 52 | LiveQuery, 53 | LocalDatastore, 54 | Object, 55 | Push, 56 | Query, 57 | Relation, 58 | Role, 59 | Schema, 60 | Session, 61 | Storage, 62 | User, 63 | LiveQueryClient, 64 | IndexedDB, 65 | Hooks, 66 | }; 67 | -------------------------------------------------------------------------------- /types/isRevocableSession.d.ts: -------------------------------------------------------------------------------- 1 | export default function isRevocableSession(token: string): boolean; 2 | -------------------------------------------------------------------------------- /types/node.d.ts: -------------------------------------------------------------------------------- 1 | import * as parse from './index'; 2 | 3 | export = parse; 4 | -------------------------------------------------------------------------------- /types/parseDate.d.ts: -------------------------------------------------------------------------------- 1 | export default function parseDate(iso8601: string): Date | null; 2 | -------------------------------------------------------------------------------- /types/promiseUtils.d.ts: -------------------------------------------------------------------------------- 1 | export declare function resolvingPromise(): Promise & { 2 | resolve: (res: T) => void; 3 | reject: (err: any) => void; 4 | }; 5 | export declare function when(promises: any): Promise | (Promise & { 6 | resolve: (res: any) => void; 7 | reject: (err: any) => void; 8 | }); 9 | export declare function continueWhile(test: () => any, emitter: () => Promise): any; 10 | -------------------------------------------------------------------------------- /types/react-native.d.ts: -------------------------------------------------------------------------------- 1 | import * as parse from './index'; 2 | 3 | export = parse; 4 | -------------------------------------------------------------------------------- /types/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "lib": ["es6", "dom"], 5 | "noImplicitAny": true, 6 | "noImplicitThis": true, 7 | "strictFunctionTypes": true, 8 | "strictNullChecks": true, 9 | "types": [], 10 | "noEmit": true, 11 | "forceConsistentCasingInFileNames": true, 12 | 13 | // If the library is an external module (uses `export`), this allows your test file to import "mylib" instead of "./index". 14 | // If the library is global (cannot be imported via `import` or `require`), leave this out. 15 | "baseUrl": ".", 16 | "paths": { 17 | "parse": ["."], 18 | "parse/node": ["./node.d.ts"], 19 | "parse/react-native": ["./react-native.d.ts"] 20 | } 21 | }, 22 | "include": [ 23 | "tests.ts" 24 | ] 25 | } 26 | -------------------------------------------------------------------------------- /types/unique.d.ts: -------------------------------------------------------------------------------- 1 | export default function unique(arr: T[]): T[]; 2 | -------------------------------------------------------------------------------- /types/unsavedChildren.d.ts: -------------------------------------------------------------------------------- 1 | import ParseFile from './ParseFile'; 2 | import type ParseObject from './ParseObject'; 3 | /** 4 | * Return an array of unsaved children, which are either Parse Objects or Files. 5 | * If it encounters any dirty Objects without Ids, it will throw an exception. 6 | * 7 | * @param {Parse.Object} obj 8 | * @param {boolean} allowDeepUnsaved 9 | * @returns {Array} 10 | */ 11 | export default function unsavedChildren(obj: ParseObject, allowDeepUnsaved?: boolean): (ParseFile | ParseObject)[]; 12 | -------------------------------------------------------------------------------- /types/uuid.d.ts: -------------------------------------------------------------------------------- 1 | declare let uuid: () => string; 2 | export default uuid; 3 | -------------------------------------------------------------------------------- /vite.config.umd.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vite'; 2 | import { nodePolyfills } from 'vite-plugin-node-polyfills'; 3 | import commonjs from 'vite-plugin-commonjs' 4 | import terser from '@rollup/plugin-terser'; 5 | import { resolve } from 'path'; 6 | import pkg from './package.json'; 7 | 8 | const banner = `/** 9 | * Parse JavaScript SDK v${pkg.version} 10 | * 11 | * Copyright 2015-present Parse Platform 12 | * All rights reserved. 13 | * 14 | * The source tree of this library can be found at: 15 | * https://github.com/parse-community/Parse-SDK-JS 16 | * 17 | * This source code is licensed under the license found in the LICENSE 18 | * file in the root directory of this source tree. Additional legal 19 | * information can be found in the NOTICE file in the same directory. 20 | */ 21 | `; 22 | 23 | const FILE_NAME = process.env.PARSE_BUILD === 'browser' ? 'parse' : 'parse.weapp'; 24 | const build = { 25 | name: 'Parse', 26 | globals: { 27 | xmlhttprequest: 'XMLHttpRequest', 28 | _process: 'process', 29 | }, 30 | banner, 31 | }; 32 | const umdBuilds: any = [{ 33 | entryFileNames: `${FILE_NAME}.js`, 34 | format: 'umd', 35 | ...build, 36 | }, { 37 | entryFileNames: `${FILE_NAME}.min.js`, 38 | format: 'umd', 39 | ...build, 40 | plugins: [ 41 | terser({ 42 | format: { 43 | comments: false, 44 | }, 45 | }) as any, 46 | ], 47 | }]; 48 | 49 | export default defineConfig({ 50 | plugins: [nodePolyfills(), commonjs()], 51 | define: { 52 | 'process.env.PARSE_BUILD': `"${process.env.PARSE_BUILD}"`, 53 | 'process.env.npm_package_version': `"${pkg.version}"`, 54 | }, 55 | build: { 56 | outDir: 'dist', 57 | emptyOutDir: false, 58 | rollupOptions: { 59 | input: resolve(__dirname, 'src/Parse.ts'), 60 | external: ['xmlhttprequest', '_process'], 61 | output: [...umdBuilds], 62 | }, 63 | minify: false, 64 | sourcemap: false, 65 | }, 66 | resolve: { 67 | alias: { 68 | 'react-native/Libraries/vendor/emitter/EventEmitter': 'events', 69 | 'EventEmitter': 'events', 70 | }, 71 | }, 72 | }); 73 | -------------------------------------------------------------------------------- /weapp.js: -------------------------------------------------------------------------------- 1 | module.exports = require('./lib/weapp/Parse.js').default; 2 | --------------------------------------------------------------------------------