├── .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 | * - channels - An Array of channels to push to.
37 | * - push_time - A Date object for when to send the push.
38 | * - expiration_time - A Date object for when to expire
39 | * the push.
40 | * - expiration_interval - The seconds from now to expire the push.
41 | * - where - A Parse.Query over Parse.Installation that is used to match
42 | * a set of installations to push to.
43 | * - data - The data to send as part of the push.
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 | * - channels - An Array of channels to push to.
32 | * - push_time - A Date object for when to send the push.
33 | * - expiration_time - A Date object for when to expire
34 | * the push.
35 | * - expiration_interval - The seconds from now to expire the push.
36 | * - where - A Parse.Query over Parse.Installation that is used to match
37 | * a set of installations to push to.
38 | * - data - The data to send as part of the push.
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 |
--------------------------------------------------------------------------------