mirror of
https://github.com/meteor/meteor.git
synced 2026-05-02 03:01:46 -04:00
Merge branch 'devel' into async-tracker-explicit
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
version: 2
|
||||
version: 2.1
|
||||
|
||||
# A reusable "run" snippet which is ran before each test to setup the
|
||||
# environment for user-limits, core-dumps, etc.
|
||||
@@ -96,6 +96,30 @@ build_machine_environment: &build_machine_environment
|
||||
NUM_GROUPS: 12
|
||||
RUNNING_AVG_LENGTH: 6
|
||||
|
||||
can_disable_fibers: &can_disable_fibers
|
||||
parameters:
|
||||
fibers:
|
||||
type: boolean
|
||||
default: true
|
||||
|
||||
set_fibers_env: &set_fibers_env
|
||||
name: "Disable Fibers"
|
||||
command: |
|
||||
if [ "<< parameters.fibers >>" == "false" ]; then
|
||||
echo "Disabling Fibers"
|
||||
echo 'export DISABLE_FIBERS=1' >> "$BASH_ENV"
|
||||
source "$BASH_ENV"
|
||||
fi
|
||||
|
||||
|
||||
# Run tests with Fibers and then without.
|
||||
matrix_for_fibers: &matrix_for_fibers
|
||||
matrix:
|
||||
parameters:
|
||||
# If we want to run with Fibers and without, just append false here.
|
||||
fibers: [true]
|
||||
|
||||
|
||||
jobs:
|
||||
Get Ready:
|
||||
<<: *build_machine_environment
|
||||
@@ -167,6 +191,7 @@ jobs:
|
||||
path: /tmp/memuse.txt
|
||||
|
||||
Isolated Tests:
|
||||
<<: *can_disable_fibers
|
||||
<<: *build_machine_environment
|
||||
steps:
|
||||
- run:
|
||||
@@ -175,6 +200,7 @@ jobs:
|
||||
<<: *run_env_change
|
||||
- attach_workspace:
|
||||
at: .
|
||||
- run: *set_fibers_env
|
||||
- run:
|
||||
name: "Print environment"
|
||||
command: printenv
|
||||
@@ -209,6 +235,7 @@ jobs:
|
||||
path: /tmp/memuse.txt
|
||||
|
||||
Test Group 0:
|
||||
<<: *can_disable_fibers
|
||||
<<: *build_machine_environment
|
||||
steps:
|
||||
- run:
|
||||
@@ -217,6 +244,7 @@ jobs:
|
||||
<<: *run_env_change
|
||||
- attach_workspace:
|
||||
at: .
|
||||
- run: *set_fibers_env
|
||||
- run:
|
||||
name: "Print environment"
|
||||
command: printenv
|
||||
@@ -249,6 +277,7 @@ jobs:
|
||||
path: /tmp/memuse.txt
|
||||
|
||||
Test Group 1:
|
||||
<<: *can_disable_fibers
|
||||
<<: *build_machine_environment
|
||||
steps:
|
||||
- run:
|
||||
@@ -257,6 +286,7 @@ jobs:
|
||||
<<: *run_env_change
|
||||
- attach_workspace:
|
||||
at: .
|
||||
- run: *set_fibers_env
|
||||
- run:
|
||||
name: "Print environment"
|
||||
command: printenv
|
||||
@@ -289,6 +319,7 @@ jobs:
|
||||
path: /tmp/memuse.txt
|
||||
|
||||
Test Group 2:
|
||||
<<: *can_disable_fibers
|
||||
<<: *build_machine_environment
|
||||
steps:
|
||||
- run:
|
||||
@@ -297,6 +328,7 @@ jobs:
|
||||
<<: *run_env_change
|
||||
- attach_workspace:
|
||||
at: .
|
||||
- run: *set_fibers_env
|
||||
- run:
|
||||
name: "Print environment"
|
||||
command: printenv
|
||||
@@ -329,6 +361,7 @@ jobs:
|
||||
path: /tmp/memuse.txt
|
||||
|
||||
Test Group 3:
|
||||
<<: *can_disable_fibers
|
||||
<<: *build_machine_environment
|
||||
steps:
|
||||
- run:
|
||||
@@ -337,6 +370,7 @@ jobs:
|
||||
<<: *run_env_change
|
||||
- attach_workspace:
|
||||
at: .
|
||||
- run: *set_fibers_env
|
||||
- run:
|
||||
name: "Print environment"
|
||||
command: printenv
|
||||
@@ -369,6 +403,7 @@ jobs:
|
||||
path: /tmp/memuse.txt
|
||||
|
||||
Test Group 4:
|
||||
<<: *can_disable_fibers
|
||||
<<: *build_machine_environment
|
||||
steps:
|
||||
- run:
|
||||
@@ -377,6 +412,7 @@ jobs:
|
||||
<<: *run_env_change
|
||||
- attach_workspace:
|
||||
at: .
|
||||
- run: *set_fibers_env
|
||||
- run:
|
||||
name: "Print environment"
|
||||
command: printenv
|
||||
@@ -409,6 +445,7 @@ jobs:
|
||||
path: /tmp/memuse.txt
|
||||
|
||||
Test Group 5:
|
||||
<<: *can_disable_fibers
|
||||
<<: *build_machine_environment
|
||||
steps:
|
||||
- run:
|
||||
@@ -417,6 +454,7 @@ jobs:
|
||||
<<: *run_env_change
|
||||
- attach_workspace:
|
||||
at: .
|
||||
- run: *set_fibers_env
|
||||
- run:
|
||||
name: "Print environment"
|
||||
command: printenv
|
||||
@@ -449,6 +487,7 @@ jobs:
|
||||
path: /tmp/memuse.txt
|
||||
|
||||
Test Group 6:
|
||||
<<: *can_disable_fibers
|
||||
<<: *build_machine_environment
|
||||
steps:
|
||||
- run:
|
||||
@@ -457,6 +496,7 @@ jobs:
|
||||
<<: *run_env_change
|
||||
- attach_workspace:
|
||||
at: .
|
||||
- run: *set_fibers_env
|
||||
- run:
|
||||
name: "Print environment"
|
||||
command: printenv
|
||||
@@ -489,6 +529,7 @@ jobs:
|
||||
path: /tmp/memuse.txt
|
||||
|
||||
Test Group 7:
|
||||
<<: *can_disable_fibers
|
||||
<<: *build_machine_environment
|
||||
steps:
|
||||
- run:
|
||||
@@ -497,6 +538,7 @@ jobs:
|
||||
<<: *run_env_change
|
||||
- attach_workspace:
|
||||
at: .
|
||||
- run: *set_fibers_env
|
||||
- run:
|
||||
name: "Print environment"
|
||||
command: printenv
|
||||
@@ -529,6 +571,7 @@ jobs:
|
||||
path: /tmp/memuse.txt
|
||||
|
||||
Test Group 8:
|
||||
<<: *can_disable_fibers
|
||||
<<: *build_machine_environment
|
||||
steps:
|
||||
- run:
|
||||
@@ -537,6 +580,7 @@ jobs:
|
||||
<<: *run_env_change
|
||||
- attach_workspace:
|
||||
at: .
|
||||
- run: *set_fibers_env
|
||||
- run:
|
||||
name: "Print environment"
|
||||
command: printenv
|
||||
@@ -569,6 +613,7 @@ jobs:
|
||||
path: /tmp/memuse.txt
|
||||
|
||||
Test Group 9:
|
||||
<<: *can_disable_fibers
|
||||
<<: *build_machine_environment
|
||||
steps:
|
||||
- run:
|
||||
@@ -577,6 +622,7 @@ jobs:
|
||||
<<: *run_env_change
|
||||
- attach_workspace:
|
||||
at: .
|
||||
- run: *set_fibers_env
|
||||
- run:
|
||||
name: "Print environment"
|
||||
command: printenv
|
||||
@@ -609,6 +655,7 @@ jobs:
|
||||
path: /tmp/memuse.txt
|
||||
|
||||
Test Group 10:
|
||||
<<: *can_disable_fibers
|
||||
<<: *build_machine_environment
|
||||
steps:
|
||||
- run:
|
||||
@@ -617,6 +664,7 @@ jobs:
|
||||
<<: *run_env_change
|
||||
- attach_workspace:
|
||||
at: .
|
||||
- run: *set_fibers_env
|
||||
- run:
|
||||
name: "Print environment"
|
||||
command: printenv
|
||||
@@ -727,6 +775,7 @@ jobs:
|
||||
npm test
|
||||
|
||||
Clean Up:
|
||||
<<: *can_disable_fibers
|
||||
<<: *build_machine_environment
|
||||
steps:
|
||||
- attach_workspace:
|
||||
@@ -809,45 +858,58 @@ workflows:
|
||||
- Docs
|
||||
- Get Ready
|
||||
- Isolated Tests:
|
||||
<<: *matrix_for_fibers
|
||||
requires:
|
||||
- Get Ready
|
||||
- Test Group 0:
|
||||
<<: *matrix_for_fibers
|
||||
requires:
|
||||
- Get Ready
|
||||
- Test Group 1:
|
||||
<<: *matrix_for_fibers
|
||||
requires:
|
||||
- Get Ready
|
||||
- Test Group 2:
|
||||
<<: *matrix_for_fibers
|
||||
requires:
|
||||
- Get Ready
|
||||
- Test Group 3:
|
||||
<<: *matrix_for_fibers
|
||||
requires:
|
||||
- Get Ready
|
||||
- Test Group 4:
|
||||
<<: *matrix_for_fibers
|
||||
requires:
|
||||
- Get Ready
|
||||
- Test Group 5:
|
||||
<<: *matrix_for_fibers
|
||||
requires:
|
||||
- Get Ready
|
||||
- Test Group 6:
|
||||
<<: *matrix_for_fibers
|
||||
requires:
|
||||
- Get Ready
|
||||
- Test Group 7:
|
||||
<<: *matrix_for_fibers
|
||||
requires:
|
||||
- Get Ready
|
||||
- Test Group 8:
|
||||
<<: *matrix_for_fibers
|
||||
requires:
|
||||
- Get Ready
|
||||
- Test Group 9:
|
||||
<<: *matrix_for_fibers
|
||||
requires:
|
||||
- Get Ready
|
||||
- Test Group 10:
|
||||
<<: *matrix_for_fibers
|
||||
requires:
|
||||
- Get Ready
|
||||
- Test Group 11:
|
||||
requires:
|
||||
- Get Ready
|
||||
- Clean Up:
|
||||
<<: *matrix_for_fibers
|
||||
requires:
|
||||
- Isolated Tests
|
||||
- Test Group 0
|
||||
|
||||
6
.github/workflows/docs.yml
vendored
6
.github/workflows/docs.yml
vendored
@@ -10,14 +10,14 @@ jobs:
|
||||
run:
|
||||
working-directory: docs/
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/setup-node@v2
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: 12.x
|
||||
- name: Build the Docs
|
||||
run: npm ci && npm run build
|
||||
- name: Deploy to Netlify for preview
|
||||
uses: nwtgck/actions-netlify@v1.2.2
|
||||
uses: nwtgck/actions-netlify@v1.2.4
|
||||
with:
|
||||
publish-dir: './docs/public/'
|
||||
production-branch: devel
|
||||
|
||||
6
.github/workflows/guide.yml
vendored
6
.github/workflows/guide.yml
vendored
@@ -10,14 +10,14 @@ jobs:
|
||||
run:
|
||||
working-directory: guide/
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/setup-node@v1
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: 12.x
|
||||
- name: Build the Guide
|
||||
run: npm ci && npm run build
|
||||
- name: Deploy to Netlify for preview
|
||||
uses: nwtgck/actions-netlify@v1.2.2
|
||||
uses: nwtgck/actions-netlify@v1.2.4
|
||||
with:
|
||||
publish-dir: './guide/public'
|
||||
production-branch: devel
|
||||
|
||||
6
.github/workflows/labeler.yml
vendored
6
.github/workflows/labeler.yml
vendored
@@ -9,10 +9,14 @@ name: Labeler
|
||||
on:
|
||||
- pull_request_target
|
||||
|
||||
permissions:
|
||||
contents: read # to determine modified files (actions/labeler)
|
||||
pull-requests: write # to add labels to PRs (actions/labeler)
|
||||
|
||||
jobs:
|
||||
label:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/labeler@v3
|
||||
- uses: actions/labeler@v4
|
||||
with:
|
||||
repo-token: "${{ secrets.GITHUB_TOKEN }}"
|
||||
|
||||
@@ -6,6 +6,10 @@ on:
|
||||
pull_request:
|
||||
paths:
|
||||
- "npm-packages/eslint-plugin-meteor/**"
|
||||
|
||||
permissions:
|
||||
contents: read # to fetch code (actions/checkout)
|
||||
|
||||
jobs:
|
||||
test:
|
||||
runs-on: ubuntu-latest
|
||||
@@ -16,9 +20,9 @@ jobs:
|
||||
matrix:
|
||||
node-version: [12.x, 14.x]
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/checkout@v3
|
||||
- name: Use Node.js ${{ matrix.node-version }}
|
||||
uses: actions/setup-node@v2
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: ${{ matrix.node-version }}
|
||||
cache: npm
|
||||
|
||||
8
.github/workflows/npm-meteor-babel.yml
vendored
8
.github/workflows/npm-meteor-babel.yml
vendored
@@ -6,6 +6,10 @@ on:
|
||||
pull_request:
|
||||
paths:
|
||||
- "npm-packages/meteor-babel/**"
|
||||
|
||||
permissions:
|
||||
contents: read # to fetch code (actions/checkout)
|
||||
|
||||
jobs:
|
||||
test:
|
||||
runs-on: ubuntu-latest
|
||||
@@ -16,9 +20,9 @@ jobs:
|
||||
matrix:
|
||||
node-version: [12.x, 14.x]
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/checkout@v3
|
||||
- name: Use Node.js ${{ matrix.node-version }}
|
||||
uses: actions/setup-node@v2
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: ${{ matrix.node-version }}
|
||||
cache: npm
|
||||
|
||||
8
.github/workflows/npm-meteor-promise.yml
vendored
8
.github/workflows/npm-meteor-promise.yml
vendored
@@ -6,6 +6,10 @@ on:
|
||||
pull_request:
|
||||
paths:
|
||||
- "npm-packages/meteor-promise/**"
|
||||
|
||||
permissions:
|
||||
contents: read # to fetch code (actions/checkout)
|
||||
|
||||
jobs:
|
||||
test:
|
||||
runs-on: ubuntu-latest
|
||||
@@ -16,9 +20,9 @@ jobs:
|
||||
matrix:
|
||||
node-version: [12.x, 14.x]
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/checkout@v3
|
||||
- name: Use Node.js ${{ matrix.node-version }}
|
||||
uses: actions/setup-node@v2
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: ${{ matrix.node-version }}
|
||||
cache: npm
|
||||
|
||||
14
.travis.yml
14
.travis.yml
@@ -8,13 +8,17 @@ cache:
|
||||
- ".meteor"
|
||||
- ".babel-cache"
|
||||
script:
|
||||
- export phantom=false
|
||||
# to skip Downloading Chromium on every run
|
||||
# https://github.com/dfernandez79/puppeteer/blob/main/README.md#q-chromium-gets-downloaded-on-every-npm-ci-run-how-can-i-cache-the-download
|
||||
- export PUPPETEER_DOWNLOAD_PATH=~/.npm/chromium
|
||||
- travis_retry ./packages/test-in-console/run.sh
|
||||
env:
|
||||
- CXX=g++-4.8
|
||||
global:
|
||||
- CXX=g++-4.8
|
||||
- phantom=false
|
||||
- PUPPETEER_DOWNLOAD_PATH=~/.npm/chromium
|
||||
jobs:
|
||||
# We don't want to run the tests without fibers anymore.
|
||||
# - DISABLE_FIBERS=1
|
||||
# Use a different flag, since node would use false as a string.
|
||||
- FIBERS_ENABLED=1
|
||||
addons:
|
||||
apt:
|
||||
sources:
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
title: Meteor API Docs
|
||||
subtitle: API Docs
|
||||
versions:
|
||||
- '2.9'
|
||||
- '2.8'
|
||||
- '2.7'
|
||||
- '2.6'
|
||||
|
||||
217
docs/history.md
217
docs/history.md
@@ -1,4 +1,205 @@
|
||||
## 2.8.1, 2022-11-14
|
||||
## v2.9.1, 2022-12-27
|
||||
|
||||
### Highlights
|
||||
|
||||
* Reverted missing types [PR](https://github.com/meteor/meteor/pull/12366) by [Grubba27](https://github.com/Grubba27).
|
||||
* Fix fetch() type declaration [PR](https://github.com/meteor/meteor/pull/12352) by [zarvox](https://github.com/zarvox).
|
||||
* update svelte skeleton [PR](https://github.com/meteor/meteor/pull/12350) by [tosinek](https://github.com/tosinek).
|
||||
* Bump to node 14.21.2.0 [PR](https://github.com/meteor/meteor/pull/12370) by [Grubba27](https://github.com/Grubba27).
|
||||
* resetPassword and verifyEmail to no longer sign in the user automatically [PR](https://github.com/meteor/meteor/pull/12385) by [denihs](https://github.com/denihs).
|
||||
* Added missing vue2 declaration for skeletons [PR](https://github.com/meteor/meteor/pull/12396) by [Grubba27](https://github.com/Grubba27) & [mlanning](https://github.com/mlanning).
|
||||
|
||||
#### Breaking Changes
|
||||
|
||||
* `accounts-password@2.3.3`
|
||||
- The methods `resetPassword` and `verifyEmail` no longer logs the user if they have 2FA enabled. Now, the functions work as before, but instead of automatically logging in the user at the end, an error with the code `2fa-enabled` will be thrown.
|
||||
|
||||
|
||||
#### Internal API changes
|
||||
|
||||
N/A
|
||||
|
||||
#### Migration Steps
|
||||
|
||||
N/A
|
||||
|
||||
#### Meteor Version Release
|
||||
|
||||
* `fetch@0.1.3`:
|
||||
- Updated fetch type definition.
|
||||
|
||||
* `meteor@1.10.4`:
|
||||
- Added back meteor type definitions that were removed by mistake in earlier version.
|
||||
|
||||
* `accounts-password@2.3.3`
|
||||
- The methods `resetPassword` and `verifyEmail` no longer logs the user if they have 2FA enabled. Now, the functions work as before, but instead of automatically logging in the user at the end, an error with the code `2fa-enabled` will be thrown.
|
||||
|
||||
* `Command line`:
|
||||
- Updated Svelte skeleton to now be able to support typescript out of the box and added ``#each`` in links in the skeleton.
|
||||
- Updated node to 14.21.2 changes can be seen [here](https://github.com/nodejs/node/releases/tag/v14.21.2).
|
||||
- Solved [issue](https://github.com/meteor/meteor/issues/12395) that could not allow vue2 apps being created in command line.
|
||||
|
||||
#### Special thanks to
|
||||
- [@zarvox](https://github.com/zarvox).
|
||||
- [@tosinek](https://github.com/tosinek).
|
||||
- [@Grubba27](https://github.com/Grubba27).
|
||||
- [@denihs](https://github.com/denihs).
|
||||
- [@mlanning](https://github.com/mlanning).
|
||||
|
||||
For making this great framework even better!
|
||||
|
||||
|
||||
## v2.9, 2022-12-12
|
||||
|
||||
### Highlights
|
||||
|
||||
* TypeScript update to v4.6.4 [PR](https://github.com/meteor/meteor/pull/12204) by [@StorytellerCZ](https://github.com/StorytellerCZ).
|
||||
* Create Email.sendAsync method without using Fibers [PR](https://github.com/meteor/meteor/pull/12101)
|
||||
by [edimarlnx](https://github.com/edimarlnx).
|
||||
* Create async method CssTools.minifyCssAsync [PR](https://github.com/meteor/meteor/pull/12105)
|
||||
by [edimarlnx](https://github.com/edimarlnx).
|
||||
* Change Accounts and Oauth to use Async methods [PR](https://github.com/meteor/meteor/pull/12156)
|
||||
by [edimarlnx](https://github.com/edimarlnx).
|
||||
* TinyTest package without Future [PR](https://github.com/meteor/meteor/pull/12222)
|
||||
by [matheusccastroo](https://github.com/matheusccastroo).
|
||||
* Feat: user accounts base async [PR](https://github.com/meteor/meteor/pull/12274)
|
||||
by [Grubba27](https://github.com/Grubba27).
|
||||
* Move somed methods from OAuth of out of accounts-base [PR](https://github.com/meteor/meteor/pull/12202)
|
||||
by [StorytellerCZ](https://github.com/StorytellerCZ).
|
||||
* Feat: not using insecure & autopublish [PR](https://github.com/meteor/meteor/pull/12220)
|
||||
by [Grubba27](https://github.com/Grubba27).
|
||||
* Don't apply babel async-await plugin when not running on Fibers [PR](https://github.com/meteor/meteor/pull/12221).
|
||||
by [matheusccastroo](https://github.com/matheusccastroo).
|
||||
* Implemented Fibers-less MongoDB count methods [PR](https://github.com/meteor/meteor/pull/12295)
|
||||
by [radekmie](https://github.com/radekmie).
|
||||
* Feat: Generate scaffold in cli [PR](https://github.com/meteor/meteor/pull/12298)
|
||||
by [Grubba27](https://github.com/Grubba27).
|
||||
* Update types [PR](https://github.com/meteor/meteor/pull/12306) by [piotrpospiech](https://github.com/piotrpospiech).
|
||||
* Remove underscore from package-version-parser [PR](https://github.com/meteor/meteor/pull/12248)
|
||||
by [harryadel](https://github.com/harryadel).
|
||||
* Update MongoDB driver version [PR](https://github.com/meteor/meteor/pull/12333) by [Grubba27](https://github.com/Grubba27).
|
||||
* New Vue3 Skeleton [PR](https://github.com/meteor/meteor/pull/12302)
|
||||
by [henriquealbert](https://github.com/henriquealbert).
|
||||
|
||||
#### Breaking Changes
|
||||
* `Accounts.createUserVerifyingEmail` is now async
|
||||
|
||||
#### Internal API changes
|
||||
* Internal methods from `OAuth` that are now async:
|
||||
- _attemptLogin
|
||||
- _loginMethod
|
||||
- _runLoginHandlers
|
||||
- OAuth.registerService now accepts async functions
|
||||
|
||||
OAuth related code has been moved from `accounts-base` to `accounts-oauth`, removing the dependency on `service-configuration`
|
||||
more can be seen in this [discussion](https://github.com/meteor/meteor/discussions/12171) and in the [PR](https://github.com/meteor/meteor/pull/12202).
|
||||
This means that if you don’t use third-party login on your project, you don’t need to add the package service-configuration anymore.
|
||||
|
||||
#### Migration Steps
|
||||
|
||||
You can follow in [here](https://guide.meteor.com/2.9-migration.html).
|
||||
|
||||
#### Meteor Version Release
|
||||
|
||||
* `eslint-plugin-meteor@7.4.0`:
|
||||
- updated Typescript deps and meteor babel.
|
||||
* `eslint-plugin-meteor@7.4.0`:
|
||||
- updated Typescript deps and meteor babel.
|
||||
* `accounts-base@2.2.6`
|
||||
- Moved some functions to accounts-oauth.
|
||||
* `accounts-oauth@1.4.2`
|
||||
- Received functions from accounts-base.
|
||||
* `accounts-password@2.3.2`
|
||||
- Asyncfied functions such as `changePassword`, `forgotPassword`, `resetPassword`, `verifyEmail`, `setPasswordAsync`.
|
||||
* `babel-compiler@7.10.1`
|
||||
- Updated babel to 7.17.1.
|
||||
* `email@2.2.3`
|
||||
- Create Email.sendAsync method without using Fibers.
|
||||
* `facebook-oauth@1.11.2`
|
||||
- Updated facebook-oauth to use async functions.
|
||||
* `github-oauth@1.4.1`
|
||||
- Updated github-oauth to use async functions.
|
||||
* `google-oauth@1.4.3`
|
||||
- Updated google-oauth to use async functions.
|
||||
* `meetup-oauth@1.1.2`
|
||||
- Updated meetup-oauth to use async functions.
|
||||
* `meteor-developer-oauth@1.3.2`
|
||||
- Updated meteor-developer-oauth to use async functions.
|
||||
* `meteor@1.10.3`
|
||||
- Added Async Local Storage helpers.
|
||||
* `minifier-css@1.6.2`
|
||||
- Asyncfied `minifyCss` function.
|
||||
* `minimongo@1.9.1`
|
||||
- Implemented Fibers-less MongoDB count methods.
|
||||
* `mongo@1.16.2`
|
||||
- Implemented Fibers-less MongoDB count methods.
|
||||
* `npm-mongo@4.12.1`
|
||||
- Updated npm-mongo to 4.12.
|
||||
* `oauth@2.1.3`
|
||||
- Asyncfied methods.
|
||||
* `oauth1@1.5.1`
|
||||
- Asyncfied methods.
|
||||
* `oauth2@1.3.2`
|
||||
- Asyncfied methods.
|
||||
* `package-version-parser@3.2.1`
|
||||
- Removed underscore.
|
||||
* `promise@0.12.2`
|
||||
- Added DISABLE_FIBERS flag.
|
||||
* `standard-minifier-css@1.8.3`
|
||||
- Asyncfied minify method.
|
||||
* `test-helpers@1.3.1`
|
||||
- added runAndThrowIfNeeded function.
|
||||
* `test-in-browser@1.3.2`
|
||||
- Adjusted e[type] to e.type
|
||||
* `tinytest@1.2.2`
|
||||
- TinyTest package without Future.
|
||||
* `twitter-oauth@1.3.2`
|
||||
- Asyncfied methods.
|
||||
* `typescript@4.6.4`
|
||||
- updated typescript to 4.6.4.
|
||||
* `weibo-oauth@1.3.2`
|
||||
- Asyncfied methods.
|
||||
|
||||
#### Special thanks to
|
||||
- [@henriquealbert](https://github.com/henriquealbert);
|
||||
- [@edimarlnx](https://github.com/edimarlnx);
|
||||
- [@matheusccastroo](https://github.com/matheusccastroo);
|
||||
- [@Grubba27](https://github.com/Grubba27);
|
||||
- [@StorytellerCZ](https://github.com/StorytellerCZ);
|
||||
- [@radekmie](https://github.com/radekmie);
|
||||
- [@piotrpospiech](https://github.com/piotrpospiech);
|
||||
- [@harryadel](https://github.com/harryadel);
|
||||
|
||||
For making this great framework even better!
|
||||
|
||||
|
||||
## v2.8.2, 2022-11-29
|
||||
|
||||
#### Highlights
|
||||
* `mongo@1.16.2`:
|
||||
- Make count NOT create a cursor. [PR](https://github.com/meteor/meteor/pull/12326).
|
||||
* `meteorjs/babel@7.16.1-beta.0`
|
||||
- Adjusted config to Auto import React on jsx,tsx files [PR](https://github.com/meteor/meteor/pull/12327).
|
||||
- needs to use directly from npm the meteorjs/babel@7.16.1-beta.0.
|
||||
|
||||
#### Breaking Changes
|
||||
N/A
|
||||
|
||||
#### Migration Steps
|
||||
|
||||
#### Meteor Version Release
|
||||
* `mongo@1.16.2`:
|
||||
- Make count NOT create a cursor. [PR](https://github.com/meteor/meteor/pull/12326).
|
||||
|
||||
#### Special thanks to
|
||||
- [@henriquealbert](https://github.com/henriquealbert);
|
||||
- [@znewsham](https://github.com/znewsham);
|
||||
|
||||
For making this great framework even better!
|
||||
|
||||
|
||||
|
||||
## v2.8.1, 2022-11-14
|
||||
|
||||
#### Highlights
|
||||
|
||||
@@ -104,7 +305,7 @@ _In case you want types in your app using the core packages types/zodern:types (
|
||||
* `test-in-browser@1.3.1`
|
||||
- removed underscore.
|
||||
* `tracker@1.2.1`
|
||||
- added types for package.
|
||||
- added types for package.
|
||||
* `twitter-oauth@1.3.1`
|
||||
- removed underscore.
|
||||
* `underscore@1.0.11`
|
||||
@@ -113,7 +314,6 @@ _In case you want types in your app using the core packages types/zodern:types (
|
||||
- added types for package.
|
||||
* `webapp-hashing@1.1.1`
|
||||
- added types for package.
|
||||
|
||||
## v2.8, 2022-10-19
|
||||
|
||||
#### Highlights
|
||||
@@ -156,7 +356,16 @@ Read our [Migration Guide](https://guide.meteor.com/2.8-migration.html) for this
|
||||
- Validates required Node.js version. [PR](https://github.com/meteor/meteor/pull/12066).
|
||||
* `npm-mongo@4.9.0`:
|
||||
- Updated MongoDB driver to 4.9. [PR](https://github.com/meteor/meteor/pull/12163).
|
||||
|
||||
* `@meteorjs/babel@7.17.0`
|
||||
- Upgrade TypeScript to `4.6.4`
|
||||
* `babel-compiler@7.10.0`
|
||||
- Upgrade TypeScript to `4.6.4`
|
||||
* `ecmascript@0.16.3`
|
||||
- Upgrade TypeScript to `4.6.4`
|
||||
* `typescript@4.6.4`
|
||||
- Upgrade TypeScript to `4.6.4`
|
||||
* `eslint-plugin-meteor@7.4.0`
|
||||
- Upgrade TypeScript to `4.6.4`
|
||||
|
||||
#### Independent Releases
|
||||
* `accounts-passwordless@2.1.3`:
|
||||
|
||||
@@ -82,6 +82,28 @@ Meteor.call(
|
||||
'This is a test of Email.send.'
|
||||
);
|
||||
```
|
||||
{% apibox "Email.sendAsync" %}
|
||||
|
||||
`sendAsync` only works on the server. It has the same behavior as `Email.send`, but returns a Promise.
|
||||
If you defined `Email.customTransport`, the `callAsync` method returns the return value from the `customTransport` method or a Promise, if this method is async.
|
||||
|
||||
```js
|
||||
// Server: Define a method that the client can call.
|
||||
Meteor.methods({
|
||||
sendEmail(to, from, subject, text) {
|
||||
// Make sure that all arguments are strings.
|
||||
check([to, from, subject, text], [String]);
|
||||
|
||||
// Let other method calls from the same client start running, without
|
||||
// waiting for the email sending to complete.
|
||||
this.unblock();
|
||||
|
||||
return Email.sendAsync({ to, from, subject, text }).catch(err => {
|
||||
//
|
||||
});
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
{% apibox "Email.hookSend" %}
|
||||
|
||||
|
||||
@@ -59,6 +59,10 @@ email with a link the user can use to verify their email address.
|
||||
|
||||
{% apibox "Accounts.verifyEmail" %}
|
||||
|
||||
If the user trying to verify the email has 2FA enabled, this error will be thrown:
|
||||
* "Email verified, but user not logged in because 2FA is enabled [2fa-enabled]": No longer signing in the user automatically if the user has 2FA enabled.
|
||||
|
||||
|
||||
This function accepts tokens passed into the callback registered with
|
||||
[`Accounts.onEmailVerificationLink`](#Accounts-onEmailVerificationLink).
|
||||
|
||||
@@ -89,6 +93,9 @@ This function accepts tokens passed into the callbacks registered with
|
||||
[`AccountsClient#onResetPasswordLink`](#Accounts-onResetPasswordLink) and
|
||||
[`Accounts.onEnrollmentLink`](#Accounts-onEnrollmentLink).
|
||||
|
||||
If the user trying to reset the password has 2FA enabled, this error will be thrown:
|
||||
* "Changed password, but user not logged in because 2FA is enabled [2fa-enabled]": No longer signing in the user automatically if the user has 2FA enabled.
|
||||
|
||||
{% apibox "Accounts.setPassword" %}
|
||||
|
||||
{% apibox "Accounts.sendResetPasswordEmail" %}
|
||||
|
||||
@@ -91,6 +91,16 @@ You can pass an absolute or relative path.
|
||||
|
||||
**Flags for default packages**
|
||||
|
||||
`--prototype`
|
||||
|
||||
Creates a package with the prototype purpose packages(`autopublish` and `insecure`)
|
||||
if you use them you can change your collections quickly,
|
||||
but it is not supposed to be used in production.
|
||||
For more information about security you can check
|
||||
it [here](https://guide.meteor.com/security.html#checklist)
|
||||
It can be used together with other flags that create apps such as `--react` or `--typescript`.
|
||||
|
||||
|
||||
`--bare`
|
||||
|
||||
Creates a basic, blaze project.
|
||||
@@ -129,7 +139,19 @@ Create a basic [Blaze](https://blazejs.org/) app.
|
||||
|
||||
`--vue`
|
||||
|
||||
Create a basic vue-based app. See the [Vue guide](https://guide.meteor.com/vue.html)
|
||||
Create a basic [Vue 3](https://vuejs.org/) app.
|
||||
|
||||
`--react`
|
||||
|
||||
Create a basic react app. See the section on [React tutorial](https://guide.meteor.com/react.html#react-tutorial)
|
||||
for more information. This is the default.
|
||||
|
||||
`--angular`
|
||||
for more information.
|
||||
|
||||
`--vue-2`
|
||||
|
||||
Create a basic vue2-based app. See the [Vue guide](https://vue-tutorial.meteor.com/)
|
||||
for more information.
|
||||
|
||||
`--svelte`
|
||||
@@ -146,43 +168,354 @@ Create a basic [React](https://reactjs.org) + [Chakra-UI](https://chakra-ui.com/
|
||||
|
||||
`--solid`
|
||||
|
||||
Create a basic [solid](https://www.solidjs.com/) app.
|
||||
Create a basic [Solid](https://www.solidjs.com/) app.
|
||||
|
||||
**Packages**
|
||||
|
||||
| | Default (`--react`) | `--bare` | `--full` | `--minimal` | `--blaze` | `--apollo` | `--vue` | `--svelte` | `--tailwind` | `--chakra-ui` | `--solid` |
|
||||
|------------------------------------------------------------------------------------------------------|:-------------------:|:--------:|:--------:|:-----------:|:---------:|:----------:|:-------:|:----------:|:------------:|:-------------:|:---------:|
|
||||
| [autopublish](https://atmospherejs.com/meteor/autopublish) | X | | | | X | | | X | X | X | X |
|
||||
| [akryum:vue-component](https://atmospherejs.com/akryum/vue-component) | | | | | | | X | | | | |
|
||||
| [apollo](https://atmospherejs.com/meteor/apollo) | | | | | | X | | | | | |
|
||||
| [blaze-html-templates](https://atmospherejs.com/meteor/blaze-html-templates) | | | X | | X | | | | | | |
|
||||
| [ecmascript](https://atmospherejs.com/meteor/ecmascript) | X | X | X | X | X | X | X | X | X | X | X |
|
||||
| [es5-shim](https://atmospherejs.com/meteor/es5-shim) | X | X | X | X | X | X | X | X | X | X | X |
|
||||
| [hot-module-replacement](https://atmospherejs.com/meteor/hot-module-replacement) | X | | | | X | X | | | X | X | X |
|
||||
| [insecure](https://atmospherejs.com/meteor/insecure) | X | | | | X | | | X | X | X | X |
|
||||
| [johanbrook:publication-collector](https://atmospherejs.com/meteor/johanbrook/publication-collector) | | | X | | | X | | | | | |
|
||||
| [jquery](https://atmospherejs.com/meteor/jquery) | | | X | | X | | | | | | |
|
||||
| [ostrio:flow-router-extra](https://atmospherejs.com/meteor/ostrio/flow-router-extra) | | | X | | | | | | | | |
|
||||
| [less](https://atmospherejs.com/meteor/less) | | | X | | | | | | | | |
|
||||
| [meteor](https://atmospherejs.com/meteor/meteor) | | | | X | | | | | | | |
|
||||
| [meteor-base](https://atmospherejs.com/meteor/meteor-base) | X | X | X | | X | X | X | X | X | X | X |
|
||||
| [mobile-experience](https://atmospherejs.com/meteor/mobile-experience) | X | X | X | | X | X | X | X | X | X | X |
|
||||
| [mongo](https://atmospherejs.com/meteor/mongo) | X | X | X | | X | X | X | X | X | X | X |
|
||||
| [meteortesting:mocha](https://atmospherejs.com/meteortesting/mocha) | | | X | | | | X | | | | |
|
||||
| [reactive-var](https://atmospherejs.com/meteor/reactive-var) | X | X | X | | X | X | X | X | X | X | X |
|
||||
| [rdb:svelte-meteor-data](https://atmospherejs.com/rdb/svelte-meteor-data) | | | | | | | | X | | | |
|
||||
| [server-render](https://atmospherejs.com/meteor/server-render) | | | | X | | X | X | | | | |
|
||||
| [shell-server](https://atmospherejs.com/meteor/shell-server) | | X | | X | X | X | X | X | X | X | X |
|
||||
| [standard-minifier-css](https://atmospherejs.com/meteor/standard-minifier-css) | X | X | X | X | X | X | X | X | X | X | X |
|
||||
| [standard-minifier-js](https://atmospherejs.com/meteor/standard-minifier-js) | X | X | X | X | X | X | X | X | X | X | X |
|
||||
| [static-html](https://atmospherejs.com/meteor/static-html) | | X | | X | | X | X | X | | | |
|
||||
| [svelte:compiler](https://atmospherejs.com/svelte/compiler) | | | | | | | | X | | | |
|
||||
| [swydo:graphql](https://atmospherejs.com/swydo/graphql) | | | | | | X | | | | | |
|
||||
| [tailwindcss](https://tailwindcss.com) | | X | X | | X | | X | | X | | |
|
||||
| [tracker](https://atmospherejs.com/meteor/tracker) | | X | X | | X | | X | | | | |
|
||||
| [typescript](https://atmospherejs.com/meteor/typescript) | X | X | X | X | X | X | X | X | X | X | X |
|
||||
| [webapp](https://atmospherejs.com/meteor/webapp) | | | | X | | | | | | | |
|
||||
| [react-meteor-data](https://atmospherejs.com/meteor/react-meteor-data) | X | | | | | | | | X | X | |
|
||||
| | Default (`--react`) | `--bare` | `--full` | `--minimal` | `--blaze` | `--apollo` | `--vue-2` | `--svelte` | `--tailwind` | `--chakra-ui` | `--solid` | `--vue` |
|
||||
|------------------------------------------------------------------------------------------------------|:-------------------:|:--------:|:--------:|:-----------:|:---------:|:----------:|:---------:|:----------:|:------------:|:-------------:|:---------:|:-------:|
|
||||
| [autopublish](https://atmospherejs.com/meteor/autopublish) | X | | | | X | | | | X | X | X | |
|
||||
| [akryum:vue-component](https://atmospherejs.com/akryum/vue-component) | | | | | | | X | | | | | |
|
||||
| [apollo](https://atmospherejs.com/meteor/apollo) | | | | | | X | | | | | | |
|
||||
| [blaze-html-templates](https://atmospherejs.com/meteor/blaze-html-templates) | | | X | | X | | | | | | | |
|
||||
| [ecmascript](https://atmospherejs.com/meteor/ecmascript) | X | X | X | X | X | X | X | X | X | X | X | X |
|
||||
| [es5-shim](https://atmospherejs.com/meteor/es5-shim) | X | X | X | X | X | X | X | X | X | X | X | X |
|
||||
| [hot-module-replacement](https://atmospherejs.com/meteor/hot-module-replacement) | X | | | | X | X | | X | X | X | X | X |
|
||||
| [insecure](https://atmospherejs.com/meteor/insecure) | X | | | | X | | | | X | X | X | X |
|
||||
| [johanbrook:publication-collector](https://atmospherejs.com/meteor/johanbrook/publication-collector) | | | X | | | X | | | | | | |
|
||||
| [jquery](https://atmospherejs.com/meteor/jquery) | | | X | | X | | | | | | | |
|
||||
| [less](https://atmospherejs.com/meteor/less) | | | X | | | | | | | | | |
|
||||
| [meteor](https://atmospherejs.com/meteor/meteor) | | | | X | | | | | | | | |
|
||||
| [meteor-base](https://atmospherejs.com/meteor/meteor-base) | X | X | X | | X | X | X | X | X | X | X | X |
|
||||
| [mobile-experience](https://atmospherejs.com/meteor/mobile-experience) | X | X | X | | X | X | X | X | X | X | X | X |
|
||||
| [mongo](https://atmospherejs.com/meteor/mongo) | X | X | X | | X | X | X | X | X | X | X | X |
|
||||
| [meteortesting:mocha](https://atmospherejs.com/meteortesting/mocha) | | | X | | | | X | | | | | |
|
||||
| [ostrio:flow-router-extra](https://atmospherejs.com/meteor/ostrio/flow-router-extra) | | | X | | | | | | | | | |
|
||||
| [react-meteor-data](https://atmospherejs.com/meteor/react-meteor-data) | X | | | | | | | | X | X | | |
|
||||
| [reactive-var](https://atmospherejs.com/meteor/reactive-var) | X | X | X | | X | X | X | | X | X | X | X |
|
||||
| [server-render](https://atmospherejs.com/meteor/server-render) | | | | X | | X | X | | | | | |
|
||||
| [shell-server](https://atmospherejs.com/meteor/shell-server) | | X | | X | X | X | X | X | X | X | X | X |
|
||||
| [standard-minifier-css](https://atmospherejs.com/meteor/standard-minifier-css) | X | X | X | X | X | X | X | X | X | X | X | X |
|
||||
| [standard-minifier-js](https://atmospherejs.com/meteor/standard-minifier-js) | X | X | X | X | X | X | X | X | X | X | X | X |
|
||||
| [static-html](https://atmospherejs.com/meteor/static-html) | | X | | X | | X | X | X | | | | |
|
||||
| [swydo:graphql](https://atmospherejs.com/swydo/graphql) | | | | | | X | | | | | | |
|
||||
| [tailwindcss](https://tailwindcss.com) | | X | X | | X | | X | | X | | | |
|
||||
| [tracker](https://atmospherejs.com/meteor/tracker) | | X | X | | X | | X | | | | | |
|
||||
| [typescript](https://atmospherejs.com/meteor/typescript) | X | X | X | X | X | X | X | X | X | X | X | |
|
||||
| [vite:bundler](https://atmospherejs.com/vite/bundler) | | | | | | | | | | | X | X |
|
||||
| [webapp](https://atmospherejs.com/meteor/webapp) | | | | X | | | | | | | | |
|
||||
| [zodern:melte](https://atmospherejs.com/zodern/melte) | | | | | | | | X | | | | |
|
||||
| [zodern:types](https://atmospherejs.com/zodern/types) | | | | | | | | X | | | | |
|
||||
|
||||
<h2 id="meteorgenerate"> meteor generate </h2>
|
||||
|
||||
``meteor generate`` is a command for generating scaffolds for your current project. When ran without arguments, it will ask
|
||||
you what is the name of the model you want to generate, if you do want methods for your api and publications. It can be
|
||||
used as a command line only operation as well.
|
||||
|
||||
> _Important to note:_
|
||||
> By default, the generator will use JavaScript but if it detects that you have a
|
||||
``tsconfig.json`` file in your project, it will use TypeScript instead.
|
||||
|
||||
running
|
||||
```bash
|
||||
meteor generate customer
|
||||
|
||||
```
|
||||
|
||||
It will generate the following code in ``/imports/api``
|
||||

|
||||
|
||||
That will have the following code:
|
||||
|
||||
|
||||
<h3 id="meteorgenerate-collection.js">collection.js</h3>
|
||||
|
||||
```js
|
||||
|
||||
import { Mongo } from 'meteor/mongo';
|
||||
|
||||
export const CustomerCollection = new Mongo.Collection('customer');
|
||||
|
||||
```
|
||||
|
||||
|
||||
|
||||
<h3 id="meteorgenerate-methods.js">methods.js</h3>
|
||||
|
||||
```js
|
||||
import { Meteor } from 'meteor/meteor';
|
||||
import { check } from 'meteor/check';
|
||||
import { CustomerCollection } from './collection';
|
||||
|
||||
export async function create(data) {
|
||||
return CustomerCollection.insertAsync({ ...data });
|
||||
}
|
||||
|
||||
export async function update(_id, data) {
|
||||
check(_id, String);
|
||||
return CustomerCollection.updateAsync(_id, { ...data });
|
||||
}
|
||||
|
||||
export async function remove(_id) {
|
||||
check(_id, String);
|
||||
return CustomerCollection.removeAsync(_id);
|
||||
}
|
||||
|
||||
export async function findById(_id) {
|
||||
check(_id, String);
|
||||
return CustomerCollection.findOneAsync(_id);
|
||||
}
|
||||
|
||||
Meteor.methods({
|
||||
'Customer.create': create,
|
||||
'Customer.update': update,
|
||||
'Customer.remove': remove,
|
||||
'Customer.find': findById
|
||||
});
|
||||
|
||||
```
|
||||
|
||||
|
||||
|
||||
<h3 id="meteorgenerate-publication.js">publication.js</h3>
|
||||
|
||||
```js
|
||||
|
||||
import { Meteor } from 'meteor/meteor';
|
||||
import { CustomerCollection } from './collection';
|
||||
|
||||
Meteor.publish('allCustomers', function publishCustomers() {
|
||||
return CustomerCollection.find({});
|
||||
});
|
||||
|
||||
|
||||
```
|
||||
|
||||
|
||||
|
||||
|
||||
<h3 id="meteorgenerate-index.js">index.js</h3>
|
||||
|
||||
```js
|
||||
|
||||
export * from './collection';
|
||||
export * from './methods';
|
||||
export * from './publications';
|
||||
|
||||
```
|
||||
|
||||
Also, there is the same version of these methods using TypeScript, that will be shown bellow.
|
||||
|
||||
<h3 id="meteorgenerate-path">path option</h3>
|
||||
|
||||
If you want to create in another path, you can use the ``--path`` option in order to select where to place this boilerplate.
|
||||
It will generate the model in that path. Note that is used TypeScript in this example.
|
||||
|
||||
```bash
|
||||
|
||||
meteor generate another-customer --path=server/admin
|
||||
|
||||
```
|
||||
|
||||
It will generate in ``server/admin`` the another-client code:
|
||||
|
||||

|
||||
|
||||
|
||||
<h3 id="meteorgenerate-collection.ts">collection.ts</h3>
|
||||
|
||||
```typescript
|
||||
|
||||
import { Mongo } from 'meteor/mongo';
|
||||
|
||||
export type AnotherCustomer = {
|
||||
_id?: string;
|
||||
name: string;
|
||||
createdAt: Date;
|
||||
}
|
||||
|
||||
export const AnotherCustomerCollection = new Mongo.Collection<AnotherCustomer, AnotherCustomer>('another-customer');
|
||||
|
||||
```
|
||||
|
||||
<h3 id="meteorgenerate-methods.ts">methods.ts</h3>
|
||||
|
||||
```typescript
|
||||
|
||||
import { Meteor } from 'meteor/meteor';
|
||||
import { Mongo } from 'meteor/mongo';
|
||||
import { check } from 'meteor/check';
|
||||
import { AnotherCustomer, AnotherCustomerCollection } from './collection';
|
||||
|
||||
export async function create(data: AnotherCustomer) {
|
||||
return AnotherCustomerCollection.insertAsync({ ...data });
|
||||
}
|
||||
|
||||
export async function update(_id: string, data: Mongo.Modifier<AnotherCustomer>) {
|
||||
check(_id, String);
|
||||
return AnotherCustomerCollection.updateAsync(_id, { ...data });
|
||||
}
|
||||
|
||||
export async function remove(_id: string) {
|
||||
check(_id, String);
|
||||
return AnotherCustomerCollection.removeAsync(_id);
|
||||
}
|
||||
|
||||
export async function findById(_id: string) {
|
||||
check(_id, String);
|
||||
return AnotherCustomerCollection.findOneAsync(_id);
|
||||
}
|
||||
|
||||
Meteor.methods({
|
||||
'AnotherCustomer.create': create,
|
||||
'AnotherCustomer.update': update,
|
||||
'AnotherCustomer.remove': remove,
|
||||
'AnotherCustomer.find': findById
|
||||
});
|
||||
|
||||
|
||||
```
|
||||
|
||||
|
||||
|
||||
<h3 id="meteorgenerate-publications.ts">publications.ts</h3>
|
||||
|
||||
```typescript
|
||||
|
||||
import { Meteor } from 'meteor/meteor';
|
||||
import { AnotherCustomerCollection } from './collection';
|
||||
|
||||
Meteor.publish('allAnotherCustomers', function publishAnotherCustomers() {
|
||||
return AnotherCustomerCollection.find({});
|
||||
});
|
||||
|
||||
```
|
||||
|
||||
|
||||
|
||||
<h3 id="meteorgenerate-index.ts">index.ts</h3>
|
||||
|
||||
```typescript
|
||||
|
||||
export * from './collection';
|
||||
export * from './methods';
|
||||
export * from './publications';
|
||||
|
||||
```
|
||||
|
||||
|
||||
|
||||
---
|
||||
|
||||
|
||||
<h3 id="meteorgenerate-wizard"> Using the Wizard </h3>
|
||||
|
||||
|
||||
If you run the following command:
|
||||
|
||||
```bash
|
||||
meteor generate
|
||||
```
|
||||
|
||||
It will prompt the following questions.
|
||||
|
||||

|
||||
|
||||
|
||||
|
||||
|
||||
---
|
||||
|
||||
<h3 id="meteorgenerate-templating"> Using your own template </h3>
|
||||
|
||||
`--templatePath`
|
||||
|
||||
```bash
|
||||
meteor generate feed --templatePath=/scaffolds-ts
|
||||
```
|
||||

|
||||
|
||||
> Note that this is not a CLI framework inside meteor but just giving some solutions for really common problems out of the box.
|
||||
> Check out Yargs, Inquirer or Commander for more information about CLI frameworks.
|
||||
|
||||
|
||||
You can use your own templates for scaffolding your specific workloads. To do that, you should pass in a template directory URL so that it can copy it with its changes.
|
||||
|
||||
<h3 id="meteorgenerate-template-rename"> How to rename things?</h3>
|
||||
|
||||
Out of the box is provided a few functions such as replacing ``$$name$$``, ``$$PascalName$$`` and ``$$camelName$$``
|
||||
|
||||
these replacements come from this function:
|
||||
|
||||
_Note that scaffoldName is the name that you have passed as argument_
|
||||
|
||||
```js
|
||||
const transformName = (name) => {
|
||||
return name.replace(/\$\$name\$\$|\$\$PascalName\$\$|\$\$camelName\$\$/g, function (substring, args) {
|
||||
if (substring === '$$name$$') return scaffoldName;
|
||||
if (substring === '$$PascalName$$') return toPascalCase(scaffoldName);
|
||||
if (substring === '$$camelName$$') return toCamelCase(scaffoldName);
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
<h3 id="meteorgenerate-template-faq"> How to bring your own templates? </h3>
|
||||
|
||||
`--replaceFn`
|
||||
|
||||
There is an option called ``--replaceFn`` that when you pass in given a .js file with two functions it will override all templating that we have defaulted to use your given function.
|
||||
_example of a replacer file_
|
||||
```js
|
||||
export function transformFilename(scaffoldName, filename) {
|
||||
console.log(scaffoldName, filename);
|
||||
return filename
|
||||
}
|
||||
|
||||
export function transformContents(scaffoldName, contents, fileName) {
|
||||
console.log(fileName, contents);
|
||||
return contents
|
||||
}
|
||||
|
||||
```
|
||||
If you run your command like this:
|
||||
|
||||
```bash
|
||||
meteor generate feed --replaceFn=/fn/replace.js
|
||||
```
|
||||
It will generate files full of ``$$PascalCase$$``using the meteor provided templates.
|
||||
|
||||
A better example of this feature would be the following js file:
|
||||
```js
|
||||
const toPascalCase = (str) => {
|
||||
if(!str.includes('-')) return str.charAt(0).toUpperCase() + str.slice(1);
|
||||
else return str.split('-').map(toPascalCase).join('');
|
||||
}
|
||||
const toCamelCase = (str) => {
|
||||
if(!str.includes('-')) return str.charAt(0).toLowerCase() + str.slice(1);
|
||||
else return str.split('-').map(toPascalCase).join('');
|
||||
}
|
||||
|
||||
const transformName = (scaffoldName, str) => {
|
||||
return str.replace(/\$\$name\$\$|\$\$PascalName\$\$|\$\$camelName\$\$/g, function (substring, args) {
|
||||
if (substring === '$$name$$') return scaffoldName;
|
||||
if (substring === '$$PascalName$$') return toPascalCase(scaffoldName);
|
||||
if (substring === '$$camelName$$') return toCamelCase(scaffoldName);
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
export function transformFilename(scaffoldName, filename) {
|
||||
return transformName(scaffoldName, filename);
|
||||
}
|
||||
|
||||
export function transformContents(scaffoldName, contents, fileName) {
|
||||
return transformName(scaffoldName, contents);
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
|
||||
|
||||
<h2 id="meteorloginlogout">meteor login / logout</h2>
|
||||
|
||||
@@ -593,7 +926,7 @@ from npm to your `node_modules` directory and save its usage in your
|
||||
Using the `meteor npm ...` commands in place of traditional `npm ...` commands
|
||||
is particularly important when using Node.js modules that have binary
|
||||
dependencies that make native C calls (like [`bcrypt`](https://www.npmjs.com/package/bcrypt))
|
||||
because doing so ensures that they are built using the same libaries.
|
||||
because doing so ensures that they are built using the same libraries.
|
||||
|
||||
Additionally, this access to the npm that comes with Meteor avoids the need to
|
||||
download and install npm separately.
|
||||
@@ -604,8 +937,8 @@ The `meteor node` command calls the
|
||||
[`node`](https://nodejs.org) version bundled with Meteor itself.
|
||||
|
||||
> This is not to be confused with [`meteor shell`](#meteorshell), which provides
|
||||
an almost identical experience but also gives you access to the "server" context
|
||||
of a Meteor application. Typically, `meteor shell` will be preferred.
|
||||
> an almost identical experience but also gives you access to the "server" context
|
||||
> of a Meteor application. Typically, `meteor shell` will be preferred.
|
||||
|
||||
Additional parameters can be passed in the same way as the `node` command, and
|
||||
the [Node.js documentation](https://nodejs.org/dist/latest-v4.x/docs/api/cli.html)
|
||||
|
||||
@@ -5,6 +5,7 @@ edit_branch: 'devel'
|
||||
edit_path: 'guide'
|
||||
content_root: 'content'
|
||||
versions:
|
||||
- '2.9'
|
||||
- '2.8'
|
||||
- '2.7'
|
||||
- '2.6'
|
||||
@@ -37,7 +38,7 @@ sidebar_categories:
|
||||
- index
|
||||
- code-style
|
||||
- structure
|
||||
- 2.8-migration
|
||||
- 2.9-migration
|
||||
Data:
|
||||
- collections
|
||||
- data-loading
|
||||
|
||||
@@ -157,7 +157,7 @@ As `callAsync` returns a promise, it'll be solved in the future. So you need to
|
||||
|
||||
It's also important to understand what will happen if you call an async method with `Meteor.call`, and vice versa.
|
||||
|
||||
If you can an async method with `Meteor.call` in the client, and you don't have the package `insecure` on your project, an error like this will be thrown:
|
||||
If you call an async method with `Meteor.call` in the client, and you don't have the package `insecure` on your project, an error like this will be thrown:
|
||||
|
||||
<img src="images/live-data-error.png">
|
||||
|
||||
|
||||
139
guide/source/2.9-migration.md
Normal file
139
guide/source/2.9-migration.md
Normal file
@@ -0,0 +1,139 @@
|
||||
---
|
||||
title: Migrating to Meteor 2.9
|
||||
description: How to migrate your application to Meteor 2.9.
|
||||
---
|
||||
|
||||
Meteor `2.9` introduces some changes in the `accounts` packages, the new method `Email.sendAsync`, the new method `Meteor.userAsync`, and more. For a complete breakdown of the changes, please refer to the [changelog](http://docs.meteor.com/changelog.html).
|
||||
|
||||
|
||||
<h3 id="why-like-this">Why is this new API important?</h3>
|
||||
|
||||
You may know that on Meteor we use a package called [Fibers](https://github.com/laverdet/node-fibers). This package is what makes it possible to call an async function inside Meteor in a sync way (without having to wait for the promise to resolve).
|
||||
|
||||
But starting from Node 16, Fibers will stop working, so Meteor needs to move away from Fibers, otherwise, we'll be stuck on Node 14.
|
||||
|
||||
If you want to know more about the plan, you can check this [discussion](https://github.com/meteor/meteor/discussions/11505).
|
||||
|
||||
<h3 id="why-now">Why doing this change now?</h3>
|
||||
|
||||
This will be a considerable change for older Meteor applications, and some parts of the code of any Meteor app will have to be adjusted eventually. So it's important to start the migration process as soon as possible.
|
||||
|
||||
The migration process started in version 2.8. We recommend you [check that out](2.8-migration.htm) first in case you skipped.
|
||||
|
||||
<h3 id="should-i-update">Can I update to this version without changing my app?</h3>
|
||||
|
||||
Yes. You can update to this version without changing your app.
|
||||
|
||||
<h2 id="what-is-new">What's new?</h2>
|
||||
|
||||
Let's start with the accounts and OAuth packages. Some methods had to be restructured to work without Fibers in the future. The current methods will continue working as of today, but if you use some of the methods we'll mention below in custom login packages, we recommend you adapt them.
|
||||
|
||||
Internal methods that are now async:
|
||||
|
||||
- **_attemptLogin**
|
||||
- **_loginMethod**
|
||||
- **_runLoginHandlers**
|
||||
- **Accounts._checkPassword**: still works as always, but now has a new version called `Accounts._checkPasswordAsync`.
|
||||
|
||||
|
||||
We also have changes to asynchronous context in the registry of handlers for OAuth services.
|
||||
|
||||
Now, the OAuth.Register method accepts an async handler, and it is possible to use the await option internally, avoiding to use methods that run on Fibers, such as **HTTP** (deprecated Meteor package), `Meteor.wrapAsync` and `Promise.await`.
|
||||
|
||||
Before the changes you would have something like:
|
||||
|
||||
```js
|
||||
OAuth.registerService('github', 2, null, (query) => {
|
||||
const accessTokenCall = Meteor.wrapAsync(getAccessToken);
|
||||
const accessToken = accessTokenCall(query);
|
||||
const identityCall = Meteor.wrapAsync(getIdentity);
|
||||
…
|
||||
});
|
||||
```
|
||||
|
||||
Now you have:
|
||||
|
||||
```js
|
||||
OAuth.registerService('github', 2, null, async (query) => {
|
||||
const accessToken = await getAccessToken(query);
|
||||
const identity = await getIdentity(accessToken);
|
||||
const emails = await getEmails(accessToken);
|
||||
…
|
||||
});
|
||||
```
|
||||
|
||||
<h3 id="new-async-methods">New async methods</h3>
|
||||
|
||||
We now have async version of methods that you already use. They are:
|
||||
|
||||
- [Email.sendAsync()](https://github.com/meteor/meteor/pull/12101/files#diff-b2453acdfd34fb563a1e258956d2733ab06a2aa77c87e402cfa53a86a48133a8R86-R107)
|
||||
- [Meteor.userAsync()](https://github.com/meteor/meteor/pull/12274)
|
||||
- [CssTools.minifyCssAsync()](https://github.com/meteor/meteor/pull/12105)
|
||||
|
||||
<h3 id="breaking-async">Breaking async</h3>
|
||||
|
||||
`Accounts.createUserVerifyingEmail` is now completely async:
|
||||
|
||||
- [Accounts.createUserVerifyingEmail](https://github.com/meteor/meteor/issues/12398)
|
||||
|
||||
To upgrade change from
|
||||
```js
|
||||
Meteor.methods({
|
||||
createUserAccount (user) {
|
||||
/**
|
||||
* This seems to be the issue.
|
||||
* Using the other method `createUser` works as expected.
|
||||
*/
|
||||
Accounts.createUserVerifyingEmail({
|
||||
username: user.username,
|
||||
email: user.email,
|
||||
password: user.password,
|
||||
});
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
to
|
||||
|
||||
```js
|
||||
Meteor.methods({
|
||||
async createUserAccount (user) {
|
||||
await Accounts.createUserVerifyingEmail({
|
||||
username: user.username,
|
||||
email: user.email,
|
||||
password: user.password,
|
||||
});
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
<h3 id="accounts-base">Accounts-base without service-configuration</h3>
|
||||
|
||||
Now `accounts-base` is [no longer tied up](https://github.com/meteor/meteor/pull/12202) with `service-configuration`. So, if you don't use third-party login on your project, you don't need to add the package `service-configuration` anymore.
|
||||
|
||||
<h2 id="older-versions">Migrating from a version older than 2.8?</h2>
|
||||
|
||||
If you're migrating from a version of Meteor older than Meteor 2.8, there may be important considerations not listed in this guide. Please review the older migration guides for details:
|
||||
|
||||
* [Migrating to Meteor 2.8](2.8-migration.html) (from 2.7)
|
||||
* [Migrating to Meteor 2.7](2.7-migration.html) (from 2.6)
|
||||
* [Migrating to Meteor 2.6](2.6-migration.html) (from 2.5)
|
||||
* [Migrating to Meteor 2.5](2.5-migration.html) (from 2.4)
|
||||
* [Migrating to Meteor 2.4](2.4-migration.html) (from 2.3)
|
||||
* [Migrating to Meteor 2.3](2.3-migration.html) (from 2.2)
|
||||
* [Migrating to Meteor 2.2](2.2-migration.html) (from 2.0)
|
||||
* [Migrating to Meteor 2.0](2.0-migration.html) (from 1.12)
|
||||
* [Migrating to Meteor 1.12](1.12-migration.html) (from 1.11)
|
||||
* [Migrating to Meteor 1.11](1.11-migration.html) (from 1.10.2)
|
||||
* [Migrating to Meteor 1.10.2](1.10.2-migration.html) (from 1.10)
|
||||
* [Migrating to Meteor 1.10](1.10-migration.html) (from 1.9.3)
|
||||
* [Migrating to Meteor 1.9.3](1.9.3-migration.html) (from 1.9)
|
||||
* [Migrating to Meteor 1.9](1.9-migration.html) (from 1.8.3)
|
||||
* [Migrating to Meteor 1.8.3](1.8.3-migration.html) (from 1.8.2)
|
||||
* [Migrating to Meteor 1.8.2](1.8.2-migration.html) (from 1.8)
|
||||
* [Migrating to Meteor 1.8](1.8-migration.html) (from 1.7)
|
||||
* [Migrating to Meteor 1.7](1.7-migration.html) (from 1.6)
|
||||
* [Migrating to Meteor 1.6](1.6-migration.html) (from 1.5)
|
||||
* [Migrating to Meteor 1.5](1.5-migration.html) (from 1.4)
|
||||
* [Migrating to Meteor 1.4](1.4-migration.html) (from 1.3)
|
||||
* [Migrating to Meteor 1.3](1.3-migration.html) (from 1.2)
|
||||
3
meteor
3
meteor
@@ -1,6 +1,7 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
BUNDLE_VERSION=14.21.1.0
|
||||
BUNDLE_VERSION=14.21.2.2
|
||||
|
||||
|
||||
# OS Check. Put here because here is where we download the precompiled
|
||||
# bundles that are arch specific.
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# eslint-config-meteor
|
||||
# @meteorjs/eslint-config-meteor
|
||||
|
||||
This is an [ESLint](https://eslint.org) configuration for [Meteor](https://www.meteor.com) apps which implements the recommendations from the [Meteor Guide](https://guide.meteor.com/)'s section on [Code style](https://guide.meteor.com/code-style.html#eslint).
|
||||
|
||||
|
||||
@@ -1,21 +1,16 @@
|
||||
module.exports = {
|
||||
parser: 'babel-eslint',
|
||||
parserOptions: {
|
||||
allowImportExportEverywhere: true
|
||||
allowImportExportEverywhere: true,
|
||||
},
|
||||
env: {
|
||||
node: true,
|
||||
browser: true
|
||||
browser: true,
|
||||
},
|
||||
plugins: [
|
||||
'meteor'
|
||||
],
|
||||
extends: [
|
||||
'airbnb',
|
||||
'plugin:meteor/recommended'
|
||||
],
|
||||
plugins: ['meteor'],
|
||||
extends: ['airbnb', 'plugin:meteor/recommended'],
|
||||
settings: {
|
||||
'import/resolver': 'meteor'
|
||||
'import/resolver': 'meteor',
|
||||
},
|
||||
rules: {
|
||||
'react/jsx-filename-extension': 0,
|
||||
@@ -30,24 +25,21 @@ module.exports = {
|
||||
'no-underscore-dangle': [
|
||||
'error',
|
||||
{
|
||||
allow: [
|
||||
'_id',
|
||||
'_ensureIndex'
|
||||
]
|
||||
}
|
||||
allow: ['_id', '_ensureIndex'],
|
||||
},
|
||||
],
|
||||
'object-shorthand': [
|
||||
'error',
|
||||
'always',
|
||||
{
|
||||
avoidQuotes: false
|
||||
}
|
||||
avoidQuotes: false,
|
||||
},
|
||||
],
|
||||
|
||||
'space-before-function-paren': 0,
|
||||
|
||||
|
||||
// for Meteor API's that rely on `this` context, e.g. Template.onCreated and publications
|
||||
'func-names': 0,
|
||||
'prefer-arrow-callback': 0
|
||||
}
|
||||
'prefer-arrow-callback': 0,
|
||||
},
|
||||
};
|
||||
|
||||
@@ -8,14 +8,14 @@
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/meteor/eslint-config-meteor.git"
|
||||
"url": "git+https://github.com/meteor/meteor.git"
|
||||
},
|
||||
"author": "David Burles",
|
||||
"license": "MIT",
|
||||
"bugs": {
|
||||
"url": "https://github.com/meteor/eslint-config-meteor/issues"
|
||||
"url": "https://github.com/meteor/meteor/issues"
|
||||
},
|
||||
"homepage": "https://github.com/meteor/eslint-config-meteor#readme",
|
||||
"homepage": "https://github.com/meteor/meteor/tree/devel/npm-packages/eslint-config-meteor#readme",
|
||||
"peerDependencies": {
|
||||
"babel-eslint": ">= 7",
|
||||
"eslint": ">= 3",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "eslint-plugin-meteor",
|
||||
"version": "7.3.0",
|
||||
"version": "7.4.0",
|
||||
"author": "Dominik Ferber <dominik.ferber+npm@gmail.com>",
|
||||
"description": "Meteor specific linting rules for ESLint",
|
||||
"main": "lib/index.js",
|
||||
|
||||
@@ -14,8 +14,8 @@ var packageJson = {
|
||||
pacote: "https://github.com/meteor/pacote/tarball/a81b0324686e85d22c7688c47629d4009000e8b8",
|
||||
"node-gyp": "8.0.0",
|
||||
"node-pre-gyp": "0.15.0",
|
||||
typescript: "4.5.4",
|
||||
"@meteorjs/babel": "7.16.0-beta.1",
|
||||
typescript: "4.6.4",
|
||||
"@meteorjs/babel": "7.17.1-beta.0",
|
||||
// Keep the versions of these packages consistent with the versions
|
||||
// found in dev-bundle-server-package.js.
|
||||
"meteor-promise": "0.9.0",
|
||||
|
||||
@@ -185,12 +185,16 @@ function getDefaultsForNode8(features) {
|
||||
|
||||
// Ensure that async functions run in a Fiber, while also taking
|
||||
// full advantage of native async/await support in Node 8.
|
||||
combined.plugins.push([require("./plugins/async-await.js"), {
|
||||
// Do not transform `await x` to `Promise.await(x)`, since Node
|
||||
// 8 has native support for await expressions.
|
||||
useNativeAsyncAwait: false
|
||||
}]);
|
||||
const isFiberDisabled = process.env.DISABLE_FIBERS === '1';
|
||||
const ignoreAsyncPlugin = process.env.IGNORE_ASYNC_PLUGIN === '1';
|
||||
|
||||
if (!ignoreAsyncPlugin) {
|
||||
combined.plugins.push([require("./plugins/async-await.js"), {
|
||||
// Do not transform `await x` to `Promise.await(x)`, since Node
|
||||
// 8 has native support for await expressions.
|
||||
useNativeAsyncAwait: isFiberDisabled,
|
||||
}]);
|
||||
}
|
||||
// Enable async generator functions proposal.
|
||||
combined.plugins.push(require("@babel/plugin-proposal-async-generator-functions"));
|
||||
}
|
||||
|
||||
3880
npm-packages/meteor-babel/package-lock.json
generated
3880
npm-packages/meteor-babel/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@meteorjs/babel",
|
||||
"author": "Meteor <dev@meteor.com>",
|
||||
"version": "7.16.0-beta.1",
|
||||
"version": "7.18.0-beta.4",
|
||||
"license": "MIT",
|
||||
"description": "Babel wrapper package for use with Meteor",
|
||||
"keywords": [
|
||||
@@ -37,7 +37,7 @@
|
||||
"@babel/plugin-transform-modules-commonjs": "^7.16.8",
|
||||
"@babel/plugin-transform-runtime": "^7.17.0",
|
||||
"@babel/preset-react": "^7.16.7",
|
||||
"@babel/runtime": "^7.17.2",
|
||||
"@babel/runtime": "7.17.2",
|
||||
"@babel/template": "^7.16.7",
|
||||
"@babel/traverse": "^7.17.0",
|
||||
"@babel/types": "^7.17.0",
|
||||
@@ -47,7 +47,7 @@
|
||||
"convert-source-map": "^1.6.0",
|
||||
"lodash": "^4.17.21",
|
||||
"meteor-babel-helpers": "0.0.3",
|
||||
"typescript": "^4.5.4"
|
||||
"typescript": "~4.6.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/plugin-proposal-decorators": "7.14.5",
|
||||
|
||||
@@ -9,7 +9,7 @@ module.exports = function (babel) {
|
||||
Function: {
|
||||
exit: function (path) {
|
||||
const node = path.node;
|
||||
if (! node.async) {
|
||||
if (!node.async) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -11,19 +11,21 @@ Module.prototype.resolve = function (id) {
|
||||
|
||||
require("@meteorjs/reify/lib/runtime").enable(Module.prototype);
|
||||
|
||||
require("meteor-promise").makeCompatible(
|
||||
global.Promise = global.Promise ||
|
||||
require("promise/lib/es6-extensions"),
|
||||
require("fibers")
|
||||
);
|
||||
if (!process.env.DISABLE_FIBERS) {
|
||||
require("meteor-promise").makeCompatible(
|
||||
global.Promise = global.Promise ||
|
||||
require("promise/lib/es6-extensions"),
|
||||
require("fibers")
|
||||
);
|
||||
|
||||
// If Promise.asyncApply is defined, use it to wrap calls to
|
||||
// regeneratorRuntime.async so that the entire async function will run in
|
||||
// its own Fiber, not just the code that comes after the first await.
|
||||
if (typeof Promise.asyncApply === "function") {
|
||||
var regeneratorRuntime = require("@babel/runtime/regenerator");
|
||||
var realAsync = regeneratorRuntime.async;
|
||||
regeneratorRuntime.async = function (innerFn) {
|
||||
return Promise.asyncApply(realAsync, regeneratorRuntime, arguments);
|
||||
};
|
||||
if (typeof Promise.asyncApply === "function") {
|
||||
var regeneratorRuntime = require("@babel/runtime/regenerator");
|
||||
var realAsync = regeneratorRuntime.async;
|
||||
regeneratorRuntime.async = function (innerFn) {
|
||||
return Promise.asyncApply(realAsync, regeneratorRuntime, arguments);
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,6 +14,8 @@ npm install -g meteor
|
||||
|
||||
| NPM Package | Meteor Official Release |
|
||||
|-------------|-------------------------|
|
||||
| 2.9.1 | 2.9.1 |
|
||||
| 2.9.0 | 2.9.0 |
|
||||
| 2.8.2 | 2.8.1 |
|
||||
| 2.8.1 | 2.8.1 |
|
||||
| 2.8.0 | 2.8.0 |
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
const path = require('path');
|
||||
const os = require('os');
|
||||
|
||||
const METEOR_LATEST_VERSION = '2.8.1';
|
||||
const METEOR_LATEST_VERSION = '2.9.1';
|
||||
const sudoUser = process.env.SUDO_USER || '';
|
||||
function isRoot() {
|
||||
return process.getuid && process.getuid() === 0;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "meteor",
|
||||
"version": "2.8.2",
|
||||
"version": "2.9.1",
|
||||
"description": "Install Meteor",
|
||||
"main": "install.js",
|
||||
"scripts": {
|
||||
|
||||
@@ -798,6 +798,11 @@ if (Package.blaze) {
|
||||
*/
|
||||
Template.registerHelper('currentUser', () => Meteor.user());
|
||||
|
||||
// TODO: the code above needs to be changed to Meteor.userAsync() when we have
|
||||
// a way to make it reactive using async.
|
||||
// Template.registerHelper('currentUserAsync',
|
||||
// async () => await Meteor.userAsync());
|
||||
|
||||
/**
|
||||
* @global
|
||||
* @name loggingIn
|
||||
|
||||
@@ -94,6 +94,20 @@ Tinytest.addAsync(
|
||||
}
|
||||
);
|
||||
|
||||
Tinytest.addAsync(
|
||||
'accounts async - Meteor.loggingIn() is false after login has completed',
|
||||
(test, done) => {
|
||||
logoutAndCreateUser(test, done, () => {
|
||||
// Login then verify loggingIn is false after login has completed
|
||||
Meteor.loginWithPassword(username, password, async () => {
|
||||
test.isFalse(Meteor.loggingIn());
|
||||
test.isTrue(await Meteor.userAsync());
|
||||
removeTestUser(done);
|
||||
});
|
||||
});
|
||||
}
|
||||
);
|
||||
|
||||
Tinytest.addAsync(
|
||||
'accounts - Meteor.loggingOut() is true right after a logout call',
|
||||
(test, done) => {
|
||||
@@ -150,7 +164,7 @@ Tinytest.addAsync(
|
||||
);
|
||||
|
||||
Tinytest.addAsync(
|
||||
'accounts - Meteor.user obeys explicit and default field selectors',
|
||||
'accounts - Meteor.user() obeys explicit and default field selectors',
|
||||
(test, done) => {
|
||||
logoutAndCreateUser(test, done, () => {
|
||||
Meteor.loginWithPassword(username, password, () => {
|
||||
@@ -178,6 +192,38 @@ Tinytest.addAsync(
|
||||
}
|
||||
);
|
||||
|
||||
Tinytest.addAsync(
|
||||
'accounts async - Meteor.userAsync() obeys explicit and default field selectors',
|
||||
(test, done) => {
|
||||
logoutAndCreateUser(test, done, () => {
|
||||
Meteor.loginWithPassword(username, password, async () => {
|
||||
// by default, all fields should be returned
|
||||
let user;
|
||||
user = await Meteor.userAsync();
|
||||
test.equal(user.profile[excludeField], excludeValue);
|
||||
|
||||
// this time we want to exclude the default fields
|
||||
const options = Accounts._options;
|
||||
Accounts._options = {};
|
||||
Accounts.config({ defaultFieldSelector: { ['profile.' + defaultExcludeField]: 0 } });
|
||||
|
||||
user = await Meteor.userAsync();
|
||||
test.isUndefined(user.profile[defaultExcludeField]);
|
||||
test.equal(user.profile[excludeField], excludeValue);
|
||||
test.equal(user.profile.name, username);
|
||||
|
||||
// this time we only want certain fields...
|
||||
|
||||
user = await Meteor.userAsync({ fields: { 'profile.name': 1 } });
|
||||
test.isUndefined(user.profile[excludeField]);
|
||||
test.isUndefined(user.profile[defaultExcludeField]);
|
||||
test.equal(user.profile.name, username);
|
||||
Accounts._options = options;
|
||||
removeTestUser(done);
|
||||
});
|
||||
});
|
||||
}
|
||||
);
|
||||
|
||||
Tinytest.addAsync(
|
||||
'accounts-2fa - Meteor.loginWithPasswordAnd2faCode() fails when token is not provided',
|
||||
|
||||
@@ -79,40 +79,6 @@ export class AccountsCommon {
|
||||
// should come up with a more generic way to do this (eg, with some sort of
|
||||
// symbolic error code rather than a number).
|
||||
this.LoginCancelledError.numericError = 0x8acdc2f;
|
||||
|
||||
// loginServiceConfiguration and ConfigError are maintained for backwards compatibility
|
||||
Meteor.startup(() => {
|
||||
const { ServiceConfiguration } = Package['service-configuration'];
|
||||
this.loginServiceConfiguration = ServiceConfiguration.configurations;
|
||||
this.ConfigError = ServiceConfiguration.ConfigError;
|
||||
|
||||
const settings = Meteor.settings?.packages?.['accounts-base'];
|
||||
if (settings) {
|
||||
if (settings.oauthSecretKey) {
|
||||
if (!Package['oauth-encryption']) {
|
||||
throw new Error(
|
||||
'The oauth-encryption package must be loaded to set oauthSecretKey'
|
||||
);
|
||||
}
|
||||
Package['oauth-encryption'].OAuthEncryption.loadKey(
|
||||
settings.oauthSecretKey
|
||||
);
|
||||
delete settings.oauthSecretKey;
|
||||
}
|
||||
// Validate config options keys
|
||||
Object.keys(settings).forEach(key => {
|
||||
if (!VALID_CONFIG_KEYS.includes(key)) {
|
||||
// TODO Consider just logging a debug message instead to allow for additional keys in the settings here?
|
||||
throw new Meteor.Error(
|
||||
`Accounts configuration: Invalid key: ${key}`
|
||||
);
|
||||
} else {
|
||||
// set values in Accounts._options
|
||||
this._options[key] = settings[key];
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -170,6 +136,18 @@ export class AccountsCommon {
|
||||
: null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @summary Get the current user record, or `null` if no user is logged in.
|
||||
* @locus Anywhere
|
||||
* @param {Object} [options]
|
||||
* @param {MongoFieldSpecifier} options.fields Dictionary of fields to return or exclude.
|
||||
*/
|
||||
async userAsync(options) {
|
||||
const userId = this.userId();
|
||||
return userId
|
||||
? this.users.findOneAsync(userId, this._addDefaultFieldSelector(options))
|
||||
: null;
|
||||
}
|
||||
// Set up config for the accounts system. Call this on both the client
|
||||
// and the server.
|
||||
//
|
||||
@@ -264,6 +242,7 @@ export class AccountsCommon {
|
||||
// Validate config options keys
|
||||
Object.keys(options).forEach(key => {
|
||||
if (!VALID_CONFIG_KEYS.includes(key)) {
|
||||
// TODO Consider just logging a debug message instead to allow for additional keys in the settings here?
|
||||
throw new Meteor.Error(`Accounts.config: Invalid key: ${key}`);
|
||||
}
|
||||
});
|
||||
@@ -418,6 +397,15 @@ Meteor.userId = () => Accounts.userId();
|
||||
*/
|
||||
Meteor.user = options => Accounts.user(options);
|
||||
|
||||
/**
|
||||
* @summary Get the current user record, or `null` if no user is logged in. A reactive data source.
|
||||
* @locus Anywhere but publish functions
|
||||
* @importFromPackage meteor
|
||||
* @param {Object} [options]
|
||||
* @param {MongoFieldSpecifier} options.fields Dictionary of fields to return or exclude.
|
||||
*/
|
||||
Meteor.userAsync = options => Accounts.userAsync(options);
|
||||
|
||||
// how long (in days) until a login token expires
|
||||
const DEFAULT_LOGIN_EXPIRATION_DAYS = 90;
|
||||
// how long (in days) until reset password token expires
|
||||
@@ -430,9 +418,6 @@ const DEFAULT_PASSWORD_ENROLL_TOKEN_EXPIRATION_DAYS = 30;
|
||||
const MIN_TOKEN_LIFETIME_CAP_SECS = 3600; // one hour
|
||||
// how often (in milliseconds) we check for expired tokens
|
||||
export const EXPIRE_TOKENS_INTERVAL_MS = 600 * 1000; // 10 minutes
|
||||
// how long we wait before logging out clients when Meteor.logoutOtherClients is
|
||||
// called
|
||||
export const CONNECTION_CLOSE_DELAY_MS = 10 * 1000;
|
||||
// A large number of expiration days (approximately 100 years worth) that is
|
||||
// used when creating unexpiring tokens.
|
||||
const LOGIN_UNEXPIRING_TOKEN_DAYS = 365 * 100;
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import crypto from 'crypto';
|
||||
import { Meteor } from 'meteor/meteor'
|
||||
import {
|
||||
AccountsCommon,
|
||||
EXPIRE_TOKENS_INTERVAL_MS,
|
||||
@@ -434,7 +435,7 @@ export class AccountsServer extends AccountsCommon {
|
||||
// If the login is allowed and isn't aborted by a validate login hook
|
||||
// callback, log in the user.
|
||||
//
|
||||
_attemptLogin(
|
||||
async _attemptLogin(
|
||||
methodInvocation,
|
||||
methodName,
|
||||
methodArgs,
|
||||
@@ -494,18 +495,18 @@ export class AccountsServer extends AccountsCommon {
|
||||
// Ensure that thrown exceptions are caught and that login hook
|
||||
// callbacks are still called.
|
||||
//
|
||||
_loginMethod(
|
||||
async _loginMethod(
|
||||
methodInvocation,
|
||||
methodName,
|
||||
methodArgs,
|
||||
type,
|
||||
fn
|
||||
) {
|
||||
return this._attemptLogin(
|
||||
return await this._attemptLogin(
|
||||
methodInvocation,
|
||||
methodName,
|
||||
methodArgs,
|
||||
tryLoginMethod(type, fn)
|
||||
await tryLoginMethod(type, fn)
|
||||
);
|
||||
};
|
||||
|
||||
@@ -582,11 +583,10 @@ export class AccountsServer extends AccountsCommon {
|
||||
// Try all of the registered login handlers until one of them doesn't
|
||||
// return `undefined`, meaning it handled this call to `login`. Return
|
||||
// that return value.
|
||||
_runLoginHandlers(methodInvocation, options) {
|
||||
async _runLoginHandlers(methodInvocation, options) {
|
||||
for (let handler of this._loginHandlers) {
|
||||
const result = tryLoginMethod(
|
||||
handler.name,
|
||||
() => handler.handler.call(methodInvocation, options)
|
||||
const result = await tryLoginMethod(handler.name, async () =>
|
||||
await handler.handler.call(methodInvocation, options)
|
||||
);
|
||||
|
||||
if (result) {
|
||||
@@ -594,7 +594,10 @@ export class AccountsServer extends AccountsCommon {
|
||||
}
|
||||
|
||||
if (result !== undefined) {
|
||||
throw new Meteor.Error(400, "A login handler should return a result or undefined");
|
||||
throw new Meteor.Error(
|
||||
400,
|
||||
'A login handler should return a result or undefined'
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -639,14 +642,15 @@ export class AccountsServer extends AccountsCommon {
|
||||
// If successful, returns {token: reconnectToken, id: userId}
|
||||
// If unsuccessful (for example, if the user closed the oauth login popup),
|
||||
// throws an error describing the reason
|
||||
methods.login = function (options) {
|
||||
methods.login = async function (options) {
|
||||
// Login handlers should really also check whatever field they look at in
|
||||
// options, but we don't enforce it.
|
||||
check(options, Object);
|
||||
|
||||
const result = accounts._runLoginHandlers(this, options);
|
||||
const result = await accounts._runLoginHandlers(this, options);
|
||||
//console.log({result});
|
||||
|
||||
return accounts._attemptLogin(this, "login", arguments, result);
|
||||
return await accounts._attemptLogin(this, "login", arguments, result);
|
||||
};
|
||||
|
||||
methods.logout = function () {
|
||||
@@ -721,14 +725,19 @@ export class AccountsServer extends AccountsCommon {
|
||||
throw new Meteor.Error(403, "Service unknown");
|
||||
}
|
||||
|
||||
const { ServiceConfiguration } = Package['service-configuration'];
|
||||
if (ServiceConfiguration.configurations.findOne({service: options.service}))
|
||||
throw new Meteor.Error(403, `Service ${options.service} already configured`);
|
||||
if (Package['service-configuration']) {
|
||||
const { ServiceConfiguration } = Package['service-configuration'];
|
||||
if (ServiceConfiguration.configurations.findOne({service: options.service}))
|
||||
throw new Meteor.Error(403, `Service ${options.service} already configured`);
|
||||
|
||||
if (hasOwn.call(options, 'secret') && usingOAuthEncryption())
|
||||
options.secret = OAuthEncryption.seal(options.secret);
|
||||
if (Package["oauth-encryption"]) {
|
||||
const { OAuthEncryption } = Package["oauth-encryption"]
|
||||
if (hasOwn.call(options, 'secret') && OAuthEncryption.keyIsLoaded())
|
||||
options.secret = OAuthEncryption.seal(options.secret);
|
||||
}
|
||||
|
||||
ServiceConfiguration.configurations.insert(options);
|
||||
ServiceConfiguration.configurations.insert(options);
|
||||
}
|
||||
};
|
||||
|
||||
accounts._server.methods(methods);
|
||||
@@ -753,8 +762,10 @@ export class AccountsServer extends AccountsCommon {
|
||||
|
||||
// Publish all login service configuration fields other than secret.
|
||||
this._server.publish("meteor.loginServiceConfiguration", () => {
|
||||
const { ServiceConfiguration } = Package['service-configuration'];
|
||||
return ServiceConfiguration.configurations.find({}, {fields: {secret: 0}});
|
||||
if (Package['service-configuration']) {
|
||||
const { ServiceConfiguration } = Package['service-configuration'];
|
||||
return ServiceConfiguration.configurations.find({}, {fields: {secret: 0}});
|
||||
}
|
||||
}, {is_auto: true}); // not technically autopublish, but stops the warning.
|
||||
|
||||
// Use Meteor.startup to give other packages a chance to call
|
||||
@@ -1507,10 +1518,10 @@ const cloneAttemptWithConnection = (connection, attempt) => {
|
||||
return clonedAttempt;
|
||||
};
|
||||
|
||||
const tryLoginMethod = (type, fn) => {
|
||||
const tryLoginMethod = async (type, fn) => {
|
||||
let result;
|
||||
try {
|
||||
result = fn();
|
||||
result = await fn();
|
||||
}
|
||||
catch (e) {
|
||||
result = {error: e};
|
||||
@@ -1679,17 +1690,7 @@ const setExpireTokensInterval = accounts => {
|
||||
}, EXPIRE_TOKENS_INTERVAL_MS);
|
||||
};
|
||||
|
||||
///
|
||||
/// OAuth Encryption Support
|
||||
///
|
||||
|
||||
const OAuthEncryption =
|
||||
Package["oauth-encryption"] &&
|
||||
Package["oauth-encryption"].OAuthEncryption;
|
||||
|
||||
const usingOAuthEncryption = () => {
|
||||
return OAuthEncryption && OAuthEncryption.keyIsLoaded();
|
||||
};
|
||||
const OAuthEncryption = Package["oauth-encryption"]?.OAuthEncryption;
|
||||
|
||||
// OAuth service data is temporarily stored in the pending credentials
|
||||
// collection during the oauth authentication process. Sensitive data
|
||||
@@ -1701,44 +1702,12 @@ const usingOAuthEncryption = () => {
|
||||
const pinEncryptedFieldsToUser = (serviceData, userId) => {
|
||||
Object.keys(serviceData).forEach(key => {
|
||||
let value = serviceData[key];
|
||||
if (OAuthEncryption && OAuthEncryption.isSealed(value))
|
||||
if (OAuthEncryption?.isSealed(value))
|
||||
value = OAuthEncryption.seal(OAuthEncryption.open(value), userId);
|
||||
serviceData[key] = value;
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
// Encrypt unencrypted login service secrets when oauth-encryption is
|
||||
// added.
|
||||
//
|
||||
// XXX For the oauthSecretKey to be available here at startup, the
|
||||
// developer must call Accounts.config({oauthSecretKey: ...}) at load
|
||||
// time, instead of in a Meteor.startup block, because the startup
|
||||
// block in the app code will run after this accounts-base startup
|
||||
// block. Perhaps we need a post-startup callback?
|
||||
|
||||
Meteor.startup(() => {
|
||||
if (! usingOAuthEncryption()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const { ServiceConfiguration } = Package['service-configuration'];
|
||||
|
||||
ServiceConfiguration.configurations.find({
|
||||
$and: [{
|
||||
secret: { $exists: true }
|
||||
}, {
|
||||
"secret.algorithm": { $exists: false }
|
||||
}]
|
||||
}).forEach(config => {
|
||||
ServiceConfiguration.configurations.update(config._id, {
|
||||
$set: {
|
||||
secret: OAuthEncryption.seal(config.secret)
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
// XXX see comment on Accounts.createUser in passwords_server about adding a
|
||||
// second "server options" argument.
|
||||
const defaultCreateUserHook = (options, user) => {
|
||||
|
||||
@@ -604,6 +604,62 @@ Tinytest.add(
|
||||
}
|
||||
);
|
||||
|
||||
|
||||
Tinytest.addAsync(
|
||||
'accounts async - Meteor.userAsync() obeys options.defaultFieldSelector',
|
||||
async test => {
|
||||
const ignoreFieldName = "bigArray";
|
||||
const customField = "customField";
|
||||
const userId = Accounts.insertUserDoc({}, { username: Random.id(), [ignoreFieldName]: [1], [customField]: 'test' });
|
||||
const stampedToken = Accounts._generateStampedLoginToken();
|
||||
Accounts._insertLoginToken(userId, stampedToken);
|
||||
const options = Accounts._options;
|
||||
|
||||
// stub Meteor.userId() so it works outside methods and returns the correct user:
|
||||
const origAccountsUserId = Accounts.userId;
|
||||
Accounts.userId = () => userId;
|
||||
|
||||
Accounts._options = {};
|
||||
|
||||
// test the field is included by default
|
||||
let user = await Meteor.userAsync();
|
||||
test.isNotUndefined(user[ignoreFieldName], 'included by default');
|
||||
|
||||
// test the field is excluded
|
||||
Accounts.config({ defaultFieldSelector: { [ignoreFieldName]: 0 } });
|
||||
user = await Meteor.userAsync();
|
||||
test.isUndefined(user[ignoreFieldName], 'excluded');
|
||||
user = await Meteor.userAsync({});
|
||||
test.isUndefined(user[ignoreFieldName], 'excluded {}');
|
||||
|
||||
// test the field can still be retrieved if required
|
||||
user = await Meteor.userAsync({ fields: { [ignoreFieldName]: 1 } });
|
||||
test.isNotUndefined(user[ignoreFieldName], 'field can be retrieved');
|
||||
test.isUndefined(user.username, 'field can be retrieved username');
|
||||
|
||||
// test a combined negative field specifier
|
||||
user = await Meteor.userAsync({ fields: { username: 0 } });
|
||||
test.isUndefined(user[ignoreFieldName], 'combined field selector');
|
||||
test.isUndefined(user.username, 'combined field selector username');
|
||||
|
||||
// test an explicit request for the full user object
|
||||
user = await Meteor.userAsync({ fields: {} });
|
||||
test.isNotUndefined(user[ignoreFieldName], 'full selector');
|
||||
test.isNotUndefined(user.username, 'full selector username');
|
||||
|
||||
Accounts._options = {};
|
||||
|
||||
// Test that a custom field gets retrieved properly
|
||||
Accounts.config({ defaultFieldSelector: { [customField]: 1 } });
|
||||
user = await Meteor.userAsync();
|
||||
test.isNotUndefined(user[customField]);
|
||||
test.isUndefined(user.username);
|
||||
test.isUndefined(user[ignoreFieldName]);
|
||||
|
||||
Accounts._options = options;
|
||||
Accounts.userId = origAccountsUserId;
|
||||
}
|
||||
);
|
||||
Tinytest.add(
|
||||
'accounts - verify onExternalLogin hook can update oauth user profiles',
|
||||
test => {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
Package.describe({
|
||||
summary: 'A user account system',
|
||||
version: '2.2.5',
|
||||
version: '2.2.6',
|
||||
});
|
||||
|
||||
Package.onUse(api => {
|
||||
@@ -15,10 +15,6 @@ Package.onUse(api => {
|
||||
api.use('reactive-var', 'client');
|
||||
api.use('url', ['client', 'server']);
|
||||
|
||||
// use unordered to work around a circular dependency
|
||||
// (service-configuration needs Accounts.connection)
|
||||
api.use('service-configuration', ['client', 'server'], { unordered: true });
|
||||
|
||||
// needed for getting the currently logged-in user and handling reconnects
|
||||
api.use('ddp', ['client', 'server']);
|
||||
|
||||
|
||||
@@ -1,3 +1,24 @@
|
||||
import { Meteor } from 'meteor/meteor';
|
||||
|
||||
// TODO get from account-base
|
||||
// config option keys
|
||||
const VALID_CONFIG_KEYS = [
|
||||
'sendVerificationEmail',
|
||||
'forbidClientAccountCreation',
|
||||
'passwordEnrollTokenExpiration',
|
||||
'passwordEnrollTokenExpirationInDays',
|
||||
'restrictCreationByEmailDomain',
|
||||
'loginExpirationInDays',
|
||||
'loginExpiration',
|
||||
'passwordResetTokenExpirationInDays',
|
||||
'passwordResetTokenExpiration',
|
||||
'ambiguousErrorMessages',
|
||||
'bcryptRounds',
|
||||
'defaultFieldSelector',
|
||||
'loginTokenExpirationHours',
|
||||
'tokenSequenceLength',
|
||||
];
|
||||
|
||||
Accounts.oauth = {};
|
||||
|
||||
const services = {};
|
||||
@@ -31,3 +52,37 @@ Accounts.oauth.unregisterService = name => {
|
||||
};
|
||||
|
||||
Accounts.oauth.serviceNames = () => Object.keys(services);
|
||||
|
||||
// loginServiceConfiguration and ConfigError are maintained for backwards compatibility
|
||||
Meteor.startup(() => {
|
||||
const { ServiceConfiguration } = Package['service-configuration'];
|
||||
Accounts.loginServiceConfiguration = ServiceConfiguration.configurations;
|
||||
Accounts.ConfigError = ServiceConfiguration.ConfigError;
|
||||
|
||||
const settings = Meteor.settings?.packages?.['accounts-base'];
|
||||
if (settings) {
|
||||
if (settings.oauthSecretKey) {
|
||||
if (!Package['oauth-encryption']) {
|
||||
throw new Error(
|
||||
'The oauth-encryption package must be loaded to set oauthSecretKey'
|
||||
);
|
||||
}
|
||||
Package['oauth-encryption'].OAuthEncryption.loadKey(
|
||||
settings.oauthSecretKey
|
||||
);
|
||||
delete settings.oauthSecretKey;
|
||||
}
|
||||
// Validate config options keys
|
||||
Object.keys(settings).forEach(key => {
|
||||
if (!VALID_CONFIG_KEYS.includes(key)) {
|
||||
// TODO Consider just logging a debug message instead to allow for additional keys in the settings here?
|
||||
throw new Meteor.Error(
|
||||
`Accounts configuration: Invalid key: ${key}`
|
||||
);
|
||||
} else {
|
||||
// set values in Accounts._options
|
||||
Accounts._options[key] = settings[key];
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import { Meteor } from 'meteor/meteor';
|
||||
|
||||
// Listen to calls to `login` with an oauth option set. This is where
|
||||
// users actually get logged in to meteor via oauth.
|
||||
Accounts.registerLoginHandler(options => {
|
||||
@@ -55,3 +57,44 @@ Accounts.registerLoginHandler(options => {
|
||||
return Accounts.updateOrCreateUserFromExternalService(result.serviceName, result.serviceData, result.options);
|
||||
}
|
||||
});
|
||||
|
||||
///
|
||||
/// OAuth Encryption Support
|
||||
///
|
||||
|
||||
const OAuthEncryption = Package["oauth-encryption"]?.OAuthEncryption;
|
||||
|
||||
const usingOAuthEncryption = () => {
|
||||
return OAuthEncryption?.keyIsLoaded();
|
||||
};
|
||||
|
||||
// Encrypt unencrypted login service secrets when oauth-encryption is
|
||||
// added.
|
||||
//
|
||||
// XXX For the oauthSecretKey to be available here at startup, the
|
||||
// developer must call Accounts.config({oauthSecretKey: ...}) at load
|
||||
// time, instead of in a Meteor.startup block, because the startup
|
||||
// block in the app code will run after this accounts-base startup
|
||||
// block. Perhaps we need a post-startup callback?
|
||||
|
||||
Meteor.startup(() => {
|
||||
if (! usingOAuthEncryption()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const { ServiceConfiguration } = Package['service-configuration'];
|
||||
|
||||
ServiceConfiguration.configurations.find({
|
||||
$and: [{
|
||||
secret: { $exists: true }
|
||||
}, {
|
||||
"secret.algorithm": { $exists: false }
|
||||
}]
|
||||
}).forEach(config => {
|
||||
ServiceConfiguration.configurations.update(config._id, {
|
||||
$set: {
|
||||
secret: OAuthEncryption.seal(config.secret)
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
Package.describe({
|
||||
summary: "Common code for OAuth-based login services",
|
||||
version: "1.4.1",
|
||||
version: "1.4.2",
|
||||
});
|
||||
|
||||
Package.onUse(api => {
|
||||
@@ -9,6 +9,11 @@ Package.onUse(api => {
|
||||
api.use(['accounts-base', 'ecmascript'], ['client', 'server']);
|
||||
// Export Accounts (etc) to packages using this one.
|
||||
api.imply('accounts-base', ['client', 'server']);
|
||||
|
||||
// use unordered to work around a circular dependency
|
||||
// (service-configuration needs Accounts.connection)
|
||||
api.use('service-configuration', ['client', 'server'], { unordered: true });
|
||||
|
||||
api.use('oauth');
|
||||
|
||||
api.addFiles('oauth_common.js');
|
||||
|
||||
@@ -5,7 +5,7 @@ Package.describe({
|
||||
// 2.2.x in the future. The version was also bumped to 2.0.0 temporarily
|
||||
// during the Meteor 1.5.1 release process, so versions 2.0.0-beta.2
|
||||
// through -beta.5 and -rc.0 have already been published.
|
||||
version: '2.3.1',
|
||||
version: '2.3.3',
|
||||
});
|
||||
|
||||
Npm.depends({
|
||||
|
||||
@@ -7,12 +7,10 @@ const reportError = (error, callback) => {
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
const internalLoginWithPassword = ({ selector, password, code, callback }) => {
|
||||
if (typeof selector === 'string')
|
||||
if (!selector.includes('@')) selector = { username: selector };
|
||||
else selector = { email: selector };
|
||||
|
||||
Accounts.callLoginMethod({
|
||||
methodArguments: [
|
||||
{
|
||||
@@ -203,7 +201,7 @@ Accounts.forgotPassword = (options, callback) => {
|
||||
// @param callback (optional) {Function(error|undefined)}
|
||||
|
||||
/**
|
||||
* @summary Reset the password for a user using a token received in email. Logs the user in afterwards.
|
||||
* @summary Reset the password for a user using a token received in email. Logs the user in afterwards if the user doesn't have 2FA enabled.
|
||||
* @locus Client
|
||||
* @param {String} token The token retrieved from the reset password URL.
|
||||
* @param {String} newPassword A new password for the user. This is __not__ sent in plain text over the wire.
|
||||
@@ -236,7 +234,7 @@ Accounts.resetPassword = (token, newPassword, callback) => {
|
||||
// @param callback (optional) {Function(error|undefined)}
|
||||
|
||||
/**
|
||||
* @summary Marks the user's email address as verified. Logs the user in afterwards.
|
||||
* @summary Marks the user's email address as verified. Logs the user in afterwards if the user doesn't have 2FA enabled.
|
||||
* @locus Client
|
||||
* @param {String} token The token retrieved from the verification URL.
|
||||
* @param {Function} [callback] Optional callback. Called with no arguments on success, or with a single `Error` argument on failure.
|
||||
|
||||
@@ -1,8 +1,5 @@
|
||||
import bcrypt from 'bcrypt'
|
||||
import {Accounts} from "meteor/accounts-base";
|
||||
|
||||
const bcryptHash = Meteor.wrapAsync(bcrypt.hash);
|
||||
const bcryptCompare = Meteor.wrapAsync(bcrypt.compare);
|
||||
import { hash as bcryptHash, compare as bcryptCompare } from 'bcrypt';
|
||||
import { Accounts } from "meteor/accounts-base";
|
||||
|
||||
// Utility for grabbing user
|
||||
const getUserById = (id, options) => Meteor.users.findOne(id, Accounts._addDefaultFieldSelector(options));
|
||||
@@ -48,9 +45,9 @@ const getPasswordString = password => {
|
||||
// SHA256 before bcrypt) or an object with properties `digest` and
|
||||
// `algorithm` (in which case we bcrypt `password.digest`).
|
||||
//
|
||||
const hashPassword = password => {
|
||||
const hashPassword = async password => {
|
||||
password = getPasswordString(password);
|
||||
return bcryptHash(password, Accounts._bcryptRounds());
|
||||
return await bcryptHash(password, Accounts._bcryptRounds());
|
||||
};
|
||||
|
||||
// Extract the number of rounds used in the specified bcrypt hash.
|
||||
@@ -74,7 +71,7 @@ const getRoundsFromBcryptHash = hash => {
|
||||
// The user parameter needs at least user._id and user.services
|
||||
Accounts._checkPasswordUserFields = {_id: 1, services: 1};
|
||||
//
|
||||
Accounts._checkPassword = (user, password) => {
|
||||
const checkPasswordAsync = async (user, password) => {
|
||||
const result = {
|
||||
userId: user._id
|
||||
};
|
||||
@@ -83,15 +80,16 @@ Accounts._checkPassword = (user, password) => {
|
||||
const hash = user.services.password.bcrypt;
|
||||
const hashRounds = getRoundsFromBcryptHash(hash);
|
||||
|
||||
if (! bcryptCompare(formattedPassword, hash)) {
|
||||
if (! await bcryptCompare(formattedPassword, hash)) {
|
||||
result.error = Accounts._handleError("Incorrect password", false);
|
||||
} else if (hash && Accounts._bcryptRounds() != hashRounds) {
|
||||
// The password checks out, but the user's bcrypt hash needs to be updated.
|
||||
Meteor.defer(() => {
|
||||
|
||||
Meteor.defer(async () => {
|
||||
Meteor.users.update({ _id: user._id }, {
|
||||
$set: {
|
||||
'services.password.bcrypt':
|
||||
bcryptHash(formattedPassword, Accounts._bcryptRounds())
|
||||
await bcryptHash(formattedPassword, Accounts._bcryptRounds())
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -99,7 +97,13 @@ Accounts._checkPassword = (user, password) => {
|
||||
|
||||
return result;
|
||||
};
|
||||
const checkPassword = Accounts._checkPassword;
|
||||
|
||||
const checkPassword = (user, password) => {
|
||||
return Promise.await(checkPasswordAsync(user, password));
|
||||
};
|
||||
|
||||
Accounts._checkPassword = checkPassword;
|
||||
Accounts._checkPasswordAsync = checkPasswordAsync;
|
||||
|
||||
///
|
||||
/// LOGIN
|
||||
@@ -163,7 +167,7 @@ const passwordValidator = Match.OneOf(
|
||||
//
|
||||
// Note that neither password option is secure without SSL.
|
||||
//
|
||||
Accounts.registerLoginHandler("password", options => {
|
||||
Accounts.registerLoginHandler("password", async options => {
|
||||
if (!options.password)
|
||||
return undefined; // don't handle
|
||||
|
||||
@@ -188,7 +192,7 @@ Accounts.registerLoginHandler("password", options => {
|
||||
Accounts._handleError("User has no password set");
|
||||
}
|
||||
|
||||
const result = checkPassword(user, options.password);
|
||||
const result = await checkPasswordAsync(user, options.password);
|
||||
// This method is added by the package accounts-2fa
|
||||
// First the login is validated, then the code situation is checked
|
||||
if (
|
||||
@@ -258,7 +262,7 @@ Accounts.setUsername = (userId, newUsername) => {
|
||||
// Let the user change their own password if they know the old
|
||||
// password. `oldPassword` and `newPassword` should be objects with keys
|
||||
// `digest` and `algorithm` (representing the SHA256 of the password).
|
||||
Meteor.methods({changePassword: function (oldPassword, newPassword) {
|
||||
Meteor.methods({changePassword: async function (oldPassword, newPassword) {
|
||||
check(oldPassword, passwordValidator);
|
||||
check(newPassword, passwordValidator);
|
||||
|
||||
@@ -278,12 +282,12 @@ Meteor.methods({changePassword: function (oldPassword, newPassword) {
|
||||
Accounts._handleError("User has no password set");
|
||||
}
|
||||
|
||||
const result = checkPassword(user, oldPassword);
|
||||
const result = await checkPasswordAsync(user, oldPassword);
|
||||
if (result.error) {
|
||||
throw result.error;
|
||||
}
|
||||
|
||||
const hashed = hashPassword(newPassword);
|
||||
const hashed = await hashPassword(newPassword);
|
||||
|
||||
// It would be better if this removed ALL existing tokens and replaced
|
||||
// the token for the current connection with a new one, but that would
|
||||
@@ -316,10 +320,10 @@ Meteor.methods({changePassword: function (oldPassword, newPassword) {
|
||||
* @param {Object} options.logout Logout all current connections with this userId (default: true)
|
||||
* @importFromPackage accounts-base
|
||||
*/
|
||||
Accounts.setPassword = (userId, newPlaintextPassword, options) => {
|
||||
check(userId, String)
|
||||
check(newPlaintextPassword, Match.Where(str => Match.test(str, String) && str.length <= Meteor.settings?.packages?.accounts?.passwordMaxLength || 256))
|
||||
check(options, Match.Maybe({ logout: Boolean }))
|
||||
Accounts.setPasswordAsync = async (userId, newPlaintextPassword, options) => {
|
||||
check(userId, String);
|
||||
check(newPlaintextPassword, Match.Where(str => Match.test(str, String) && str.length <= Meteor.settings?.packages?.accounts?.passwordMaxLength || 256));
|
||||
check(options, Match.Maybe({ logout: Boolean }));
|
||||
options = { logout: true , ...options };
|
||||
|
||||
const user = getUserById(userId, {fields: {_id: 1}});
|
||||
@@ -331,7 +335,7 @@ Accounts.setPassword = (userId, newPlaintextPassword, options) => {
|
||||
$unset: {
|
||||
'services.password.reset': 1
|
||||
},
|
||||
$set: {'services.password.bcrypt': hashPassword(newPlaintextPassword)}
|
||||
$set: {'services.password.bcrypt': await hashPassword(newPlaintextPassword)}
|
||||
};
|
||||
|
||||
if (options.logout) {
|
||||
@@ -341,6 +345,19 @@ Accounts.setPassword = (userId, newPlaintextPassword, options) => {
|
||||
Meteor.users.update({_id: user._id}, update);
|
||||
};
|
||||
|
||||
/**
|
||||
* @summary Forcibly change the password for a user.
|
||||
* @locus Server
|
||||
* @param {String} userId The id of the user to update.
|
||||
* @param {String} newPassword A new password for the user.
|
||||
* @param {Object} [options]
|
||||
* @param {Object} options.logout Logout all current connections with this userId (default: true)
|
||||
* @importFromPackage accounts-base
|
||||
*/
|
||||
Accounts.setPassword = (userId, newPlaintextPassword, options) => {
|
||||
return Promise.await(Accounts.setPasswordAsync(userId, newPlaintextPassword, options));
|
||||
};
|
||||
|
||||
|
||||
///
|
||||
/// RESETTING VIA EMAIL
|
||||
@@ -560,15 +577,15 @@ Accounts.sendEnrollmentEmail = (userId, email, extraTokenData, extraParams) => {
|
||||
|
||||
// Take token from sendResetPasswordEmail or sendEnrollmentEmail, change
|
||||
// the users password, and log them in.
|
||||
Meteor.methods({resetPassword: function (...args) {
|
||||
Meteor.methods({resetPassword: async function (...args) {
|
||||
const token = args[0];
|
||||
const newPassword = args[1];
|
||||
return Accounts._loginMethod(
|
||||
return await Accounts._loginMethod(
|
||||
this,
|
||||
"resetPassword",
|
||||
args,
|
||||
"password",
|
||||
() => {
|
||||
async () => {
|
||||
check(token, String);
|
||||
check(newPassword, passwordValidator);
|
||||
|
||||
@@ -617,7 +634,7 @@ Meteor.methods({resetPassword: function (...args) {
|
||||
error: new Meteor.Error(403, "Token has invalid email address")
|
||||
};
|
||||
|
||||
const hashed = hashPassword(newPassword);
|
||||
const hashed = await hashPassword(newPassword);
|
||||
|
||||
// NOTE: We're about to invalidate tokens on the user, who we might be
|
||||
// logged in as. Make sure to avoid logging ourselves out if this
|
||||
@@ -670,6 +687,17 @@ Meteor.methods({resetPassword: function (...args) {
|
||||
// password should invalidate existing sessions).
|
||||
Accounts._clearAllLoginTokens(user._id);
|
||||
|
||||
if (Accounts._check2faEnabled?.(user)) {
|
||||
return {
|
||||
userId: user._id,
|
||||
error: Accounts._handleError(
|
||||
'Changed password, but user not logged in because 2FA is enabled',
|
||||
false,
|
||||
'2fa-enabled'
|
||||
),
|
||||
};
|
||||
}
|
||||
|
||||
return {userId: user._id};
|
||||
}
|
||||
);
|
||||
@@ -712,9 +740,9 @@ Accounts.sendVerificationEmail = (userId, email, extraTokenData, extraParams) =>
|
||||
|
||||
// Take token from sendVerificationEmail, mark the email as verified,
|
||||
// and log them in.
|
||||
Meteor.methods({verifyEmail: function (...args) {
|
||||
Meteor.methods({verifyEmail: async function (...args) {
|
||||
const token = args[0];
|
||||
return Accounts._loginMethod(
|
||||
return await Accounts._loginMethod(
|
||||
this,
|
||||
"verifyEmail",
|
||||
args,
|
||||
@@ -761,6 +789,17 @@ Meteor.methods({verifyEmail: function (...args) {
|
||||
{$set: {'emails.$.verified': true},
|
||||
$pull: {'services.email.verificationTokens': {address: tokenRecord.address}}});
|
||||
|
||||
if (Accounts._check2faEnabled?.(user)) {
|
||||
return {
|
||||
userId: user._id,
|
||||
error: Accounts._handleError(
|
||||
'Email verified, but user not logged in because 2FA is enabled',
|
||||
false,
|
||||
'2fa-enabled'
|
||||
),
|
||||
};
|
||||
}
|
||||
|
||||
return {userId: user._id};
|
||||
}
|
||||
);
|
||||
@@ -888,7 +927,7 @@ Accounts.removeEmail = (userId, email) => {
|
||||
// does the actual user insertion.
|
||||
//
|
||||
// returns the user id
|
||||
const createUser = options => {
|
||||
const createUser = async options => {
|
||||
// Unknown keys allowed, because a onCreateUserHook can take arbitrary
|
||||
// options.
|
||||
check(options, Match.ObjectIncluding({
|
||||
@@ -903,22 +942,22 @@ const createUser = options => {
|
||||
|
||||
const user = {services: {}};
|
||||
if (password) {
|
||||
const hashed = hashPassword(password);
|
||||
const hashed = await hashPassword(password);
|
||||
user.services.password = { bcrypt: hashed };
|
||||
}
|
||||
|
||||
return Accounts._createUserCheckingDuplicates({ user, email, username, options })
|
||||
return Accounts._createUserCheckingDuplicates({ user, email, username, options });
|
||||
};
|
||||
|
||||
// method for create user. Requests come from the client.
|
||||
Meteor.methods({createUser: function (...args) {
|
||||
Meteor.methods({createUser: async function (...args) {
|
||||
const options = args[0];
|
||||
return Accounts._loginMethod(
|
||||
return await Accounts._loginMethod(
|
||||
this,
|
||||
"createUser",
|
||||
args,
|
||||
"password",
|
||||
() => {
|
||||
async () => {
|
||||
// createUser() above does more checking.
|
||||
check(options, Object);
|
||||
if (Accounts._options.forbidClientAccountCreation)
|
||||
@@ -926,7 +965,7 @@ Meteor.methods({createUser: function (...args) {
|
||||
error: new Meteor.Error(403, "Signups forbidden")
|
||||
};
|
||||
|
||||
const userId = Accounts.createUserVerifyingEmail(options);
|
||||
const userId = await Accounts.createUserVerifyingEmail(options);
|
||||
|
||||
// client gets logged in as the new user afterwards.
|
||||
return {userId: userId};
|
||||
@@ -948,10 +987,10 @@ Meteor.methods({createUser: function (...args) {
|
||||
* @param {Object} options.profile The user's profile, typically including the `name` field.
|
||||
* @importFromPackage accounts-base
|
||||
* */
|
||||
Accounts.createUserVerifyingEmail = (options) => {
|
||||
Accounts.createUserVerifyingEmail = async (options) => {
|
||||
options = { ...options };
|
||||
// Create user. result contains id and token.
|
||||
const userId = createUser(options);
|
||||
const userId = await createUser(options);
|
||||
// safety belt. createUser is supposed to throw on error. send 500 error
|
||||
// instead of sending a verification email with empty userid.
|
||||
if (! userId)
|
||||
@@ -976,14 +1015,15 @@ Accounts.createUserVerifyingEmail = (options) => {
|
||||
// Unlike the client version, this does not log you in as this user
|
||||
// after creation.
|
||||
//
|
||||
// returns userId or throws an error if it can't create
|
||||
// returns Promise<userId> or throws an error if it can't create
|
||||
//
|
||||
// XXX add another argument ("server options") that gets sent to onCreateUser,
|
||||
// which is always empty when called from the createUser method? eg, "admin:
|
||||
// true", which we want to prevent the client from setting, but which a custom
|
||||
// method calling Accounts.createUser could set?
|
||||
//
|
||||
Accounts.createUser = (options, callback) => {
|
||||
|
||||
Accounts.createUserAsync = async (options, callback) => {
|
||||
options = { ...options };
|
||||
|
||||
// XXX allow an optional callback?
|
||||
@@ -994,6 +1034,23 @@ Accounts.createUser = (options, callback) => {
|
||||
return createUser(options);
|
||||
};
|
||||
|
||||
// Create user directly on the server.
|
||||
//
|
||||
// Unlike the client version, this does not log you in as this user
|
||||
// after creation.
|
||||
//
|
||||
// returns userId or throws an error if it can't create
|
||||
//
|
||||
// XXX add another argument ("server options") that gets sent to onCreateUser,
|
||||
// which is always empty when called from the createUser method? eg, "admin:
|
||||
// true", which we want to prevent the client from setting, but which a custom
|
||||
// method calling Accounts.createUser could set?
|
||||
//
|
||||
|
||||
Accounts.createUser = (options, callback) => {
|
||||
return Promise.await(Accounts.createUserAsync(options, callback));
|
||||
};
|
||||
|
||||
///
|
||||
/// PASSWORD-SPECIFIC INDEXES ON USERS
|
||||
///
|
||||
|
||||
@@ -1747,7 +1747,7 @@ if (Meteor.isServer) (() => {
|
||||
|
||||
Tinytest.addAsync(
|
||||
'passwords - allow custom bcrypt rounds',
|
||||
(test, done) => {
|
||||
async (test, done) => {
|
||||
const getUserHashRounds = user =>
|
||||
Number(user.services.password.bcrypt.substring(4, 6));
|
||||
|
||||
@@ -1768,7 +1768,7 @@ if (Meteor.isServer) (() => {
|
||||
const defaultRounds = Accounts._bcryptRounds();
|
||||
const customRounds = 11;
|
||||
Accounts._options.bcryptRounds = customRounds;
|
||||
Accounts._checkPassword(user1, password);
|
||||
await Accounts._checkPasswordAsync(user1, password);
|
||||
Meteor.setTimeout(() => {
|
||||
user1 = Meteor.users.findOne(userId1);
|
||||
rounds = getUserHashRounds(user1);
|
||||
|
||||
746
packages/babel-compiler/.npm/package/npm-shrinkwrap.json
generated
746
packages/babel-compiler/.npm/package/npm-shrinkwrap.json
generated
File diff suppressed because it is too large
Load Diff
@@ -1,11 +1,11 @@
|
||||
Package.describe({
|
||||
name: "babel-compiler",
|
||||
summary: "Parser/transpiler for ECMAScript 2015+ syntax",
|
||||
version: '7.9.0'
|
||||
version: '7.10.1'
|
||||
});
|
||||
|
||||
Npm.depends({
|
||||
'@meteorjs/babel': '7.16.0-beta.1',
|
||||
'@meteorjs/babel': '7.17.2-beta.0',
|
||||
'json5': '2.1.1'
|
||||
});
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
Package.describe({
|
||||
name: 'ecmascript',
|
||||
version: '0.16.3',
|
||||
version: '0.16.4',
|
||||
summary: 'Compiler plugin that supports ES2015+ in all .js files',
|
||||
documentation: 'README.md',
|
||||
});
|
||||
|
||||
@@ -2,7 +2,6 @@ import { Meteor } from 'meteor/meteor';
|
||||
import { Log } from 'meteor/logging';
|
||||
import { Hook } from 'meteor/callback-hook';
|
||||
|
||||
import Future from 'fibers/future';
|
||||
import url from 'url';
|
||||
import nodemailer from 'nodemailer';
|
||||
import wellKnow from 'nodemailer/lib/well-known';
|
||||
@@ -25,7 +24,7 @@ export const EmailInternals = {
|
||||
|
||||
const MailComposer = EmailInternals.NpmModules.mailcomposer.module;
|
||||
|
||||
const makeTransport = function(mailUrlString) {
|
||||
const makeTransport = function (mailUrlString) {
|
||||
const mailUrl = new URL(mailUrlString);
|
||||
|
||||
if (mailUrl.protocol !== 'smtp:' && mailUrl.protocol !== 'smtps:') {
|
||||
@@ -60,7 +59,7 @@ const makeTransport = function(mailUrlString) {
|
||||
};
|
||||
|
||||
// More info: https://nodemailer.com/smtp/well-known/
|
||||
const knownHostsTransport = function(settings = undefined, url = undefined) {
|
||||
const knownHostsTransport = function (settings = undefined, url = undefined) {
|
||||
let service, user, password;
|
||||
|
||||
const hasSettings = settings && Object.keys(settings).length;
|
||||
@@ -110,7 +109,7 @@ const knownHostsTransport = function(settings = undefined, url = undefined) {
|
||||
};
|
||||
EmailTest.knowHostsTransport = knownHostsTransport;
|
||||
|
||||
const getTransport = function() {
|
||||
const getTransport = function () {
|
||||
const packageSettings = Meteor.settings.packages?.email || {};
|
||||
// We delay this check until the first call to Email.send, in case someone
|
||||
// set process.env.MAIL_URL in startup code. Then we store in a cache until
|
||||
@@ -138,40 +137,40 @@ const getTransport = function() {
|
||||
};
|
||||
|
||||
let nextDevModeMailId = 0;
|
||||
let output_stream = process.stdout;
|
||||
|
||||
EmailTest._getAndIncNextDevModeMailId = function () {
|
||||
return nextDevModeMailId++;
|
||||
};
|
||||
|
||||
// Testing hooks
|
||||
EmailTest.overrideOutputStream = function(stream) {
|
||||
EmailTest.resetNextDevModeMailId = function () {
|
||||
nextDevModeMailId = 0;
|
||||
output_stream = stream;
|
||||
};
|
||||
|
||||
EmailTest.restoreOutputStream = function() {
|
||||
output_stream = process.stdout;
|
||||
};
|
||||
const devModeSendAsync = function (mail, options) {
|
||||
const stream = options?.stream || process.stdout;
|
||||
return new Promise((resolve, reject) => {
|
||||
let devModeMailId = EmailTest._getAndIncNextDevModeMailId();
|
||||
|
||||
const devModeSend = function(mail) {
|
||||
let devModeMailId = nextDevModeMailId++;
|
||||
|
||||
const stream = output_stream;
|
||||
|
||||
// This approach does not prevent other writers to stdout from interleaving.
|
||||
stream.write('====== BEGIN MAIL #' + devModeMailId + ' ======\n');
|
||||
stream.write(
|
||||
'(Mail not sent; to enable sending, set the MAIL_URL ' +
|
||||
// This approach does not prevent other writers to stdout from interleaving.
|
||||
const output = ['====== BEGIN MAIL #' + devModeMailId + ' ======\n'];
|
||||
output.push(
|
||||
'(Mail not sent; to enable sending, set the MAIL_URL ' +
|
||||
'environment variable.)\n'
|
||||
);
|
||||
const readStream = new MailComposer(mail).compile().createReadStream();
|
||||
readStream.pipe(stream, { end: false });
|
||||
const future = new Future();
|
||||
readStream.on('end', function() {
|
||||
stream.write('====== END MAIL #' + devModeMailId + ' ======\n');
|
||||
future.return();
|
||||
);
|
||||
const readStream = new MailComposer(mail).compile().createReadStream();
|
||||
readStream.on('data', buffer => {
|
||||
output.push(buffer.toString());
|
||||
});
|
||||
readStream.on('end', function () {
|
||||
output.push('====== END MAIL #' + devModeMailId + ' ======\n');
|
||||
stream.write(output.join(''), () => resolve());
|
||||
});
|
||||
readStream.on('error', (err) => reject(err));
|
||||
});
|
||||
future.wait();
|
||||
};
|
||||
|
||||
const smtpSend = function(transport, mail) {
|
||||
const smtpSend = function (transport, mail) {
|
||||
transport._syncSendMail(mail);
|
||||
};
|
||||
|
||||
@@ -186,7 +185,7 @@ const sendHooks = new Hook();
|
||||
* false to skip sending.
|
||||
* @returns {{ stop: function, callback: function }}
|
||||
*/
|
||||
Email.hookSend = function(f) {
|
||||
Email.hookSend = function (f) {
|
||||
return sendHooks.register(f);
|
||||
};
|
||||
|
||||
@@ -231,25 +230,77 @@ Email.customTransport = undefined;
|
||||
* You can create a `MailComposer` object via
|
||||
* `new EmailInternals.NpmModules.mailcomposer.module`.
|
||||
*/
|
||||
Email.send = function(options) {
|
||||
if (options.mailComposer) {
|
||||
options = options.mailComposer.mail;
|
||||
Email.send = function (options) {
|
||||
if (Email.customTransport) {
|
||||
// Preserve current behavior
|
||||
const email = options.mailComposer ? options.mailComposer.mail : options;
|
||||
let send = true;
|
||||
sendHooks.forEach((hook) => {
|
||||
send = hook(email);
|
||||
return send;
|
||||
});
|
||||
if (!send) {
|
||||
return;
|
||||
}
|
||||
const packageSettings = Meteor.settings.packages?.email || {};
|
||||
Email.customTransport({ packageSettings, ...email });
|
||||
return;
|
||||
}
|
||||
// Using Fibers Promise.await
|
||||
return Promise.await(Email.sendAsync(options));
|
||||
};
|
||||
|
||||
/**
|
||||
* @summary Send an email with asyncronous method. Capture Throws an `Error` on failure to contact mail server
|
||||
* or if mail server returns an error. All fields should match
|
||||
* [RFC5322](http://tools.ietf.org/html/rfc5322) specification.
|
||||
*
|
||||
* If the `MAIL_URL` environment variable is set, actually sends the email.
|
||||
* Otherwise, prints the contents of the email to standard out.
|
||||
*
|
||||
* Note that this package is based on **nodemailer**, so make sure to refer to
|
||||
* [the documentation](http://nodemailer.com/)
|
||||
* when using the `attachments` or `mailComposer` options.
|
||||
*
|
||||
* @locus Server
|
||||
* @return {Promise}
|
||||
* @param {Object} options
|
||||
* @param {String} [options.from] "From:" address (required)
|
||||
* @param {String|String[]} options.to,cc,bcc,replyTo
|
||||
* "To:", "Cc:", "Bcc:", and "Reply-To:" addresses
|
||||
* @param {String} [options.inReplyTo] Message-ID this message is replying to
|
||||
* @param {String|String[]} [options.references] Array (or space-separated string) of Message-IDs to refer to
|
||||
* @param {String} [options.messageId] Message-ID for this message; otherwise, will be set to a random value
|
||||
* @param {String} [options.subject] "Subject:" line
|
||||
* @param {String} [options.text|html] Mail body (in plain text and/or HTML)
|
||||
* @param {String} [options.watchHtml] Mail body in HTML specific for Apple Watch
|
||||
* @param {String} [options.icalEvent] iCalendar event attachment
|
||||
* @param {Object} [options.headers] Dictionary of custom headers - e.g. `{ "header name": "header value" }`. To set an object under a header name, use `JSON.stringify` - e.g. `{ "header name": JSON.stringify({ tracking: { level: 'full' } }) }`.
|
||||
* @param {Object[]} [options.attachments] Array of attachment objects, as
|
||||
* described in the [nodemailer documentation](https://nodemailer.com/message/attachments/).
|
||||
* @param {MailComposer} [options.mailComposer] A [MailComposer](https://nodemailer.com/extras/mailcomposer/#e-mail-message-fields)
|
||||
* object representing the message to be sent. Overrides all other options.
|
||||
* You can create a `MailComposer` object via
|
||||
* `new EmailInternals.NpmModules.mailcomposer.module`.
|
||||
*/
|
||||
Email.sendAsync = async function (options) {
|
||||
|
||||
const email = options.mailComposer ? options.mailComposer.mail : options;
|
||||
|
||||
let send = true;
|
||||
sendHooks.forEach(hook => {
|
||||
send = hook(options);
|
||||
sendHooks.forEach((hook) => {
|
||||
send = hook(email);
|
||||
return send;
|
||||
});
|
||||
if (!send) return;
|
||||
|
||||
const customTransport = Email.customTransport;
|
||||
if (customTransport) {
|
||||
const packageSettings = Meteor.settings.packages?.email || {};
|
||||
customTransport({ packageSettings, ...options });
|
||||
if (!send) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (Email.customTransport) {
|
||||
const packageSettings = Meteor.settings.packages?.email || {};
|
||||
return Email.customTransport({ packageSettings, ...email });
|
||||
}
|
||||
|
||||
const mailUrlEnv = process.env.MAIL_URL;
|
||||
const mailUrlSettings = Meteor.settings.packages?.email;
|
||||
|
||||
@@ -263,8 +314,8 @@ Email.send = function(options) {
|
||||
|
||||
if (mailUrlEnv || mailUrlSettings) {
|
||||
const transport = getTransport();
|
||||
smtpSend(transport, options);
|
||||
smtpSend(transport, email);
|
||||
return;
|
||||
}
|
||||
devModeSend(options);
|
||||
return devModeSendAsync(email, options);
|
||||
};
|
||||
|
||||
21
packages/email/email_test_helpers.js
Normal file
21
packages/email/email_test_helpers.js
Normal file
@@ -0,0 +1,21 @@
|
||||
import streamBuffers from 'stream-buffers';
|
||||
|
||||
export const devWarningBanner =
|
||||
'(Mail not sent; to enable ' +
|
||||
'sending, set the MAIL_URL environment variable.)\n';
|
||||
|
||||
export const smokeEmailTest = (testFunction) => {
|
||||
// This only tests dev mode, so don't run the test if this is deployed.
|
||||
if (process.env.MAIL_URL) return;
|
||||
const stream = new streamBuffers.WritableStreamBuffer();
|
||||
EmailTest.resetNextDevModeMailId();
|
||||
testFunction(stream);
|
||||
};
|
||||
|
||||
export const canonicalize = (string) => {
|
||||
// Remove generated content for test.equal to succeed.
|
||||
return string
|
||||
.replace(/Message-ID: <[^<>]*>\r\n/, 'Message-ID: <...>\r\n')
|
||||
.replace(/Date: (?!dummy).*\r\n/, 'Date: ...\r\n')
|
||||
.replace(/(boundary="|^--)--[^\s"]+?(-Part|")/gm, '$1--...$2');
|
||||
};
|
||||
@@ -1,304 +1,85 @@
|
||||
import streamBuffers from 'stream-buffers';
|
||||
import { Email } from 'meteor/email';
|
||||
import { smokeEmailTest } from './email_test_helpers';
|
||||
import { TEST_CASES } from './email_tests_data';
|
||||
|
||||
const devWarningBanner = "(Mail not sent; to enable " +
|
||||
"sending, set the MAIL_URL environment variable.)\n";
|
||||
const CUSTOM_TRANSPORT_SETTINGS = {
|
||||
email: { service: '1on1', user: 'test', password: 'pwd' },
|
||||
};
|
||||
|
||||
function smokeEmailTest(testFunction) {
|
||||
// This only tests dev mode, so don't run the test if this is deployed.
|
||||
if (process.env.MAIL_URL) return;
|
||||
const sleep = (ms) => {
|
||||
return new Promise((resolve) => setTimeout(resolve, ms));
|
||||
};
|
||||
|
||||
try {
|
||||
const stream = new streamBuffers.WritableStreamBuffer;
|
||||
EmailTest.overrideOutputStream(stream);
|
||||
// Create dynamic sync tests
|
||||
TEST_CASES.forEach(({ title, options, testCalls }) => {
|
||||
Tinytest.add(`[Sync] ${title}`, function (test) {
|
||||
smokeEmailTest((stream) => {
|
||||
Object.entries(options).forEach(([key, option]) => {
|
||||
const testCall = testCalls[key];
|
||||
Email.send({ ...option, stream });
|
||||
testCall(test, stream);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
testFunction(stream);
|
||||
// Create dynamic async tests
|
||||
TEST_CASES.forEach(({ title, options, testCalls }) => {
|
||||
Tinytest.addAsync(`[Async] ${title}`, function (test, onComplete) {
|
||||
smokeEmailTest((stream) => {
|
||||
const allPromises = Object.entries(options).map(([key, option]) => {
|
||||
const testCall = testCalls[key];
|
||||
return Email.sendAsync({ ...option, stream }).then(() => {
|
||||
testCall(test, stream);
|
||||
});
|
||||
});
|
||||
Promise.all(allPromises).then(() => onComplete());
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
} finally {
|
||||
EmailTest.restoreOutputStream();
|
||||
// Individual sync tests
|
||||
|
||||
Tinytest.add(
|
||||
'[Sync] email - alternate API is used for sending gets data',
|
||||
function (test) {
|
||||
smokeEmailTest(function (stream) {
|
||||
Email.customTransport = (options) => {
|
||||
test.equal(options.from, 'foo@example.com');
|
||||
};
|
||||
Email.send({
|
||||
from: 'foo@example.com',
|
||||
to: 'bar@example.com',
|
||||
text: '*Cool*, man',
|
||||
html: '<i>Cool</i>, man',
|
||||
stream,
|
||||
});
|
||||
test.equal(stream.getContentsAsString('utf8'), false);
|
||||
});
|
||||
|
||||
smokeEmailTest(function (stream) {
|
||||
Meteor.settings.packages = CUSTOM_TRANSPORT_SETTINGS;
|
||||
Email.customTransport = (options) => {
|
||||
test.equal(options.from, 'foo@example.com');
|
||||
test.equal(options.packageSettings?.service, '1on1');
|
||||
};
|
||||
|
||||
Email.send({
|
||||
from: 'foo@example.com',
|
||||
to: 'bar@example.com',
|
||||
text: '*Cool*, man',
|
||||
html: '<i>Cool</i>, man',
|
||||
stream,
|
||||
});
|
||||
|
||||
test.equal(stream.getContentsAsString('utf8'), false);
|
||||
});
|
||||
Email.customTransport = undefined;
|
||||
Meteor.settings.packages = undefined;
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
function canonicalize(string) {
|
||||
// Remove generated content for test.equal to succeed.
|
||||
return string.replace(/Message-ID: <[^<>]*>\r\n/, "Message-ID: <...>\r\n")
|
||||
.replace(/Date: (?!dummy).*\r\n/, "Date: ...\r\n")
|
||||
.replace(/(boundary="|^--)--[^\s"]+?(-Part|")/mg, "$1--...$2");
|
||||
}
|
||||
|
||||
Tinytest.add("email - fully customizable", function (test) {
|
||||
smokeEmailTest(function(stream) {
|
||||
Email.send({
|
||||
from: "foo@example.com",
|
||||
to: "bar@example.com",
|
||||
cc: ["friends@example.com", "enemies@example.com"],
|
||||
subject: "This is the subject",
|
||||
text: "This is the body\nof the message\nFrom us.",
|
||||
headers: {
|
||||
'X-Meteor-Test': 'a custom header',
|
||||
'Date': 'dummy',
|
||||
},
|
||||
});
|
||||
// XXX brittle if mailcomposer changes header order, etc
|
||||
test.equal(canonicalize(stream.getContentsAsString("utf8")),
|
||||
"====== BEGIN MAIL #0 ======\n" +
|
||||
devWarningBanner +
|
||||
"Content-Type: text/plain; charset=utf-8\r\n" +
|
||||
"X-Meteor-Test: a custom header\r\n" +
|
||||
"Date: dummy\r\n" +
|
||||
"From: foo@example.com\r\n" +
|
||||
"To: bar@example.com\r\n" +
|
||||
"Cc: friends@example.com, enemies@example.com\r\n" +
|
||||
"Subject: This is the subject\r\n" +
|
||||
"Message-ID: <...>\r\n" +
|
||||
"Content-Transfer-Encoding: 7bit\r\n" +
|
||||
"MIME-Version: 1.0\r\n" +
|
||||
"\r\n" +
|
||||
"This is the body\n" +
|
||||
"of the message\n" +
|
||||
"From us.\r\n" +
|
||||
"====== END MAIL #0 ======\n");
|
||||
});
|
||||
});
|
||||
|
||||
Tinytest.add("email - undefined headers sends properly", function (test) {
|
||||
smokeEmailTest(function (stream) {
|
||||
Email.send({
|
||||
from: "foo@example.com",
|
||||
to: "bar@example.com",
|
||||
subject: "This is the subject",
|
||||
text: "This is the body\nof the message\nFrom us.",
|
||||
});
|
||||
|
||||
test.matches(canonicalize(stream.getContentsAsString("utf8")),
|
||||
/^====== BEGIN MAIL #0 ======$[\s\S]+^To: bar@example.com$/m);
|
||||
});
|
||||
});
|
||||
|
||||
Tinytest.add("email - multiple e-mails same stream", function (test) {
|
||||
smokeEmailTest(function (stream) {
|
||||
Email.send({
|
||||
from: "foo@example.com",
|
||||
to: "bar@example.com",
|
||||
subject: "This is the subject",
|
||||
text: "This is the body\nof the message\nFrom us.",
|
||||
});
|
||||
|
||||
const contents = canonicalize(stream.getContentsAsString("utf8"));
|
||||
test.matches(contents, /^====== BEGIN MAIL #0 ======$/m);
|
||||
test.matches(contents, /^From: foo@example.com$/m);
|
||||
test.matches(contents, /^To: bar@example.com$/m);
|
||||
|
||||
Email.send({
|
||||
from: "qux@example.com",
|
||||
to: "baz@example.com",
|
||||
subject: "This is important",
|
||||
text: "This is another message\nFrom Qux.",
|
||||
});
|
||||
|
||||
const contents2 = canonicalize(stream.getContentsAsString("utf8"));
|
||||
test.matches(contents2, /^====== BEGIN MAIL #1 ======$/m);
|
||||
test.matches(contents2, /^From: qux@example.com$/m);
|
||||
test.matches(contents2, /^To: baz@example.com$/m);
|
||||
|
||||
});
|
||||
});
|
||||
|
||||
Tinytest.add("email - using mail composer", function (test) {
|
||||
smokeEmailTest(function (stream) {
|
||||
// Test direct MailComposer usage.
|
||||
const mc = new EmailInternals.NpmModules.mailcomposer.module({
|
||||
from: "a@b.com",
|
||||
text: "body"
|
||||
});
|
||||
Email.send({mailComposer: mc});
|
||||
test.equal(canonicalize(stream.getContentsAsString("utf8")),
|
||||
"====== BEGIN MAIL #0 ======\n" +
|
||||
devWarningBanner +
|
||||
"Content-Type: text/plain; charset=utf-8\r\n" +
|
||||
"From: a@b.com\r\n" +
|
||||
"Message-ID: <...>\r\n" +
|
||||
"Content-Transfer-Encoding: 7bit\r\n" +
|
||||
"Date: ...\r\n" +
|
||||
"MIME-Version: 1.0\r\n" +
|
||||
"\r\n" +
|
||||
"body\r\n" +
|
||||
"====== END MAIL #0 ======\n");
|
||||
});
|
||||
});
|
||||
|
||||
Tinytest.add("email - date auto generated", function (test) {
|
||||
smokeEmailTest(function (stream) {
|
||||
// Test if date header is automatically generated, if not specified
|
||||
Email.send({
|
||||
from: "foo@example.com",
|
||||
to: "bar@example.com",
|
||||
subject: "This is the subject",
|
||||
text: "This is the body\nof the message\nFrom us.",
|
||||
headers: {
|
||||
'X-Meteor-Test': 'a custom header',
|
||||
},
|
||||
});
|
||||
|
||||
test.matches(canonicalize(stream.getContentsAsString("utf8")),
|
||||
/^Date: .+$/m);
|
||||
});
|
||||
});
|
||||
|
||||
Tinytest.add("email - long lines", function (test) {
|
||||
smokeEmailTest(function (stream) {
|
||||
// Test that long header lines get wrapped with single leading whitespace,
|
||||
// and that long body lines get wrapped with quoted-printable conventions.
|
||||
Email.send({
|
||||
from: "foo@example.com",
|
||||
to: "bar@example.com",
|
||||
subject: "This is a very very very very very very very very very very very very long subject",
|
||||
text: "This is a very very very very very very very very very very very very long text",
|
||||
});
|
||||
|
||||
test.equal(canonicalize(stream.getContentsAsString("utf8")),
|
||||
"====== BEGIN MAIL #0 ======\n" +
|
||||
devWarningBanner +
|
||||
"Content-Type: text/plain; charset=utf-8\r\n" +
|
||||
"From: foo@example.com\r\n" +
|
||||
"To: bar@example.com\r\n" +
|
||||
"Subject: This is a very very very very very very very very " +
|
||||
"very very very\r\n very long subject\r\n" +
|
||||
"Message-ID: <...>\r\n" +
|
||||
"Content-Transfer-Encoding: quoted-printable\r\n" +
|
||||
"Date: ...\r\n" +
|
||||
"MIME-Version: 1.0\r\n" +
|
||||
"\r\n" +
|
||||
"This is a very very very very very very very very very very " +
|
||||
"very very long =\r\ntext\r\n" +
|
||||
"====== END MAIL #0 ======\n");
|
||||
});
|
||||
});
|
||||
|
||||
Tinytest.add("email - unicode", function (test) {
|
||||
smokeEmailTest(function (stream) {
|
||||
// Test that unicode characters in header and body get encoded.
|
||||
Email.send({
|
||||
from: "foo@example.com",
|
||||
to: "bar@example.com",
|
||||
subject: "\u263a",
|
||||
text: "I \u2665 Meteor",
|
||||
});
|
||||
|
||||
test.equal(canonicalize(stream.getContentsAsString("utf8")),
|
||||
"====== BEGIN MAIL #0 ======\n" +
|
||||
devWarningBanner +
|
||||
"Content-Type: text/plain; charset=utf-8\r\n" +
|
||||
"From: foo@example.com\r\n" +
|
||||
"To: bar@example.com\r\n" +
|
||||
"Subject: =?UTF-8?B?4pi6?=\r\n" +
|
||||
"Message-ID: <...>\r\n" +
|
||||
"Content-Transfer-Encoding: quoted-printable\r\n" +
|
||||
"Date: ...\r\n" +
|
||||
"MIME-Version: 1.0\r\n" +
|
||||
"\r\n" +
|
||||
"I =E2=99=A5 Meteor\r\n" +
|
||||
"====== END MAIL #0 ======\n");
|
||||
});
|
||||
});
|
||||
|
||||
Tinytest.add("email - text and html", function (test) {
|
||||
smokeEmailTest(function (stream) {
|
||||
// Test including both text and HTML versions of message.
|
||||
Email.send({
|
||||
from: "foo@example.com",
|
||||
to: "bar@example.com",
|
||||
text: "*Cool*, man",
|
||||
html: "<i>Cool</i>, man",
|
||||
});
|
||||
|
||||
test.equal(canonicalize(stream.getContentsAsString("utf8")),
|
||||
"====== BEGIN MAIL #0 ======\n" +
|
||||
devWarningBanner +
|
||||
"Content-Type: multipart/alternative;\r\n" +
|
||||
' boundary="--...-Part_1"\r\n' +
|
||||
"From: foo@example.com\r\n" +
|
||||
"To: bar@example.com\r\n" +
|
||||
"Message-ID: <...>\r\n" +
|
||||
"Date: ...\r\n" +
|
||||
"MIME-Version: 1.0\r\n" +
|
||||
"\r\n" +
|
||||
"----...-Part_1\r\n" +
|
||||
"Content-Type: text/plain; charset=utf-8\r\n" +
|
||||
"Content-Transfer-Encoding: 7bit\r\n" +
|
||||
"\r\n" +
|
||||
"*Cool*, man\r\n" +
|
||||
"----...-Part_1\r\n" +
|
||||
"Content-Type: text/html; charset=utf-8\r\n" +
|
||||
"Content-Transfer-Encoding: 7bit\r\n" +
|
||||
"\r\n" +
|
||||
"<i>Cool</i>, man\r\n" +
|
||||
"----...-Part_1--\r\n" +
|
||||
"====== END MAIL #0 ======\n");
|
||||
});
|
||||
});
|
||||
|
||||
Tinytest.add("email - alternate API is used for sending gets data", function(test) {
|
||||
smokeEmailTest(function(stream) {
|
||||
Email.customTransport = (options) => {
|
||||
test.equal(options.from, 'foo@example.com');
|
||||
};
|
||||
Email.send({
|
||||
from: "foo@example.com",
|
||||
to: "bar@example.com",
|
||||
text: "*Cool*, man",
|
||||
html: "<i>Cool</i>, man",
|
||||
});
|
||||
test.equal(stream.getContentsAsString("utf8"), false);
|
||||
});
|
||||
|
||||
smokeEmailTest(function(stream) {
|
||||
Meteor.settings.packages = { email: { service: '1on1', user: 'test', password: 'pwd' } };
|
||||
Email.customTransport = (options) => {
|
||||
test.equal(options.from, 'foo@example.com');
|
||||
test.equal(options.packageSettings?.service, '1on1');
|
||||
};
|
||||
|
||||
Email.send({
|
||||
from: "foo@example.com",
|
||||
to: "bar@example.com",
|
||||
text: "*Cool*, man",
|
||||
html: "<i>Cool</i>, man",
|
||||
});
|
||||
|
||||
test.equal(stream.getContentsAsString("utf8"), false);
|
||||
});
|
||||
Email.customTransport = undefined;
|
||||
Meteor.settings.packages = undefined;
|
||||
});
|
||||
|
||||
Tinytest.add("email - URL string for known hosts", function(test) {
|
||||
const oneTransport = EmailTest.knowHostsTransport({ service: '1und1', user: 'test', password: 'pwd' });
|
||||
test.equal(oneTransport.transporter.auth.type, 'LOGIN');
|
||||
test.equal(oneTransport.transporter.auth.user, 'test');
|
||||
|
||||
const aolUrlTransport = EmailTest.knowHostsTransport(null, 'AOL://test:pwd@aol.com');
|
||||
test.equal(aolUrlTransport.transporter.auth.user, 'test');
|
||||
test.equal(aolUrlTransport.transporter.auth.type, 'LOGIN');
|
||||
|
||||
const outlookTransport = EmailTest.knowHostsTransport(null, 'Outlook365://firstname.lastname%40hotmail.com:password@hotmail.com');
|
||||
const outlookTransport2 = EmailTest.knowHostsTransport(undefined, 'Outlook365://firstname.lastname@hotmail.com:password@hotmail.com');
|
||||
test.equal(outlookTransport.transporter.auth.user, 'firstname.lastname%40hotmail.com');
|
||||
test.equal(outlookTransport.options.auth.user, 'firstname.lastname%40hotmail.com');
|
||||
test.equal(outlookTransport.transporter.options.service, 'outlook365');
|
||||
test.equal(outlookTransport2.transporter.auth.user, 'firstname.lastname%40hotmail.com');
|
||||
test.equal(outlookTransport2.transporter.options.service, 'outlook365');
|
||||
|
||||
const hotmailTransport = EmailTest.knowHostsTransport(undefined, 'Hotmail://firstname.lastname@hotmail.com:password@hotmail.com');
|
||||
console.dir(hotmailTransport);
|
||||
test.equal(hotmailTransport.transporter.options.service, 'hotmail');
|
||||
|
||||
const falseService = { service: '1on1', user: 'test', password: 'pwd' };
|
||||
const errorMsg = 'Could not recognize e-mail service. See list at https://nodemailer.com/smtp/well-known/ for services that we can configure for you.';
|
||||
test.throws(() => EmailTest.knowHostsTransport(falseService), errorMsg);
|
||||
test.throws(() => EmailTest.knowHostsTransport(null, 'smtp://bbb:bb@bb.com'), errorMsg);
|
||||
});
|
||||
|
||||
Tinytest.add("email - hooks stop the sending", function(test) {
|
||||
Tinytest.add('[Sync] email - hooks stop the sending', function (test) {
|
||||
// Register hooks
|
||||
const hook1 = Email.hookSend((options) => {
|
||||
// Test that we get options through
|
||||
@@ -313,17 +94,218 @@ Tinytest.add("email - hooks stop the sending", function(test) {
|
||||
const hook3 = Email.hookSend(() => {
|
||||
console.log('FAIL');
|
||||
});
|
||||
smokeEmailTest(function(stream) {
|
||||
smokeEmailTest(function (stream) {
|
||||
Email.send({
|
||||
from: "foo@example.com",
|
||||
to: "bar@example.com",
|
||||
text: "*Cool*, man",
|
||||
html: "<i>Cool</i>, man",
|
||||
from: 'foo@example.com',
|
||||
to: 'bar@example.com',
|
||||
text: '*Cool*, man',
|
||||
html: '<i>Cool</i>, man',
|
||||
stream,
|
||||
});
|
||||
|
||||
test.equal(stream.getContentsAsString("utf8"), false);
|
||||
test.equal(stream.getContentsAsString('utf8'), false);
|
||||
});
|
||||
hook1.stop();
|
||||
hook2.stop();
|
||||
hook3.stop();
|
||||
});
|
||||
|
||||
// Individual Async tests
|
||||
|
||||
Tinytest.addAsync(
|
||||
'[Async] email - alternate API is used for sending gets data',
|
||||
function (test, onComplete) {
|
||||
const allPromises = [];
|
||||
smokeEmailTest((stream) => {
|
||||
Email.customTransport = (options) => {
|
||||
test.equal(options.from, 'foo@example.com');
|
||||
};
|
||||
allPromises.push(
|
||||
Email.sendAsync({
|
||||
from: 'foo@example.com',
|
||||
to: 'bar@example.com',
|
||||
text: '*Cool*, man',
|
||||
html: '<i>Cool</i>, man',
|
||||
stream,
|
||||
}).then(() => {
|
||||
test.equal(stream.getContentsAsString('utf8'), false);
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
smokeEmailTest(function (stream) {
|
||||
Meteor.settings.packages = CUSTOM_TRANSPORT_SETTINGS;
|
||||
Email.customTransport = (options) => {
|
||||
test.equal(options.from, 'foo@example.com');
|
||||
test.equal(options.packageSettings?.service, '1on1');
|
||||
};
|
||||
|
||||
allPromises.push(
|
||||
Email.sendAsync({
|
||||
from: 'foo@example.com',
|
||||
to: 'bar@example.com',
|
||||
text: '*Cool*, man',
|
||||
html: '<i>Cool</i>, man',
|
||||
stream,
|
||||
}).then(() => {
|
||||
test.equal(stream.getContentsAsString('utf8'), false);
|
||||
})
|
||||
);
|
||||
});
|
||||
Promise.all(allPromises).then(() => {
|
||||
Email.customTransport = undefined;
|
||||
Meteor.settings.packages = undefined;
|
||||
onComplete();
|
||||
});
|
||||
}
|
||||
);
|
||||
|
||||
Tinytest.addAsync(
|
||||
'[Async] email - hooks stop the sending',
|
||||
function (test, onComplete) {
|
||||
// Register hooks
|
||||
const hook1 = Email.hookSend((options) => {
|
||||
// Test that we get options through
|
||||
test.equal(options.from, 'foo@example.com');
|
||||
console.log('EXECUTE');
|
||||
return true;
|
||||
});
|
||||
const hook2 = Email.hookSend(() => {
|
||||
console.log('STOP');
|
||||
return false;
|
||||
});
|
||||
const hook3 = Email.hookSend(() => {
|
||||
console.log('FAIL');
|
||||
});
|
||||
smokeEmailTest((stream) => {
|
||||
Email.sendAsync({
|
||||
from: 'foo@example.com',
|
||||
to: 'bar@example.com',
|
||||
text: '*Cool*, man',
|
||||
html: '<i>Cool</i>, man',
|
||||
stream,
|
||||
}).then(() => {
|
||||
test.equal(stream.getContentsAsString('utf8'), false);
|
||||
hook1.stop();
|
||||
hook2.stop();
|
||||
hook3.stop();
|
||||
onComplete();
|
||||
});
|
||||
});
|
||||
}
|
||||
);
|
||||
|
||||
// Another tests
|
||||
|
||||
Tinytest.add('[Sync] email - URL string for known hosts', function (test) {
|
||||
const oneTransport = EmailTest.knowHostsTransport({
|
||||
service: '1und1',
|
||||
user: 'test',
|
||||
password: 'pwd',
|
||||
});
|
||||
test.equal(oneTransport.transporter.auth.type, 'LOGIN');
|
||||
test.equal(oneTransport.transporter.auth.user, 'test');
|
||||
|
||||
const aolUrlTransport = EmailTest.knowHostsTransport(
|
||||
null,
|
||||
'AOL://test:pwd@aol.com'
|
||||
);
|
||||
test.equal(aolUrlTransport.transporter.auth.user, 'test');
|
||||
test.equal(aolUrlTransport.transporter.auth.type, 'LOGIN');
|
||||
|
||||
const outlookTransport = EmailTest.knowHostsTransport(
|
||||
null,
|
||||
'Outlook365://firstname.lastname%40hotmail.com:password@hotmail.com'
|
||||
);
|
||||
const outlookTransport2 = EmailTest.knowHostsTransport(
|
||||
undefined,
|
||||
'Outlook365://firstname.lastname@hotmail.com:password@hotmail.com'
|
||||
);
|
||||
test.equal(
|
||||
outlookTransport.transporter.auth.user,
|
||||
'firstname.lastname%40hotmail.com'
|
||||
);
|
||||
test.equal(
|
||||
outlookTransport.options.auth.user,
|
||||
'firstname.lastname%40hotmail.com'
|
||||
);
|
||||
test.equal(outlookTransport.transporter.options.service, 'outlook365');
|
||||
test.equal(
|
||||
outlookTransport2.transporter.auth.user,
|
||||
'firstname.lastname%40hotmail.com'
|
||||
);
|
||||
test.equal(outlookTransport2.transporter.options.service, 'outlook365');
|
||||
|
||||
const hotmailTransport = EmailTest.knowHostsTransport(
|
||||
undefined,
|
||||
'Hotmail://firstname.lastname@hotmail.com:password@hotmail.com'
|
||||
);
|
||||
console.dir(hotmailTransport);
|
||||
test.equal(hotmailTransport.transporter.options.service, 'hotmail');
|
||||
|
||||
const falseService = CUSTOM_TRANSPORT_SETTINGS.email;
|
||||
const errorMsg =
|
||||
'Could not recognize e-mail service. See list at https://nodemailer.com/smtp/well-known/ for services that we can configure for you.';
|
||||
test.throws(() => EmailTest.knowHostsTransport(falseService), errorMsg);
|
||||
test.throws(
|
||||
() => EmailTest.knowHostsTransport(null, 'smtp://bbb:bb@bb.com'),
|
||||
errorMsg
|
||||
);
|
||||
});
|
||||
|
||||
Tinytest.addAsync(
|
||||
'[Async] email - with custom transport exception',
|
||||
async function (test) {
|
||||
Meteor.settings.packages = CUSTOM_TRANSPORT_SETTINGS;
|
||||
Email.customTransport = (options) => {
|
||||
test.equal(options.from, 'foo@example.com');
|
||||
test.equal(options.packageSettings?.service, '1on1');
|
||||
throw new Meteor.Error('Expected error');
|
||||
};
|
||||
await Email.sendAsync({
|
||||
from: 'foo@example.com',
|
||||
to: 'bar@example.com',
|
||||
}).catch((err) => {
|
||||
test.equal(err.error, 'Expected error');
|
||||
});
|
||||
Meteor.settings.packages = undefined;
|
||||
Email.customTransport = undefined;
|
||||
}
|
||||
);
|
||||
|
||||
Tinytest.addAsync(
|
||||
'[Async] email - with custom transport long time running',
|
||||
async function (test) {
|
||||
Meteor.settings.packages = CUSTOM_TRANSPORT_SETTINGS;
|
||||
Email.customTransport = async (options) => {
|
||||
await sleep(3000);
|
||||
test.equal(options.from, 'foo@example.com');
|
||||
test.equal(options.packageSettings?.service, '1on1');
|
||||
};
|
||||
await Email.sendAsync({
|
||||
from: 'foo@example.com',
|
||||
to: 'bar@example.com',
|
||||
});
|
||||
Meteor.settings.packages = undefined;
|
||||
Email.customTransport = undefined;
|
||||
}
|
||||
);
|
||||
|
||||
Tinytest.addAsync(
|
||||
'[Sync] email - with custom transport long time running',
|
||||
function (test, onComplete) {
|
||||
Meteor.settings.packages = CUSTOM_TRANSPORT_SETTINGS;
|
||||
Email.customTransport = async (options) => {
|
||||
await sleep(3000);
|
||||
test.equal(options.from, 'foo@example.com');
|
||||
test.equal(options.packageSettings?.service, '1on1');
|
||||
Meteor.settings.packages = undefined;
|
||||
Email.customTransport = undefined;
|
||||
onComplete();
|
||||
};
|
||||
Email.send({
|
||||
from: 'foo@example.com',
|
||||
to: 'bar@example.com',
|
||||
});
|
||||
}
|
||||
);
|
||||
|
||||
254
packages/email/email_tests_data.js
Normal file
254
packages/email/email_tests_data.js
Normal file
@@ -0,0 +1,254 @@
|
||||
import { canonicalize, devWarningBanner } from './email_test_helpers';
|
||||
|
||||
export const TEST_CASES = [
|
||||
{
|
||||
title: 'email - fully customizable',
|
||||
options: {
|
||||
0: {
|
||||
from: 'foo@example.com',
|
||||
to: 'bar@example.com',
|
||||
cc: ['friends@example.com', 'enemies@example.com'],
|
||||
subject: 'This is the subject',
|
||||
text: 'This is the body\nof the message\nFrom us.',
|
||||
headers: {
|
||||
'X-Meteor-Test': 'a custom header',
|
||||
Date: 'dummy',
|
||||
},
|
||||
},
|
||||
},
|
||||
testCalls: {
|
||||
0: (test, stream) => {
|
||||
// XXX brittle if mailcomposer changes header order, etc
|
||||
test.equal(
|
||||
canonicalize(stream.getContentsAsString('utf8')),
|
||||
'====== BEGIN MAIL #0 ======\n' +
|
||||
devWarningBanner +
|
||||
'Content-Type: text/plain; charset=utf-8\r\n' +
|
||||
'X-Meteor-Test: a custom header\r\n' +
|
||||
'Date: dummy\r\n' +
|
||||
'From: foo@example.com\r\n' +
|
||||
'To: bar@example.com\r\n' +
|
||||
'Cc: friends@example.com, enemies@example.com\r\n' +
|
||||
'Subject: This is the subject\r\n' +
|
||||
'Message-ID: <...>\r\n' +
|
||||
'Content-Transfer-Encoding: 7bit\r\n' +
|
||||
'MIME-Version: 1.0\r\n' +
|
||||
'\r\n' +
|
||||
'This is the body\n' +
|
||||
'of the message\n' +
|
||||
'From us.\r\n' +
|
||||
'====== END MAIL #0 ======\n'
|
||||
);
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
title: 'email - undefined headers sends properly',
|
||||
options: {
|
||||
0: {
|
||||
from: 'foo@example.com',
|
||||
to: 'bar@example.com',
|
||||
subject: 'This is the subject',
|
||||
text: 'This is the body\nof the message\nFrom us.',
|
||||
},
|
||||
},
|
||||
testCalls: {
|
||||
0: (test, stream) => {
|
||||
test.matches(
|
||||
canonicalize(stream.getContentsAsString('utf8')),
|
||||
/^====== BEGIN MAIL #0 ======$[\s\S]+^To: bar@example.com$/m
|
||||
);
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
title: 'email - multiple e-mails same stream',
|
||||
options: {
|
||||
0: {
|
||||
from: 'foo@example.com',
|
||||
to: 'bar@example.com',
|
||||
subject: 'This is the subject',
|
||||
text: 'This is the body\nof the message\nFrom us.',
|
||||
},
|
||||
1: {
|
||||
from: 'qux@example.com',
|
||||
to: 'baz@example.com',
|
||||
subject: 'This is important',
|
||||
text: 'This is another message\nFrom Qux.',
|
||||
},
|
||||
},
|
||||
|
||||
testCalls: {
|
||||
0: (test, stream) => {
|
||||
const contents = canonicalize(stream.getContentsAsString('utf8'));
|
||||
test.matches(contents, /^====== BEGIN MAIL #0 ======$/m);
|
||||
test.matches(contents, /^From: foo@example.com$/m);
|
||||
test.matches(contents, /^To: bar@example.com$/m);
|
||||
},
|
||||
1: (test, stream) => {
|
||||
const contents2 = canonicalize(stream.getContentsAsString('utf8'));
|
||||
test.matches(contents2, /^====== BEGIN MAIL #1 ======$/m);
|
||||
test.matches(contents2, /^From: qux@example.com$/m);
|
||||
test.matches(contents2, /^To: baz@example.com$/m);
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
title: 'email - using mail composer',
|
||||
options: {
|
||||
0: {
|
||||
mailComposer: new EmailInternals.NpmModules.mailcomposer.module({
|
||||
from: 'a@b.com',
|
||||
text: 'body',
|
||||
}),
|
||||
},
|
||||
},
|
||||
|
||||
testCalls: {
|
||||
0: (test, stream) => {
|
||||
test.equal(
|
||||
canonicalize(stream.getContentsAsString('utf8')),
|
||||
'====== BEGIN MAIL #0 ======\n' +
|
||||
devWarningBanner +
|
||||
'Content-Type: text/plain; charset=utf-8\r\n' +
|
||||
'From: a@b.com\r\n' +
|
||||
'Message-ID: <...>\r\n' +
|
||||
'Content-Transfer-Encoding: 7bit\r\n' +
|
||||
'Date: ...\r\n' +
|
||||
'MIME-Version: 1.0\r\n' +
|
||||
'\r\n' +
|
||||
'body\r\n' +
|
||||
'====== END MAIL #0 ======\n'
|
||||
);
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
title: 'email - date auto generated',
|
||||
options: {
|
||||
0: {
|
||||
from: 'foo@example.com',
|
||||
to: 'bar@example.com',
|
||||
subject: 'This is the subject',
|
||||
text: 'This is the body\nof the message\nFrom us.',
|
||||
headers: {
|
||||
'X-Meteor-Test': 'a custom header',
|
||||
},
|
||||
},
|
||||
},
|
||||
testCalls: {
|
||||
0: (test, stream) => {
|
||||
test.matches(
|
||||
canonicalize(stream.getContentsAsString('utf8')),
|
||||
/^Date: .+$/m
|
||||
);
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
title: 'email - long lines',
|
||||
options: {
|
||||
0: {
|
||||
from: 'foo@example.com',
|
||||
to: 'bar@example.com',
|
||||
subject:
|
||||
'This is a very very very very very very very very very very very very long subject',
|
||||
text: 'This is a very very very very very very very very very very very very long text',
|
||||
},
|
||||
},
|
||||
testCalls: {
|
||||
0: (test, stream) => {
|
||||
test.equal(
|
||||
canonicalize(stream.getContentsAsString('utf8')),
|
||||
'====== BEGIN MAIL #0 ======\n' +
|
||||
devWarningBanner +
|
||||
'Content-Type: text/plain; charset=utf-8\r\n' +
|
||||
'From: foo@example.com\r\n' +
|
||||
'To: bar@example.com\r\n' +
|
||||
'Subject: This is a very very very very very very very very ' +
|
||||
'very very very\r\n very long subject\r\n' +
|
||||
'Message-ID: <...>\r\n' +
|
||||
'Content-Transfer-Encoding: quoted-printable\r\n' +
|
||||
'Date: ...\r\n' +
|
||||
'MIME-Version: 1.0\r\n' +
|
||||
'\r\n' +
|
||||
'This is a very very very very very very very very very very ' +
|
||||
'very very long =\r\ntext\r\n' +
|
||||
'====== END MAIL #0 ======\n'
|
||||
);
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
title: 'email - unicode',
|
||||
options: {
|
||||
0: {
|
||||
from: 'foo@example.com',
|
||||
to: 'bar@example.com',
|
||||
subject: '\u263a',
|
||||
text: 'I \u2665 Meteor',
|
||||
},
|
||||
},
|
||||
testCalls: {
|
||||
0: (test, stream) => {
|
||||
test.equal(
|
||||
canonicalize(stream.getContentsAsString('utf8')),
|
||||
'====== BEGIN MAIL #0 ======\n' +
|
||||
devWarningBanner +
|
||||
'Content-Type: text/plain; charset=utf-8\r\n' +
|
||||
'From: foo@example.com\r\n' +
|
||||
'To: bar@example.com\r\n' +
|
||||
'Subject: =?UTF-8?B?4pi6?=\r\n' +
|
||||
'Message-ID: <...>\r\n' +
|
||||
'Content-Transfer-Encoding: quoted-printable\r\n' +
|
||||
'Date: ...\r\n' +
|
||||
'MIME-Version: 1.0\r\n' +
|
||||
'\r\n' +
|
||||
'I =E2=99=A5 Meteor\r\n' +
|
||||
'====== END MAIL #0 ======\n'
|
||||
);
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
title: 'email - text and html',
|
||||
options: {
|
||||
0: {
|
||||
from: 'foo@example.com',
|
||||
to: 'bar@example.com',
|
||||
text: '*Cool*, man',
|
||||
html: '<i>Cool</i>, man',
|
||||
},
|
||||
},
|
||||
testCalls: {
|
||||
0: (test, stream) => {
|
||||
test.equal(
|
||||
canonicalize(stream.getContentsAsString('utf8')),
|
||||
'====== BEGIN MAIL #0 ======\n' +
|
||||
devWarningBanner +
|
||||
'Content-Type: multipart/alternative;\r\n' +
|
||||
' boundary="--...-Part_1"\r\n' +
|
||||
'From: foo@example.com\r\n' +
|
||||
'To: bar@example.com\r\n' +
|
||||
'Message-ID: <...>\r\n' +
|
||||
'Date: ...\r\n' +
|
||||
'MIME-Version: 1.0\r\n' +
|
||||
'\r\n' +
|
||||
'----...-Part_1\r\n' +
|
||||
'Content-Type: text/plain; charset=utf-8\r\n' +
|
||||
'Content-Transfer-Encoding: 7bit\r\n' +
|
||||
'\r\n' +
|
||||
'*Cool*, man\r\n' +
|
||||
'----...-Part_1\r\n' +
|
||||
'Content-Type: text/html; charset=utf-8\r\n' +
|
||||
'Content-Transfer-Encoding: 7bit\r\n' +
|
||||
'\r\n' +
|
||||
'<i>Cool</i>, man\r\n' +
|
||||
'----...-Part_1--\r\n' +
|
||||
'====== END MAIL #0 ======\n'
|
||||
);
|
||||
},
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
Package.describe({
|
||||
summary: 'Send email messages',
|
||||
version: '2.2.2',
|
||||
version: '2.2.3',
|
||||
});
|
||||
|
||||
Npm.depends({
|
||||
|
||||
@@ -4,13 +4,13 @@ import { Accounts } from 'meteor/accounts-base';
|
||||
|
||||
const API_VERSION = Meteor.settings?.public?.packages?.['facebook-oauth']?.apiVersion || '13.0';
|
||||
|
||||
Facebook.handleAuthFromAccessToken = (accessToken, expiresAt) => {
|
||||
Facebook.handleAuthFromAccessToken = async (accessToken, expiresAt) => {
|
||||
// include basic fields from facebook
|
||||
// https://developers.facebook.com/docs/facebook-login/permissions/
|
||||
const whitelisted = ['id', 'email', 'name', 'first_name', 'last_name',
|
||||
'middle_name', 'name_format', 'picture', 'short_name'];
|
||||
|
||||
const identity = getIdentity(accessToken, whitelisted);
|
||||
const identity = await getIdentity(accessToken, whitelisted);
|
||||
|
||||
const fields = {};
|
||||
whitelisted.forEach(field => fields[field] = identity[field]);
|
||||
@@ -34,8 +34,8 @@ Accounts.registerLoginHandler(request => {
|
||||
return Accounts.updateOrCreateUserFromExternalService('facebook', facebookData.serviceData, facebookData.options);
|
||||
});
|
||||
|
||||
OAuth.registerService('facebook', 2, null, query => {
|
||||
const response = getTokenResponse(query);
|
||||
OAuth.registerService('facebook', 2, null, async query => {
|
||||
const response = await getTokenResponse(query);
|
||||
const { accessToken } = response;
|
||||
const { expiresIn } = response;
|
||||
|
||||
@@ -52,7 +52,7 @@ function getAbsoluteUrlOptions(query) {
|
||||
const redirectUrl = new URL(state.redirectUrl);
|
||||
return {
|
||||
rootUrl: redirectUrl.origin,
|
||||
}
|
||||
};
|
||||
} catch (e) {
|
||||
console.error(
|
||||
`Failed to complete OAuth handshake with Facebook because it was not able to obtain the redirect url from the state and you are using overrideRootUrlFromStateRedirectUrl.`, e
|
||||
@@ -61,73 +61,86 @@ function getAbsoluteUrlOptions(query) {
|
||||
}
|
||||
}
|
||||
|
||||
// returns an object containing:
|
||||
// - accessToken
|
||||
// - expiresIn: lifetime of token in seconds
|
||||
const getTokenResponse = query => {
|
||||
const config = ServiceConfiguration.configurations.findOne({service: 'facebook'});
|
||||
if (!config)
|
||||
throw new ServiceConfiguration.ConfigError();
|
||||
/**
|
||||
* @typedef {Object} UserAccessToken
|
||||
* @property {string} accessToken - User access Token
|
||||
* @property {number} expiresIn - lifetime of token in seconds
|
||||
*/
|
||||
/**
|
||||
* @async
|
||||
* @function getTokenResponse
|
||||
* @param {Object} query - An object with the code.
|
||||
* @returns {Promise<UserAccessToken>} - Promise with an Object containing the accessToken and expiresIn (lifetime of token in seconds)
|
||||
*/
|
||||
const getTokenResponse = async (query) => {
|
||||
const config = ServiceConfiguration.configurations.findOne({
|
||||
service: 'facebook',
|
||||
});
|
||||
if (!config) throw new ServiceConfiguration.ConfigError();
|
||||
|
||||
let responseContent;
|
||||
try {
|
||||
const absoluteUrlOptions = getAbsoluteUrlOptions(query);
|
||||
const redirectUri = OAuth._redirectUri('facebook', config, undefined, absoluteUrlOptions);
|
||||
|
||||
const absoluteUrlOptions = getAbsoluteUrlOptions(query);
|
||||
const redirectUri = OAuth._redirectUri('facebook', config, undefined, absoluteUrlOptions);
|
||||
// Request an access token
|
||||
responseContent = HTTP.get(
|
||||
`https://graph.facebook.com/v${API_VERSION}/oauth/access_token`, {
|
||||
params: {
|
||||
client_id: config.appId,
|
||||
redirect_uri: redirectUri,
|
||||
client_secret: OAuth.openSecret(config.secret),
|
||||
code: query.code
|
||||
}
|
||||
}).data;
|
||||
} catch (err) {
|
||||
throw Object.assign(
|
||||
new Error(`Failed to complete OAuth handshake with Facebook. ${err.message}`),
|
||||
{ response: err.response },
|
||||
);
|
||||
}
|
||||
|
||||
const fbAccessToken = responseContent.access_token;
|
||||
const fbExpires = responseContent.expires_in;
|
||||
|
||||
if (!fbAccessToken) {
|
||||
throw new Error("Failed to complete OAuth handshake with facebook " +
|
||||
`-- can't find access token in HTTP response. ${responseContent}`);
|
||||
}
|
||||
return {
|
||||
accessToken: fbAccessToken,
|
||||
expiresIn: fbExpires
|
||||
};
|
||||
return OAuth._fetch(
|
||||
`https://graph.facebook.com/v${API_VERSION}/oauth/access_token`,
|
||||
'GET',
|
||||
{
|
||||
queryParams: {
|
||||
client_id: config.appId,
|
||||
redirect_uri: redirectUri,
|
||||
client_secret: OAuth.openSecret(config.secret),
|
||||
code: query.code,
|
||||
},
|
||||
}
|
||||
)
|
||||
.then((res) => res.json())
|
||||
.then(data => {
|
||||
const fbAccessToken = data.access_token;
|
||||
const fbExpires = data.expires_in;
|
||||
if (!fbAccessToken) {
|
||||
throw new Error("Failed to complete OAuth handshake with facebook " +
|
||||
`-- can't find access token in HTTP response. ${data}`);
|
||||
}
|
||||
return {
|
||||
accessToken: fbAccessToken,
|
||||
expiresIn: fbExpires
|
||||
};
|
||||
})
|
||||
.catch((err) => {
|
||||
throw Object.assign(
|
||||
new Error(
|
||||
`Failed to complete OAuth handshake with Facebook. ${err.message}`
|
||||
),
|
||||
{ response: err.response }
|
||||
);
|
||||
});
|
||||
};
|
||||
|
||||
const getIdentity = (accessToken, fields) => {
|
||||
const config = ServiceConfiguration.configurations.findOne({service: 'facebook'});
|
||||
if (!config)
|
||||
throw new ServiceConfiguration.ConfigError();
|
||||
const getIdentity = async (accessToken, fields) => {
|
||||
const config = ServiceConfiguration.configurations.findOne({
|
||||
service: 'facebook',
|
||||
});
|
||||
if (!config) throw new ServiceConfiguration.ConfigError();
|
||||
|
||||
// Generate app secret proof that is a sha256 hash of the app access token, with the app secret as the key
|
||||
// https://developers.facebook.com/docs/graph-api/securing-requests#appsecret_proof
|
||||
const hmac = crypto.createHmac('sha256', OAuth.openSecret(config.secret));
|
||||
hmac.update(accessToken);
|
||||
|
||||
try {
|
||||
return HTTP.get(`https://graph.facebook.com/v${API_VERSION}/me`, {
|
||||
params: {
|
||||
access_token: accessToken,
|
||||
appsecret_proof: hmac.digest('hex'),
|
||||
fields: fields.join(",")
|
||||
}
|
||||
}).data;
|
||||
} catch (err) {
|
||||
throw Object.assign(
|
||||
new Error(`Failed to fetch identity from Facebook. ${err.message}`),
|
||||
{ response: err.response },
|
||||
);
|
||||
}
|
||||
return OAuth._fetch(`https://graph.facebook.com/v${API_VERSION}/me`, 'GET', {
|
||||
queryParams: {
|
||||
access_token: accessToken,
|
||||
appsecret_proof: hmac.digest('hex'),
|
||||
fields: fields.join(','),
|
||||
},
|
||||
})
|
||||
.then((res) => res.json())
|
||||
.catch((err) => {
|
||||
throw Object.assign(
|
||||
new Error(`Failed to fetch identity from Facebook. ${err.message}`),
|
||||
{ response: err.response }
|
||||
);
|
||||
});
|
||||
};
|
||||
|
||||
Facebook.retrieveCredential = (credentialToken, credentialSecret) =>
|
||||
|
||||
@@ -1,13 +1,12 @@
|
||||
Package.describe({
|
||||
summary: "Facebook OAuth flow",
|
||||
version: '1.11.1'
|
||||
version: '1.11.2'
|
||||
});
|
||||
|
||||
Package.onUse(api => {
|
||||
api.use('ecmascript', ['client', 'server']);
|
||||
api.use('oauth2', ['client', 'server']);
|
||||
api.use('oauth', ['client', 'server']);
|
||||
api.use('http@1.4.4 || 2.0.0', ['server']);
|
||||
api.use('random', 'client');
|
||||
api.use('service-configuration', ['client', 'server']);
|
||||
|
||||
|
||||
2
packages/fetch/fetch.d.ts
vendored
2
packages/fetch/fetch.d.ts
vendored
@@ -1,4 +1,4 @@
|
||||
export declare function fetch(): typeof globalThis.fetch;
|
||||
export declare var fetch: typeof globalThis.fetch;
|
||||
export declare var Headers: typeof globalThis.Headers;
|
||||
export declare var Request: typeof globalThis.Request;
|
||||
export declare var Response: typeof globalThis.Response;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
Package.describe({
|
||||
name: "fetch",
|
||||
version: '0.1.2',
|
||||
version: '0.1.3',
|
||||
summary: "Isomorphic modern/legacy/Node polyfill for WHATWG fetch()",
|
||||
documentation: "README.md"
|
||||
});
|
||||
|
||||
@@ -1,12 +1,9 @@
|
||||
Github = {};
|
||||
|
||||
OAuth.registerService('github', 2, null, (query) => {
|
||||
const accessTokenCall = Meteor.wrapAsync(getAccessToken);
|
||||
const accessToken = accessTokenCall(query);
|
||||
const identityCall = Meteor.wrapAsync(getIdentity);
|
||||
const identity = identityCall(accessToken);
|
||||
const emailsCall = Meteor.wrapAsync(getEmails);
|
||||
const emails = emailsCall(accessToken);
|
||||
OAuth.registerService('github', 2, null, async (query) => {
|
||||
const accessToken = await getAccessToken(query);
|
||||
const identity = await getIdentity(accessToken);
|
||||
const emails = await getEmails(accessToken);
|
||||
const primaryEmail = emails.find((email) => email.primary);
|
||||
|
||||
return {
|
||||
@@ -31,7 +28,7 @@ OAuth.registerService('github', 2, null, (query) => {
|
||||
let userAgent = 'Meteor';
|
||||
if (Meteor.release) userAgent += `/${Meteor.release}`;
|
||||
|
||||
const getAccessToken = async (query, callback) => {
|
||||
const getAccessToken = async (query) => {
|
||||
const config = ServiceConfiguration.configurations.findOne({
|
||||
service: 'github'
|
||||
});
|
||||
@@ -68,18 +65,16 @@ const getAccessToken = async (query, callback) => {
|
||||
);
|
||||
}
|
||||
if (response.error) {
|
||||
callback(response.error);
|
||||
// if the http response was a json object with an error attribute
|
||||
throw new Error(
|
||||
`Failed to complete OAuth handshake with GitHub. ${response.error}`
|
||||
);
|
||||
} else {
|
||||
callback(null, response.access_token);
|
||||
return response.access_token;
|
||||
}
|
||||
};
|
||||
|
||||
const getIdentity = async (accessToken, callback) => {
|
||||
const getIdentity = async (accessToken) => {
|
||||
try {
|
||||
const request = await fetch('https://api.github.com/user', {
|
||||
method: 'GET',
|
||||
@@ -89,11 +84,8 @@ const getIdentity = async (accessToken, callback) => {
|
||||
Authorization: `token ${accessToken}`
|
||||
} // http://developer.github.com/v3/#user-agent-required
|
||||
});
|
||||
const response = await request.json();
|
||||
callback(null, response);
|
||||
return response;
|
||||
return await request.json();
|
||||
} catch (err) {
|
||||
callback(err.message);
|
||||
throw Object.assign(
|
||||
new Error(`Failed to fetch identity from Github. ${err.message}`),
|
||||
{ response: err.response }
|
||||
@@ -101,7 +93,7 @@ const getIdentity = async (accessToken, callback) => {
|
||||
}
|
||||
};
|
||||
|
||||
const getEmails = async (accessToken, callback) => {
|
||||
const getEmails = async (accessToken) => {
|
||||
try {
|
||||
const request = await fetch('https://api.github.com/user/emails', {
|
||||
method: 'GET',
|
||||
@@ -111,11 +103,8 @@ const getEmails = async (accessToken, callback) => {
|
||||
Authorization: `token ${accessToken}`
|
||||
} // http://developer.github.com/v3/#user-agent-required
|
||||
});
|
||||
const response = await request.json();
|
||||
callback(null, response);
|
||||
return response;
|
||||
return await request.json();
|
||||
} catch (err) {
|
||||
callback(err.message, []);
|
||||
return [];
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
Package.describe({
|
||||
summary: 'GitHub OAuth flow',
|
||||
version: '1.4.0'
|
||||
version: '1.4.1'
|
||||
});
|
||||
|
||||
Package.onUse(api => {
|
||||
|
||||
@@ -5,40 +5,46 @@ import { fetch } from 'meteor/fetch';
|
||||
const hasOwn = Object.prototype.hasOwnProperty;
|
||||
|
||||
// https://developers.google.com/accounts/docs/OAuth2Login#userinfocall
|
||||
Google.whitelistedFields = ['id', 'email', 'verified_email', 'name', 'given_name',
|
||||
'family_name', 'picture', 'locale', 'timezone', 'gender'];
|
||||
Google.whitelistedFields = [
|
||||
'id',
|
||||
'email',
|
||||
'verified_email',
|
||||
'name',
|
||||
'given_name',
|
||||
'family_name',
|
||||
'picture',
|
||||
'locale',
|
||||
'timezone',
|
||||
'gender',
|
||||
];
|
||||
|
||||
const getServiceDataFromTokens = tokens => {
|
||||
const getServiceDataFromTokens = async (tokens, callback) => {
|
||||
const { accessToken, idToken } = tokens;
|
||||
const scopesCall = Meteor.wrapAsync(getScopes);
|
||||
let scopes;
|
||||
try {
|
||||
scopes = scopesCall(accessToken);
|
||||
} catch (err) {
|
||||
throw Object.assign(
|
||||
const scopes = await getScopes(accessToken).catch((err) => {
|
||||
const error = Object.assign(
|
||||
new Error(`Failed to fetch tokeninfo from Google. ${err.message}`),
|
||||
{ response: err.response }
|
||||
);
|
||||
}
|
||||
const identityCall = Meteor.wrapAsync(getIdentity);
|
||||
let identity;
|
||||
try {
|
||||
identity = identityCall(accessToken);
|
||||
} catch (err) {
|
||||
throw Object.assign(
|
||||
callback && callback(error);
|
||||
throw error;
|
||||
});
|
||||
|
||||
let identity = await getIdentity(accessToken).catch((err) => {
|
||||
const error = Object.assign(
|
||||
new Error(`Failed to fetch identity from Google. ${err.message}`),
|
||||
{ response: err.response }
|
||||
);
|
||||
}
|
||||
callback && callback(error);
|
||||
throw error;
|
||||
});
|
||||
const serviceData = {
|
||||
accessToken,
|
||||
idToken,
|
||||
scope: scopes
|
||||
scope: scopes,
|
||||
};
|
||||
|
||||
if (hasOwn.call(tokens, "expiresIn")) {
|
||||
serviceData.expiresAt =
|
||||
Date.now() + 1000 * parseInt(tokens.expiresIn, 10);
|
||||
if (hasOwn.call(tokens, 'expiresIn')) {
|
||||
serviceData.expiresAt = Date.now() + 1000 * parseInt(tokens.expiresIn, 10);
|
||||
}
|
||||
|
||||
const fields = Object.create(null);
|
||||
@@ -56,22 +62,25 @@ const getServiceDataFromTokens = tokens => {
|
||||
if (tokens.refreshToken) {
|
||||
serviceData.refreshToken = tokens.refreshToken;
|
||||
}
|
||||
|
||||
return {
|
||||
const returnValue = {
|
||||
serviceData,
|
||||
options: {
|
||||
profile: {
|
||||
name: identity.name
|
||||
}
|
||||
}
|
||||
name: identity.name,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
callback && callback(undefined, returnValue);
|
||||
|
||||
return returnValue;
|
||||
};
|
||||
|
||||
Accounts.registerLoginHandler(request => {
|
||||
Accounts.registerLoginHandler(async (request) => {
|
||||
if (request.googleSignIn !== true) {
|
||||
return;
|
||||
}
|
||||
|
||||
console.log({ request });
|
||||
const tokens = {
|
||||
accessToken: request.accessToken,
|
||||
refreshToken: request.refreshToken,
|
||||
@@ -79,29 +88,38 @@ Accounts.registerLoginHandler(request => {
|
||||
};
|
||||
|
||||
if (request.serverAuthCode) {
|
||||
Object.assign(tokens, getTokens({
|
||||
code: request.serverAuthCode
|
||||
}));
|
||||
Object.assign(
|
||||
tokens,
|
||||
await getTokens({
|
||||
code: request.serverAuthCode,
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
let result;
|
||||
try {
|
||||
result = getServiceDataFromTokens(tokens);
|
||||
result = await getServiceDataFromTokens(tokens);
|
||||
} catch (err) {
|
||||
throw Object.assign(
|
||||
new Error(`Failed to complete OAuth handshake with Google. ${err.message}`),
|
||||
new Error(
|
||||
`Failed to complete OAuth handshake with Google. ${err.message}`
|
||||
),
|
||||
{ response: err.response }
|
||||
);
|
||||
}
|
||||
|
||||
return Accounts.updateOrCreateUserFromExternalService("google", {
|
||||
id: request.userId,
|
||||
idToken: request.idToken,
|
||||
accessToken: request.accessToken,
|
||||
email: request.email,
|
||||
picture: request.imageUrl,
|
||||
...result.serviceData,
|
||||
}, result.options);
|
||||
console.log({ result });
|
||||
return Accounts.updateOrCreateUserFromExternalService(
|
||||
'google',
|
||||
{
|
||||
id: request.userId,
|
||||
idToken: request.idToken,
|
||||
accessToken: request.accessToken,
|
||||
email: request.email,
|
||||
picture: request.imageUrl,
|
||||
...result.serviceData,
|
||||
},
|
||||
result.options
|
||||
);
|
||||
});
|
||||
|
||||
// returns an object containing:
|
||||
@@ -109,45 +127,48 @@ Accounts.registerLoginHandler(request => {
|
||||
// - expiresIn: lifetime of token in seconds
|
||||
// - refreshToken, if this is the first authorization request
|
||||
const getTokens = async (query, callback) => {
|
||||
const config = ServiceConfiguration.configurations.findOne({service: 'google'});
|
||||
if (!config)
|
||||
throw new ServiceConfiguration.ConfigError();
|
||||
const config = ServiceConfiguration.configurations.findOne({
|
||||
service: 'google',
|
||||
});
|
||||
if (!config) throw new ServiceConfiguration.ConfigError();
|
||||
|
||||
const content = new URLSearchParams({
|
||||
code: query.code,
|
||||
client_id: config.clientId,
|
||||
client_secret: OAuth.openSecret(config.secret),
|
||||
redirect_uri: OAuth._redirectUri('google', config),
|
||||
grant_type: 'authorization_code'
|
||||
grant_type: 'authorization_code',
|
||||
});
|
||||
const request = await fetch('https://accounts.google.com/o/oauth2/token', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
Accept: 'application/json',
|
||||
'Content-Type': 'application/x-www-form-urlencoded',
|
||||
},
|
||||
body: content,
|
||||
});
|
||||
const request = await fetch(
|
||||
"https://accounts.google.com/o/oauth2/token", {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
Accept: 'application/json',
|
||||
'Content-Type': 'application/x-www-form-urlencoded'
|
||||
},
|
||||
body: content,
|
||||
});
|
||||
const response = await request.json();
|
||||
|
||||
if (response.error) { // if the http response was a json object with an error attribute
|
||||
callback(response.error);
|
||||
throw new Meteor.Error(`Failed to complete OAuth handshake with Google. ${response.error}`);
|
||||
if (response.error) {
|
||||
// if the http response was a json object with an error attribute
|
||||
callback && callback(response.error);
|
||||
throw new Meteor.Error(
|
||||
`Failed to complete OAuth handshake with Google. ${response.error}`
|
||||
);
|
||||
} else {
|
||||
const data = {
|
||||
accessToken: response.access_token,
|
||||
refreshToken: response.refresh_token,
|
||||
expiresIn: response.expires_in,
|
||||
idToken: response.id_token
|
||||
idToken: response.id_token,
|
||||
};
|
||||
callback(undefined, data);
|
||||
callback && callback(undefined, data);
|
||||
return data;
|
||||
}
|
||||
};
|
||||
|
||||
const getTokensCall = Meteor.wrapAsync(getTokens);
|
||||
const getServiceData = query => getServiceDataFromTokens(getTokensCall(query));
|
||||
const getServiceData = async (query) =>
|
||||
getServiceDataFromTokens(await getTokens(query));
|
||||
|
||||
OAuth.registerService('google', 2, null, getServiceData);
|
||||
|
||||
@@ -159,14 +180,15 @@ const getIdentity = async (accessToken, callback) => {
|
||||
`https://www.googleapis.com/oauth2/v1/userinfo?${content.toString()}`,
|
||||
{
|
||||
method: 'GET',
|
||||
headers: { Accept: 'application/json' }
|
||||
});
|
||||
headers: { Accept: 'application/json' },
|
||||
}
|
||||
);
|
||||
response = await request.json();
|
||||
} catch (e) {
|
||||
callback(e);
|
||||
callback && callback(e);
|
||||
throw new Meteor.Error(e.reason);
|
||||
}
|
||||
callback(undefined, response);
|
||||
callback && callback(undefined, response);
|
||||
return response;
|
||||
};
|
||||
|
||||
@@ -178,14 +200,15 @@ const getScopes = async (accessToken, callback) => {
|
||||
`https://www.googleapis.com/oauth2/v1/tokeninfo?${content.toString()}`,
|
||||
{
|
||||
method: 'GET',
|
||||
headers: { Accept: 'application/json' }
|
||||
});
|
||||
headers: { Accept: 'application/json' },
|
||||
}
|
||||
);
|
||||
response = await request.json();
|
||||
} catch (e) {
|
||||
callback(e);
|
||||
callback && callback(e);
|
||||
throw new Meteor.Error(e.reason);
|
||||
}
|
||||
callback(undefined, response.scope.split(' '));
|
||||
callback && callback(undefined, response.scope.split(' '));
|
||||
return response.scope.split(' ');
|
||||
};
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
Package.describe({
|
||||
summary: "Google OAuth flow",
|
||||
version: "1.4.2",
|
||||
version: "1.4.3",
|
||||
});
|
||||
|
||||
Cordova.depends({
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
Meetup = {};
|
||||
|
||||
OAuth.registerService('meetup', 2, null, query => {
|
||||
const response = getAccessToken(query);
|
||||
OAuth.registerService('meetup', 2, null, async query => {
|
||||
const response = await getAccessToken(query);
|
||||
const accessToken = response.access_token;
|
||||
const expiresAt = (+new Date) + (1000 * response.expires_in);
|
||||
const identity = getIdentity(accessToken);
|
||||
const identity = await getIdentity(accessToken);
|
||||
const {
|
||||
id,
|
||||
name,
|
||||
@@ -33,50 +33,63 @@ OAuth.registerService('meetup', 2, null, query => {
|
||||
};
|
||||
});
|
||||
|
||||
const getAccessToken = query => {
|
||||
const getAccessToken = async query => {
|
||||
const config = ServiceConfiguration.configurations.findOne({service: 'meetup'});
|
||||
if (!config)
|
||||
throw new ServiceConfiguration.ConfigError();
|
||||
|
||||
let response;
|
||||
try {
|
||||
response = HTTP.post(
|
||||
"https://secure.meetup.com/oauth2/access", {headers: {Accept: 'application/json'}, params: {
|
||||
code: query.code,
|
||||
client_id: config.clientId,
|
||||
client_secret: OAuth.openSecret(config.secret),
|
||||
grant_type: 'authorization_code',
|
||||
redirect_uri: OAuth._redirectUri('meetup', config),
|
||||
state: query.state
|
||||
}});
|
||||
} catch (err) {
|
||||
throw Object.assign(
|
||||
new Error(`Failed to complete OAuth handshake with Meetup. ${err.message}`),
|
||||
{ response: err.response }
|
||||
);
|
||||
}
|
||||
const body = OAuth._addValuesToQueryParams({
|
||||
code: query.code,
|
||||
client_id: config.clientId,
|
||||
client_secret: OAuth.openSecret(config.secret),
|
||||
grant_type: 'authorization_code',
|
||||
redirect_uri: OAuth._redirectUri('meetup', config),
|
||||
state: query.state
|
||||
});
|
||||
|
||||
if (response.data.error) { // if the http response was a json object with an error attribute
|
||||
throw new Error(`Failed to complete OAuth handshake with Meetup. ${response.data.error}`);
|
||||
} else {
|
||||
return response.data;
|
||||
}
|
||||
return OAuth._fetch('https://secure.meetup.com/oauth2/access', 'POST', {
|
||||
headers: {
|
||||
Accept: 'application/json',
|
||||
'Content-type': 'application/x-www-form-urlencoded',
|
||||
},
|
||||
body,
|
||||
})
|
||||
.then(data => data.json())
|
||||
.then(data => {
|
||||
if (data.error) {
|
||||
throw new Error(`Failed to complete OAuth handshake with Meetup. ${data.error.message}`);
|
||||
}
|
||||
return data;
|
||||
})
|
||||
.catch(err => {
|
||||
throw Object.assign(
|
||||
new Error(`Failed to complete OAuth handshake with Meetup. ${err.message}`),
|
||||
{ response: err.response },
|
||||
);
|
||||
});
|
||||
};
|
||||
|
||||
const getIdentity = accessToken => {
|
||||
try {
|
||||
const response = HTTP.get(
|
||||
"https://api.meetup.com/2/members",
|
||||
{params: {member_id: 'self', access_token: accessToken}});
|
||||
return response.data.results && response.data.results[0];
|
||||
} catch (err) {
|
||||
const getIdentity = async accessToken => {
|
||||
const body = OAuth._addValuesToQueryParams({
|
||||
member_id: 'self',
|
||||
access_token: accessToken
|
||||
});
|
||||
|
||||
return OAuth._fetch('https://api.meetup.com/2/members', 'POST', {
|
||||
headers: {
|
||||
Accept: 'application/json',
|
||||
'Content-type': 'application/x-www-form-urlencoded',
|
||||
},
|
||||
body,
|
||||
}).then(data => data.json())
|
||||
.then(({results = []}) => results.length && results[0])
|
||||
.catch(err => {
|
||||
throw Object.assign(
|
||||
new Error(`Failed to fetch identity from Meetup. ${err.message}`),
|
||||
{ response: err.response }
|
||||
);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
Meetup.retrieveCredential = (credentialToken, credentialSecret) =>
|
||||
OAuth.retrieveCredential(credentialToken, credentialSecret);
|
||||
|
||||
@@ -1,13 +1,12 @@
|
||||
Package.describe({
|
||||
summary: 'Meetup OAuth flow',
|
||||
version: '1.1.1'
|
||||
version: '1.1.2'
|
||||
});
|
||||
|
||||
Package.onUse(api => {
|
||||
api.use('ecmascript');
|
||||
api.use('oauth2', ['client', 'server']);
|
||||
api.use('oauth', ['client', 'server']);
|
||||
api.use('http@1.4.4 || 2.0.0', 'server');
|
||||
api.use('random', 'client');
|
||||
api.use('service-configuration', ['client', 'server']);
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
OAuth.registerService("meteor-developer", 2, null, query => {
|
||||
const response = getTokens(query);
|
||||
OAuth.registerService("meteor-developer", 2, null, async query => {
|
||||
const response = await getTokens(query);
|
||||
const { accessToken } = response;
|
||||
const identity = getIdentity(accessToken);
|
||||
const identity = await getIdentity(accessToken);
|
||||
|
||||
const serviceData = {
|
||||
accessToken: OAuth.sealSecret(accessToken),
|
||||
@@ -28,69 +28,77 @@ OAuth.registerService("meteor-developer", 2, null, query => {
|
||||
// - expiresIn: lifetime of token in seconds
|
||||
// - refreshToken, if this is the first authorization request and we got a
|
||||
// refresh token from the server
|
||||
const getTokens = query => {
|
||||
const getTokens = async (query) => {
|
||||
const config = ServiceConfiguration.configurations.findOne({
|
||||
service: 'meteor-developer'
|
||||
service: 'meteor-developer',
|
||||
});
|
||||
if (!config)
|
||||
if (!config) {
|
||||
throw new ServiceConfiguration.ConfigError();
|
||||
}
|
||||
|
||||
let response;
|
||||
try {
|
||||
response = HTTP.post(
|
||||
MeteorDeveloperAccounts._server + "/oauth2/token", {
|
||||
params: {
|
||||
grant_type: "authorization_code",
|
||||
code: query.code,
|
||||
client_id: config.clientId,
|
||||
client_secret: OAuth.openSecret(config.secret),
|
||||
redirect_uri: OAuth._redirectUri('meteor-developer', config)
|
||||
}
|
||||
const body = OAuth._addValuesToQueryParams({
|
||||
grant_type: 'authorization_code',
|
||||
code: query.code,
|
||||
client_id: config.clientId,
|
||||
client_secret: OAuth.openSecret(config.secret),
|
||||
redirect_uri: OAuth._redirectUri('meteor-developer', config),
|
||||
}).toString();
|
||||
|
||||
return OAuth._fetch(
|
||||
MeteorDeveloperAccounts._server + '/oauth2/token',
|
||||
'POST',
|
||||
{
|
||||
headers: {
|
||||
Accept: 'application/json',
|
||||
'Content-type': 'application/x-www-form-urlencoded',
|
||||
},
|
||||
body,
|
||||
}
|
||||
)
|
||||
.then((data) => data.json())
|
||||
.then((data) => {
|
||||
if (data.error) {
|
||||
throw new Error(
|
||||
'Failed to complete OAuth handshake with Meteor developer accounts. ' +
|
||||
(data ? data.error : 'No response data')
|
||||
);
|
||||
}
|
||||
);
|
||||
} catch (err) {
|
||||
throw Object.assign(
|
||||
new Error(
|
||||
"Failed to complete OAuth handshake with Meteor developer accounts. "
|
||||
+ err.message
|
||||
),
|
||||
{response: err.response}
|
||||
);
|
||||
}
|
||||
|
||||
if (! response.data || response.data.error) {
|
||||
// if the http response was a json object with an error attribute
|
||||
throw new Error(
|
||||
"Failed to complete OAuth handshake with Meteor developer accounts. " +
|
||||
(response.data ? response.data.error :
|
||||
"No response data")
|
||||
);
|
||||
} else {
|
||||
return {
|
||||
accessToken: response.data.access_token,
|
||||
refreshToken: response.data.refresh_token,
|
||||
expiresIn: response.data.expires_in
|
||||
};
|
||||
}
|
||||
return {
|
||||
accessToken: data.access_token,
|
||||
refreshToken: data.refresh_token,
|
||||
expiresIn: data.expires_in,
|
||||
};
|
||||
})
|
||||
.catch((err) => {
|
||||
throw Object.assign(
|
||||
new Error(
|
||||
`Failed to complete OAuth handshake with Meteor developer accounts. ${err.message}`
|
||||
),
|
||||
{ response: err.response }
|
||||
);
|
||||
});
|
||||
};
|
||||
|
||||
const getIdentity = accessToken => {
|
||||
try {
|
||||
return HTTP.get(
|
||||
`${MeteorDeveloperAccounts._server}/api/v1/identity`,
|
||||
{
|
||||
headers: { Authorization: `Bearer ${accessToken}`}
|
||||
}
|
||||
).data;
|
||||
} catch (err) {
|
||||
throw Object.assign(
|
||||
new Error("Failed to fetch identity from Meteor developer accounts. " +
|
||||
err.message),
|
||||
{response: err.response}
|
||||
);
|
||||
}
|
||||
const getIdentity = async (accessToken) => {
|
||||
return OAuth._fetch(
|
||||
`${MeteorDeveloperAccounts._server}/api/v1/identity`,
|
||||
'GET',
|
||||
{
|
||||
headers: { Authorization: `Bearer ${accessToken}` },
|
||||
}
|
||||
)
|
||||
.then((data) => data.json())
|
||||
.catch((err) => {
|
||||
throw Object.assign(
|
||||
new Error(
|
||||
'Failed to fetch identity from Meteor developer accounts. ' +
|
||||
err.message
|
||||
),
|
||||
{ response: err.response }
|
||||
);
|
||||
});
|
||||
};
|
||||
|
||||
MeteorDeveloperAccounts.retrieveCredential =
|
||||
(credentialToken, credentialSecret) =>
|
||||
MeteorDeveloperAccounts.retrieveCredential =
|
||||
(credentialToken, credentialSecret) =>
|
||||
OAuth.retrieveCredential(credentialToken, credentialSecret);
|
||||
|
||||
@@ -1,12 +1,11 @@
|
||||
Package.describe({
|
||||
summary: 'Meteor developer accounts OAuth flow',
|
||||
version: '1.3.1'
|
||||
version: '1.3.2'
|
||||
});
|
||||
|
||||
Package.onUse(api => {
|
||||
api.use('oauth2', ['client', 'server']);
|
||||
api.use('oauth', ['client', 'server']);
|
||||
api.use('http@1.4.4 || 2.0.0', ['server']);
|
||||
api.use(['ecmascript', 'service-configuration'], ['client', 'server']);
|
||||
api.use('random', 'client');
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
Package.describe({
|
||||
summary: 'The Meteor command-line tool',
|
||||
version: '2.8.1',
|
||||
version: '2.9.1',
|
||||
});
|
||||
|
||||
Package.includeTool();
|
||||
|
||||
22
packages/meteor/asl-helpers.js
Normal file
22
packages/meteor/asl-helpers.js
Normal file
@@ -0,0 +1,22 @@
|
||||
const getAslStore = () => (Meteor.isServer && global?.asyncLocalStorage?.getStore()) || {};
|
||||
const getValueFromAslStore = key => getAslStore()[key];
|
||||
const updateAslStore = (key, value) => getAslStore()[key] = value;
|
||||
|
||||
Meteor._isFibersEnabled = !process.env.DISABLE_FIBERS && Meteor.isServer;
|
||||
Meteor._getAslStore = getAslStore;
|
||||
Meteor._getValueFromAslStore = getValueFromAslStore;
|
||||
Meteor._updateAslStore = updateAslStore;
|
||||
|
||||
Meteor._runAsync = (fn, ctx) => {
|
||||
if (Meteor._isFibersEnabled) {
|
||||
const Fiber = Npm.require('fibers');
|
||||
|
||||
return Fiber(() => {
|
||||
fn.call(ctx);
|
||||
}).run();
|
||||
}
|
||||
|
||||
global.asyncLocalStorage.run(Meteor._getAslStore(), () => {
|
||||
fn.call(ctx);
|
||||
});
|
||||
};
|
||||
@@ -71,6 +71,38 @@ Meteor._delete = function (obj /*, arguments */) {
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Takes a function that has a callback argument as the last one and promissify it.
|
||||
* One option would be to use node utils.promisify, but it won't work on the browser.
|
||||
* @param fn
|
||||
* @param context
|
||||
* @param errorFirst - If the callback follows the errorFirst style
|
||||
* @returns {function(...[*]): Promise<unknown>}
|
||||
*/
|
||||
Meteor.promisify = function (fn, context, errorFirst = true) {
|
||||
return function (...fnArgs) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const callback = Meteor.bindEnvironment((error, result) => {
|
||||
let _error = error, _result = result;
|
||||
if (!errorFirst) {
|
||||
_error = result;
|
||||
_result = error;
|
||||
}
|
||||
|
||||
if (_error) {
|
||||
return reject(_error);
|
||||
}
|
||||
|
||||
resolve(_result);
|
||||
});
|
||||
|
||||
const filteredArgs = [...fnArgs, callback].filter(i => i !== undefined);
|
||||
return fn.apply(context || this, filteredArgs);
|
||||
});
|
||||
};
|
||||
};
|
||||
|
||||
// wrapAsync can wrap any function that takes some number of arguments that
|
||||
// can't be undefined, followed by some optional arguments, where the callback
|
||||
// is the last optional argument.
|
||||
@@ -171,5 +203,3 @@ function logErr(err) {
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Meteor._isFibersEnabled = global._isFibersEnabled;
|
||||
|
||||
18
packages/meteor/meteor.d.ts
vendored
18
packages/meteor/meteor.d.ts
vendored
@@ -147,12 +147,19 @@ export namespace Meteor {
|
||||
}): void;
|
||||
|
||||
/**
|
||||
* Invokes a method passing any number of arguments.
|
||||
* Invokes a method with a sync stub, passing any number of arguments.
|
||||
* @param name Name of method to invoke
|
||||
* @param args Optional method arguments
|
||||
*/
|
||||
function call(name: string, ...args: any[]): any;
|
||||
|
||||
/**
|
||||
* Invokes a method with an async stub, passing any number of arguments.
|
||||
* @param name Name of method to invoke
|
||||
* @param args Optional method arguments
|
||||
*/
|
||||
function callAsync(name: string, ...args: any[]): Promise<any>;
|
||||
|
||||
function apply<
|
||||
Result extends
|
||||
| EJSONable
|
||||
@@ -434,7 +441,14 @@ export namespace Meteor {
|
||||
*/
|
||||
function publish(
|
||||
name: string | null,
|
||||
func: (this: Subscription, ...args: any[]) => void,
|
||||
func: (
|
||||
this: Subscription,
|
||||
...args: any[]
|
||||
) =>
|
||||
| void
|
||||
| Mongo.Cursor<any>
|
||||
| Mongo.Cursor<any>[]
|
||||
| Promise<void | Mongo.Cursor<any> | Mongo.Cursor<any>[]>,
|
||||
options?: { is_auto: boolean }
|
||||
): void;
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
Package.describe({
|
||||
summary: "Core Meteor environment",
|
||||
version: '1.10.2'
|
||||
version: '1.10.4'
|
||||
});
|
||||
|
||||
Package.registerBuildPlugin({
|
||||
@@ -33,6 +33,7 @@ Package.onUse(function (api) {
|
||||
api.addFiles('setimmediate.js', ['client', 'server']);
|
||||
api.addFiles('timers.js', ['client', 'server']);
|
||||
api.addFiles('errors.js', ['client', 'server']);
|
||||
api.addFiles('asl-helpers.js', 'server');
|
||||
api.addFiles('fiber_helpers.js', 'server');
|
||||
api.addFiles('fiber_stubs_client.js', 'client');
|
||||
api.addFiles('startup_client.js', ['client']);
|
||||
|
||||
51
packages/minifier-css/minifier-async-tests.js
Normal file
51
packages/minifier-css/minifier-async-tests.js
Normal file
@@ -0,0 +1,51 @@
|
||||
import { CssTools } from './minifier';
|
||||
const TEST_CASES = [
|
||||
['a \t\n{ color: red } \n', 'a{color:red}', 'whitespace check'],
|
||||
[
|
||||
'a \t\n{ color: red; margin: 1; } \n',
|
||||
'a{color:red;margin:1}',
|
||||
'only last one loses semicolon',
|
||||
],
|
||||
[
|
||||
'a \t\n{ color: red;;; margin: 1;;; } \n',
|
||||
'a{color:red;margin:1}',
|
||||
'more semicolons than needed',
|
||||
],
|
||||
['a , p \t\n{ color: red; } \n', 'a,p{color:red}', 'multiple selectors'],
|
||||
['body {}', '', 'removing empty rules'],
|
||||
[
|
||||
'*.my-class { color: #fff; }',
|
||||
'.my-class{color:#fff}',
|
||||
'removing universal selector',
|
||||
],
|
||||
[
|
||||
'p > *.my-class { color: #fff; }',
|
||||
'p>.my-class{color:#fff}',
|
||||
'removing optional whitespace around ">" in selector',
|
||||
],
|
||||
[
|
||||
'p + *.my-class { color: #fff; }',
|
||||
'p+.my-class{color:#fff}',
|
||||
'removing optional whitespace around "+" in selector',
|
||||
],
|
||||
[
|
||||
'a {\n\
|
||||
font:12px \'Helvetica\',"Arial",\'Nautica\';\n\
|
||||
background:url("/some/nice/picture.png");\n}',
|
||||
'a{font:12px Helvetica,Arial,Nautica;background:url(/some/nice/picture.png)}',
|
||||
'removing quotes in font and url (if possible)',
|
||||
],
|
||||
['/* no comments */ a { color: red; }', 'a{color:red}', 'remove comments'],
|
||||
];
|
||||
|
||||
Tinytest.addAsync(
|
||||
'[Async] minifier-css - simple CSS minification',
|
||||
async (test) => {
|
||||
const promises = TEST_CASES.map(([css, expected, desc]) =>
|
||||
CssTools.minifyCssAsync(css).then((minifiedCss) => {
|
||||
test.equal(minifiedCss[0], expected, desc);
|
||||
})
|
||||
);
|
||||
return Promise.all(promises);
|
||||
}
|
||||
);
|
||||
@@ -1,6 +1,5 @@
|
||||
import path from 'path';
|
||||
import url from 'url';
|
||||
import Future from 'fibers/future';
|
||||
import postcss from 'postcss';
|
||||
import cssnano from 'cssnano';
|
||||
|
||||
@@ -65,23 +64,21 @@ const CssTools = {
|
||||
* @return {String[]} Array containing the minified CSS.
|
||||
*/
|
||||
minifyCss(cssText) {
|
||||
const f = new Future;
|
||||
postcss([
|
||||
cssnano({ safe: true }),
|
||||
]).process(cssText, {
|
||||
from: void 0,
|
||||
}).then(result => {
|
||||
f.return(result.css);
|
||||
}).catch(error => {
|
||||
f.throw(error);
|
||||
});
|
||||
const minifiedCss = f.wait();
|
||||
return Promise.await(CssTools.minifyCssAsync(cssText));
|
||||
},
|
||||
|
||||
// Since this function has always returned an array, we'll wrap the
|
||||
// minified css string in an array before returning, even though we're
|
||||
// only ever returning one minified css string in that array (maintaining
|
||||
// backwards compatibility).
|
||||
return [minifiedCss];
|
||||
/**
|
||||
* Minify the passed in CSS string.
|
||||
*
|
||||
* @param {string} cssText CSS string to minify.
|
||||
* @return {Promise<String[]>} Array containing the minified CSS.
|
||||
*/
|
||||
async minifyCssAsync(cssText) {
|
||||
return await postcss([cssnano({ safe: true })])
|
||||
.process(cssText, {
|
||||
from: void 0,
|
||||
})
|
||||
.then((result) => [result.css]);
|
||||
},
|
||||
|
||||
/**
|
||||
@@ -187,6 +184,7 @@ if (typeof Profile !== 'undefined') {
|
||||
'parseCss',
|
||||
'stringifyCss',
|
||||
'minifyCss',
|
||||
'minifyCssAsync',
|
||||
'mergeCssAsts',
|
||||
'rewriteCssUrls',
|
||||
].forEach(funcName => {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
Package.describe({
|
||||
summary: 'CSS minifier',
|
||||
version: '1.6.1'
|
||||
version: '1.6.2'
|
||||
});
|
||||
|
||||
Npm.depends({
|
||||
@@ -19,6 +19,7 @@ Package.onTest(function (api) {
|
||||
api.use('tinytest');
|
||||
api.addFiles([
|
||||
'minifier-tests.js',
|
||||
'minifier-async-tests.js',
|
||||
'urlrewriting-tests.js'
|
||||
], 'server');
|
||||
});
|
||||
|
||||
@@ -39,7 +39,11 @@ export default class Cursor {
|
||||
}
|
||||
|
||||
/**
|
||||
* @summary Returns the number of documents that match a query.
|
||||
* @deprecated in 2.9
|
||||
* @summary Returns the number of documents that match a query. This method is
|
||||
* [deprecated since MongoDB 4.0](https://www.mongodb.com/docs/v4.4/reference/command/count/);
|
||||
* see `Collection.countDocuments` and
|
||||
* `Collection.estimatedDocumentCount` for a replacement.
|
||||
* @memberOf Mongo.Cursor
|
||||
* @method count
|
||||
* @instance
|
||||
|
||||
@@ -39,6 +39,14 @@ export default class LocalCollection {
|
||||
this.paused = false;
|
||||
}
|
||||
|
||||
countDocuments(selector, options) {
|
||||
return this.find(selector ?? {}, options).countAsync();
|
||||
}
|
||||
|
||||
estimatedDocumentCount(options) {
|
||||
return this.find({}, options).countAsync();
|
||||
}
|
||||
|
||||
// options may include sort, skip, limit, reactive
|
||||
// sort may be any of these forms:
|
||||
// {a: 1, b: -1}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
Package.describe({
|
||||
summary: "Meteor's client-side datastore: a port of MongoDB to Javascript",
|
||||
version: '1.9.0'
|
||||
version: '1.9.1'
|
||||
});
|
||||
|
||||
Package.onUse(api => {
|
||||
|
||||
@@ -319,6 +319,33 @@ Object.assign(Mongo.Collection.prototype, {
|
||||
///
|
||||
/// Main collection API
|
||||
///
|
||||
/**
|
||||
* @summary Gets the number of documents matching the filter. For a fast count of the total documents in a collection see `estimatedDocumentCount`.
|
||||
* @locus Anywhere
|
||||
* @method countDocuments
|
||||
* @memberof Mongo.Collection
|
||||
* @instance
|
||||
* @param {MongoSelector} [selector] A query describing the documents to count
|
||||
* @param {Object} [options] All options are listed in [MongoDB documentation](https://mongodb.github.io/node-mongodb-native/4.11/interfaces/CountDocumentsOptions.html). Please note that not all of them are available on the client.
|
||||
* @returns {Promise<number>}
|
||||
*/
|
||||
countDocuments(...args) {
|
||||
return this._collection.countDocuments(...args);
|
||||
},
|
||||
|
||||
/**
|
||||
* @summary Gets an estimate of the count of documents in a collection using collection metadata. For an exact count of the documents in a collection see `countDocuments`.
|
||||
* @locus Anywhere
|
||||
* @method estimatedDocumentCount
|
||||
* @memberof Mongo.Collection
|
||||
* @instance
|
||||
* @param {MongoSelector} [selector] A query describing the documents to count
|
||||
* @param {Object} [options] All options are listed in [MongoDB documentation](https://mongodb.github.io/node-mongodb-native/4.11/interfaces/EstimatedDocumentCountOptions.html). Please note that not all of them are available on the client.
|
||||
* @returns {Promise<number>}
|
||||
*/
|
||||
estimatedDocumentCount(...args) {
|
||||
return this._collection.estimatedDocumentCount(...args);
|
||||
},
|
||||
|
||||
_getFindSelector(args) {
|
||||
if (args.length == 0) return {};
|
||||
|
||||
@@ -19,3 +19,14 @@ Tinytest.add('async collection - check for methods presence', function (test) {
|
||||
isFunction(cursor.mapAsync);
|
||||
isFunction(cursor[Symbol.asyncIterator]);
|
||||
});
|
||||
|
||||
['countDocuments', 'estimatedDocumentCount'].forEach(method => {
|
||||
Tinytest.addAsync(`async collection - ${method}`, async test => {
|
||||
const collection = new Mongo.Collection(method + test.id);
|
||||
for (let index = 0; index < 10; ++index) {
|
||||
test.instanceOf(collection[method](), Promise);
|
||||
test.equal(await collection[method](), index);
|
||||
collection.insert({});
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
@@ -170,8 +170,8 @@ Tinytest.add('collection - calling find with a valid readPreference',
|
||||
);
|
||||
|
||||
// Trigger the creation of _synchronousCursor
|
||||
defaultCursor.count();
|
||||
customCursor.count();
|
||||
defaultCursor.fetch();
|
||||
customCursor.fetch();
|
||||
|
||||
// defaultCursor._synchronousCursor._dbCursor.operation is not an option anymore
|
||||
// as the cursor options are now private
|
||||
@@ -384,3 +384,33 @@ Tinytest.add('collection - finding with a query with a binary field should retur
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
|
||||
Tinytest.add('collection - count should release the session',
|
||||
function(test) {
|
||||
const client = MongoInternals.defaultRemoteCollectionDriver().mongo.client;
|
||||
var collectionName = 'count' + test.id;
|
||||
var collection = new Mongo.Collection(collectionName);
|
||||
collection.insert({ _id: '1' });
|
||||
collection.insert({ _id: '2' });
|
||||
collection.insert({ _id: '3' });
|
||||
const preCount = client.s.activeSessions.size;
|
||||
|
||||
test.equal(collection.find().count(), 3);
|
||||
|
||||
// options and selector still work
|
||||
test.equal(collection.find({ _id: { $ne: '1' } }, { skip: 1 }).count(), 1);
|
||||
|
||||
// cursor reuse
|
||||
const cursor1 = collection.find({ _id: { $ne: '1' } }, { skip: 1 });
|
||||
test.equal(cursor1.count(), 1);
|
||||
test.equal(cursor1.fetch().length, 1);
|
||||
|
||||
const cursor2 = collection.find({ _id: { $ne: '1' } }, { skip: 1 });
|
||||
test.equal(cursor2.fetch().length, 1);
|
||||
test.equal(cursor2.count(), 1);
|
||||
|
||||
const postCount = client.s.activeSessions.size;
|
||||
test.equal(preCount, postCount);
|
||||
}
|
||||
);
|
||||
|
||||
@@ -826,6 +826,18 @@ MongoConnection.prototype.createIndex = function (collectionName, index,
|
||||
future.wait();
|
||||
};
|
||||
|
||||
MongoConnection.prototype.countDocuments = function (collectionName, ...args) {
|
||||
args = args.map(arg => replaceTypes(arg, replaceMeteorAtomWithMongo));
|
||||
const collection = this.rawCollection(collectionName);
|
||||
return collection.countDocuments(...args);
|
||||
};
|
||||
|
||||
MongoConnection.prototype.estimatedDocumentCount = function (collectionName, ...args) {
|
||||
args = args.map(arg => replaceTypes(arg, replaceMeteorAtomWithMongo));
|
||||
const collection = this.rawCollection(collectionName);
|
||||
return collection.estimatedDocumentCount(...args);
|
||||
};
|
||||
|
||||
MongoConnection.prototype._ensureIndex = MongoConnection.prototype.createIndex;
|
||||
|
||||
MongoConnection.prototype._dropIndex = function (collectionName, index) {
|
||||
@@ -907,11 +919,24 @@ function setupSynchronousCursor(cursor, method) {
|
||||
return cursor._synchronousCursor;
|
||||
}
|
||||
|
||||
|
||||
Cursor.prototype.count = function () {
|
||||
const collection = this._mongo.rawCollection(this._cursorDescription.collectionName);
|
||||
return Promise.await(collection.countDocuments(
|
||||
replaceTypes(this._cursorDescription.selector, replaceMeteorAtomWithMongo),
|
||||
replaceTypes(this._cursorDescription.options, replaceMeteorAtomWithMongo),
|
||||
));
|
||||
};
|
||||
|
||||
[...ASYNC_CURSOR_METHODS, Symbol.iterator, Symbol.asyncIterator].forEach(methodName => {
|
||||
Cursor.prototype[methodName] = function (...args) {
|
||||
const cursor = setupSynchronousCursor(this, methodName);
|
||||
return cursor[methodName](...args);
|
||||
};
|
||||
// count is handled specially since we don't want to create a cursor.
|
||||
// it is still included in ASYNC_CURSOR_METHODS because we still want an async version of it to exist.
|
||||
if (methodName !== 'count') {
|
||||
Cursor.prototype[methodName] = function (...args) {
|
||||
const cursor = setupSynchronousCursor(this, methodName);
|
||||
return cursor[methodName](...args);
|
||||
};
|
||||
}
|
||||
|
||||
// These methods are handled separately.
|
||||
if (methodName === Symbol.iterator || methodName === Symbol.asyncIterator) {
|
||||
|
||||
@@ -36,7 +36,7 @@ function join(prefix, key) {
|
||||
return prefix ? `${prefix}.${key}` : key;
|
||||
}
|
||||
|
||||
const arrayOperatorKeyRegex = /^(a|u\d+)$/;
|
||||
const arrayOperatorKeyRegex = /^(a|[su]\d+)$/;
|
||||
|
||||
function isArrayOperatorKey(field) {
|
||||
return arrayOperatorKeyRegex.test(field);
|
||||
@@ -96,7 +96,9 @@ function convertOplogDiff(oplogEntry, diff, prefix) {
|
||||
}
|
||||
|
||||
const positionKey = join(join(prefix, key), position.slice(1));
|
||||
if (value === null) {
|
||||
if (position[0] === 's') {
|
||||
convertOplogDiff(oplogEntry, value, positionKey);
|
||||
} else if (value === null) {
|
||||
oplogEntry.$unset ??= {};
|
||||
oplogEntry.$unset[positionKey] = true;
|
||||
} else {
|
||||
|
||||
@@ -77,6 +77,71 @@ const cases = [
|
||||
{ $v: 2, diff: { u: { params: { e: { _str: '5f953cde8ceca90030bdb86f' } } } } },
|
||||
{ $v: 2, $set: { params: { e: { _str: '5f953cde8ceca90030bdb86f' } } } },
|
||||
],
|
||||
[
|
||||
{
|
||||
$v: 2,
|
||||
diff: {
|
||||
sitems: {
|
||||
a: true,
|
||||
s0: {
|
||||
u: { id: 'm57DsX8g8L66bM5JX', name: 'Alice' },
|
||||
sbio: { u: { en: 'Just Alice' } },
|
||||
slanguages: {
|
||||
a: true,
|
||||
s0: {
|
||||
u: { englishName: 'English', key: 'en', localName: 'English' },
|
||||
},
|
||||
},
|
||||
},
|
||||
u1: {
|
||||
id: 'FJwSQHqwpenCN6RQH',
|
||||
name: 'Bob',
|
||||
title: { en: 'Fictional character', sv: '' },
|
||||
bio: { en: 'Just Bob', sv: '' },
|
||||
avatar: null,
|
||||
languages: [
|
||||
{ key: 'sv', englishName: 'Swedish', localName: 'Sverige' },
|
||||
],
|
||||
},
|
||||
u2: null
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
$v: 2,
|
||||
$set: {
|
||||
'items.0.id': 'm57DsX8g8L66bM5JX',
|
||||
'items.0.name': 'Alice',
|
||||
'items.0.bio.en': 'Just Alice',
|
||||
'items.0.languages.0.englishName': 'English',
|
||||
'items.0.languages.0.key': 'en',
|
||||
'items.0.languages.0.localName': 'English',
|
||||
'items.1': {
|
||||
id: 'FJwSQHqwpenCN6RQH',
|
||||
name: 'Bob',
|
||||
title: {
|
||||
en: 'Fictional character',
|
||||
sv: '',
|
||||
},
|
||||
bio: {
|
||||
en: 'Just Bob',
|
||||
sv: '',
|
||||
},
|
||||
avatar: null,
|
||||
languages: [
|
||||
{
|
||||
key: 'sv',
|
||||
englishName: 'Swedish',
|
||||
localName: 'Sverige',
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
$unset: {
|
||||
'items.2': true
|
||||
}
|
||||
},
|
||||
]
|
||||
];
|
||||
|
||||
Tinytest.add('oplog - v2/v1 conversion', function (test) {
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
|
||||
Package.describe({
|
||||
summary: "Adaptor for using MongoDB and Minimongo over DDP",
|
||||
version: '1.16.1'
|
||||
version: '1.16.3'
|
||||
});
|
||||
|
||||
Npm.depends({
|
||||
|
||||
@@ -4,13 +4,28 @@ MongoInternals.RemoteCollectionDriver = function (
|
||||
self.mongo = new MongoConnection(mongo_url, options);
|
||||
};
|
||||
|
||||
const REMOTE_COLLECTION_METHODS = [
|
||||
'_createCappedCollection',
|
||||
'_dropIndex',
|
||||
'_ensureIndex',
|
||||
'createIndex',
|
||||
'countDocuments',
|
||||
'dropCollection',
|
||||
'estimatedDocumentCount',
|
||||
'find',
|
||||
'findOne',
|
||||
'insert',
|
||||
'rawCollection',
|
||||
'remove',
|
||||
'update',
|
||||
'upsert',
|
||||
];
|
||||
|
||||
Object.assign(MongoInternals.RemoteCollectionDriver.prototype, {
|
||||
open: function (name) {
|
||||
var self = this;
|
||||
var ret = {};
|
||||
['find', 'findOne', 'insert', 'update', 'upsert',
|
||||
'remove', '_ensureIndex', 'createIndex', '_dropIndex', '_createCappedCollection',
|
||||
'dropCollection', 'rawCollection'].forEach(
|
||||
REMOTE_COLLECTION_METHODS.forEach(
|
||||
function (m) {
|
||||
ret[m] = _.bind(self.mongo[m], self.mongo, name);
|
||||
});
|
||||
|
||||
374
packages/npm-mongo/.npm/package/npm-shrinkwrap.json
generated
374
packages/npm-mongo/.npm/package/npm-shrinkwrap.json
generated
@@ -62,214 +62,224 @@
|
||||
}
|
||||
},
|
||||
"@aws-sdk/abort-controller": {
|
||||
"version": "3.190.0",
|
||||
"resolved": "https://registry.npmjs.org/@aws-sdk/abort-controller/-/abort-controller-3.190.0.tgz",
|
||||
"integrity": "sha512-M6qo2exTzEfHT5RuW7K090OgesUojhb2JyWiV4ulu7ngY4DWBUBMKUqac696sHRUZvGE5CDzSi0606DMboM+kA=="
|
||||
"version": "3.215.0",
|
||||
"resolved": "https://registry.npmjs.org/@aws-sdk/abort-controller/-/abort-controller-3.215.0.tgz",
|
||||
"integrity": "sha512-HTvL542nawhVqe0oC1AJchdcomEOmPivJEzYUT1LqiG3e8ikxMNa2KWSqqLPeKi2t0A/cfQy7wDUyg9+BZhDSQ=="
|
||||
},
|
||||
"@aws-sdk/client-cognito-identity": {
|
||||
"version": "3.192.0",
|
||||
"resolved": "https://registry.npmjs.org/@aws-sdk/client-cognito-identity/-/client-cognito-identity-3.192.0.tgz",
|
||||
"integrity": "sha512-nIRmiv5JY8wWGUadhG7yLx8o8aVETj5CAgO8e8UJIwwqfue/Yv9bHi2mvkUphO1pj0TeBatAtvu79neJQtsR5g=="
|
||||
"version": "3.218.0",
|
||||
"resolved": "https://registry.npmjs.org/@aws-sdk/client-cognito-identity/-/client-cognito-identity-3.218.0.tgz",
|
||||
"integrity": "sha512-IHzM9jpLqdeqj2w7YA7FrmLCQyKaun7eXtu1OJYMFbJT5XHx6B4jlQ1T/N8xivSSzDfjpJxG6/MMmjec4pI+CA=="
|
||||
},
|
||||
"@aws-sdk/client-sso": {
|
||||
"version": "3.190.0",
|
||||
"resolved": "https://registry.npmjs.org/@aws-sdk/client-sso/-/client-sso-3.190.0.tgz",
|
||||
"integrity": "sha512-joEKRjJEzgvXnEih/x2UDDCPlvXWCO3MAHmqi44yJ36Ph4YsFS299mOjPdVLuzUtpQ+cST1nRO7hXNFrulW2jQ=="
|
||||
"version": "3.218.0",
|
||||
"resolved": "https://registry.npmjs.org/@aws-sdk/client-sso/-/client-sso-3.218.0.tgz",
|
||||
"integrity": "sha512-kVMlpjaVblxgb1G8q3wD65mKxO3RzKwnjUjIBmOHpmseXzlSkAdAvYcikaDoJP+CRmys4uXk5DN8c7ZdL0OmgA=="
|
||||
},
|
||||
"@aws-sdk/client-sso-oidc": {
|
||||
"version": "3.216.0",
|
||||
"resolved": "https://registry.npmjs.org/@aws-sdk/client-sso-oidc/-/client-sso-oidc-3.216.0.tgz",
|
||||
"integrity": "sha512-O8kmM86BHwiSwyNoIe+iHXuSpUE9PBWl3re8u+/igt/w5W5VmMVz+zQr7gRUDQ1FDgLWNEdAJa0r+JFx3pZdzA=="
|
||||
},
|
||||
"@aws-sdk/client-sts": {
|
||||
"version": "3.192.0",
|
||||
"resolved": "https://registry.npmjs.org/@aws-sdk/client-sts/-/client-sts-3.192.0.tgz",
|
||||
"integrity": "sha512-iv72dmRxbZ1cN5jGn4KIVzzu11eduS2fXHbNgd7JsFd5hLBV5TvJaugQzUdXNmy2gN4HiRJr+qa9WkD5b39lsA=="
|
||||
"version": "3.218.0",
|
||||
"resolved": "https://registry.npmjs.org/@aws-sdk/client-sts/-/client-sts-3.218.0.tgz",
|
||||
"integrity": "sha512-0A81eHvryKFEPq7IeY34Opzh5b9bVhhLlf2fDy5VuZjCFf4R9vD2ceOANvFSJeMsmdlqVDq8U1mHYl0E6FRUug=="
|
||||
},
|
||||
"@aws-sdk/config-resolver": {
|
||||
"version": "3.190.0",
|
||||
"resolved": "https://registry.npmjs.org/@aws-sdk/config-resolver/-/config-resolver-3.190.0.tgz",
|
||||
"integrity": "sha512-K+VnDtjTgjpf7yHEdDB0qgGbHToF0pIL0pQMSnmk2yc8BoB3LGG/gg1T0Ki+wRlrFnDCJ6L+8zUdawY2qDsbyw=="
|
||||
"version": "3.215.0",
|
||||
"resolved": "https://registry.npmjs.org/@aws-sdk/config-resolver/-/config-resolver-3.215.0.tgz",
|
||||
"integrity": "sha512-DxX4R+YYLQOtg0qfceKBrjVD4t1mQBG1eb7IVr2QSlckFCX8ztUNymFMuaSEo3938Jyy/NpgfUDpFqPDaSKnng=="
|
||||
},
|
||||
"@aws-sdk/credential-provider-cognito-identity": {
|
||||
"version": "3.192.0",
|
||||
"resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-cognito-identity/-/credential-provider-cognito-identity-3.192.0.tgz",
|
||||
"integrity": "sha512-CWo+KyHCGyYtvjlmDIGtnwBEkdiondergZADiStbFFvie8pPI7IsdTXNVssQQ1VxKIBGGHVebgZGSklHBqthwA=="
|
||||
"version": "3.218.0",
|
||||
"resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-cognito-identity/-/credential-provider-cognito-identity-3.218.0.tgz",
|
||||
"integrity": "sha512-ndhlPBvnxUgje23TnVw0fkDgTZHh0GVapKSgeEIxmxAy3IVLN15iMs7dCV7LWvb7z1P0cYx9cwvxa0nTrVxjtg=="
|
||||
},
|
||||
"@aws-sdk/credential-provider-env": {
|
||||
"version": "3.190.0",
|
||||
"resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-env/-/credential-provider-env-3.190.0.tgz",
|
||||
"integrity": "sha512-GTY7l3SJhTmRGFpWddbdJOihSqoMN8JMo3CsCtIjk4/h3xirBi02T4GSvbrMyP7FP3Fdl4NAdT+mHJ4q2Bvzxw=="
|
||||
"version": "3.215.0",
|
||||
"resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-env/-/credential-provider-env-3.215.0.tgz",
|
||||
"integrity": "sha512-n5G7I7Pxfsn81+tNsSOzspKp9SYai78oRfImsfFY4JLTcWutv7szMgFUbtEzBfUUINHpOxLiO2Lk5yu5K1C7IQ=="
|
||||
},
|
||||
"@aws-sdk/credential-provider-imds": {
|
||||
"version": "3.190.0",
|
||||
"resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-imds/-/credential-provider-imds-3.190.0.tgz",
|
||||
"integrity": "sha512-gI5pfBqGYCKdmx8igPvq+jLzyE2kuNn9Q5u73pdM/JZxiq7GeWYpE/MqqCubHxPtPcTFgAwxCxCFoXlUTBh/2g=="
|
||||
"version": "3.215.0",
|
||||
"resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-imds/-/credential-provider-imds-3.215.0.tgz",
|
||||
"integrity": "sha512-/4FUUR6u9gkNfxB6mEwBr0kk0myIkrDcXbAocWN3fPd/t7otzxpx/JqPZXgM6kcVP7M4T/QT75l1E1RRHLWCCQ=="
|
||||
},
|
||||
"@aws-sdk/credential-provider-ini": {
|
||||
"version": "3.190.0",
|
||||
"resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.190.0.tgz",
|
||||
"integrity": "sha512-Z7NN/evXJk59hBQlfOSWDfHntwmxwryu6uclgv7ECI6SEVtKt1EKIlPuCLUYgQ4lxb9bomyO5lQAl/1WutNT5w=="
|
||||
"version": "3.218.0",
|
||||
"resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.218.0.tgz",
|
||||
"integrity": "sha512-tDDrGW+4A+PQThVJ+l9ee03CsDoD0XLpOB5dcf+dr/dCHjcQ7x/CeVFZ8eM+XUtGQnZVvuzXZGwzS8bUWEdJIg=="
|
||||
},
|
||||
"@aws-sdk/credential-provider-node": {
|
||||
"version": "3.190.0",
|
||||
"resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.190.0.tgz",
|
||||
"integrity": "sha512-ctCG5+TsIK2gVgvvFiFjinPjc5nGpSypU3nQKCaihtPh83wDN6gCx4D0p9M8+fUrlPa5y+o/Y7yHo94ATepM8w=="
|
||||
"version": "3.218.0",
|
||||
"resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.218.0.tgz",
|
||||
"integrity": "sha512-J9PB6XFA+V0mgxleuY5W6Jjh5WejV8HjMViTJQpp2JN+NWZP3bGvquUSQHRqWGRGg2fSJy6Z/J4zQ8fpPbGsdQ=="
|
||||
},
|
||||
"@aws-sdk/credential-provider-process": {
|
||||
"version": "3.190.0",
|
||||
"resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-process/-/credential-provider-process-3.190.0.tgz",
|
||||
"integrity": "sha512-sIJhICR80n5XY1kW/EFHTh5ZzBHb5X+744QCH3StcbKYI44mOZvNKfFdeRL2fQ7yLgV7npte2HJRZzQPWpZUrw=="
|
||||
"version": "3.215.0",
|
||||
"resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-process/-/credential-provider-process-3.215.0.tgz",
|
||||
"integrity": "sha512-JNvj4L5B7W8byoFdfn/8Y4scoPiwCi+Ha/fRsFCrdSC7C+snDuxM/oQj33HI8DpKY1cjuigzEnpnxiNWaA09EA=="
|
||||
},
|
||||
"@aws-sdk/credential-provider-sso": {
|
||||
"version": "3.190.0",
|
||||
"resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.190.0.tgz",
|
||||
"integrity": "sha512-uarU9vk471MHHT+GJj3KWFSmaaqLNL5n1KcMer2CCAZfjs+mStAi8+IjZuuKXB4vqVs5DxdH8cy5aLaJcBlXwQ=="
|
||||
"version": "3.218.0",
|
||||
"resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.218.0.tgz",
|
||||
"integrity": "sha512-HecWvmxD+xffmY8G4SfLRfCOgSoLFki45wOOU8ESgRM9fQp2+3CfRSyiThKZI5PTmE+xhPTRvmR61HUmQjEv8w=="
|
||||
},
|
||||
"@aws-sdk/credential-provider-web-identity": {
|
||||
"version": "3.190.0",
|
||||
"resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.190.0.tgz",
|
||||
"integrity": "sha512-nlIBeK9hGHKWC874h+ITAfPZ9Eaok+x/ydZQVKsLHiQ9PH3tuQ8AaGqhuCwBSH0hEAHZ/BiKeEx5VyWAE8/x+Q=="
|
||||
"version": "3.215.0",
|
||||
"resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.215.0.tgz",
|
||||
"integrity": "sha512-AWaDDEE3VU1HeLrXvyUrkQ6Wb3PQij5bvvrMil9L0da3b1yrcpoDanQQy7wBFBXcZIVmcmSFe5MMA/nyh2Le4g=="
|
||||
},
|
||||
"@aws-sdk/credential-providers": {
|
||||
"version": "3.192.0",
|
||||
"resolved": "https://registry.npmjs.org/@aws-sdk/credential-providers/-/credential-providers-3.192.0.tgz",
|
||||
"integrity": "sha512-iBTrEPkfOHlfgQyk7EeUCmZnhUKXsGcc/hhxBbc6Z/Xc7Y8LqRVLbEmHq9lruXraFuvs26xV9oZi1s1UMXneQA=="
|
||||
"version": "3.218.0",
|
||||
"resolved": "https://registry.npmjs.org/@aws-sdk/credential-providers/-/credential-providers-3.218.0.tgz",
|
||||
"integrity": "sha512-MWpb5k+Oq56NrHA5fYPIDX8QRYUAw4Jp8ErTELBd83kLhTgqTw025YQ05YbhIzAs84+viMeWKif0z/5kNshphw=="
|
||||
},
|
||||
"@aws-sdk/fetch-http-handler": {
|
||||
"version": "3.190.0",
|
||||
"resolved": "https://registry.npmjs.org/@aws-sdk/fetch-http-handler/-/fetch-http-handler-3.190.0.tgz",
|
||||
"integrity": "sha512-5riRpKydARXAPLesTZm6eP6QKJ4HJGQ3k0Tepi3nvxHVx3UddkRNoX0pLS3rvbajkykWPNC2qdfRGApWlwOYsA=="
|
||||
"version": "3.215.0",
|
||||
"resolved": "https://registry.npmjs.org/@aws-sdk/fetch-http-handler/-/fetch-http-handler-3.215.0.tgz",
|
||||
"integrity": "sha512-JfZyrJOE+0ik1PumsIUZd0NfgEx4sZ43VSdPCD9GRhssRWudNsSF1B5fz3xA5v+1y5oQPjXZyaWCzKtnYruiWw=="
|
||||
},
|
||||
"@aws-sdk/hash-node": {
|
||||
"version": "3.190.0",
|
||||
"resolved": "https://registry.npmjs.org/@aws-sdk/hash-node/-/hash-node-3.190.0.tgz",
|
||||
"integrity": "sha512-DNwVT3O8zc9Jk/bXiXcN0WsD98r+JJWryw9F1/ZZbuzbf6rx2qhI8ZK+nh5X6WMtYPU84luQMcF702fJt/1bzg=="
|
||||
"version": "3.215.0",
|
||||
"resolved": "https://registry.npmjs.org/@aws-sdk/hash-node/-/hash-node-3.215.0.tgz",
|
||||
"integrity": "sha512-MkSRuZvo1RCRmI0VNEmRYCGGD/DkMd9lqnLtOyglMPnSX1mhyD4/DyXmcc3rYa7PsjDRAfykGWJRiMqpoMLjiQ=="
|
||||
},
|
||||
"@aws-sdk/invalid-dependency": {
|
||||
"version": "3.190.0",
|
||||
"resolved": "https://registry.npmjs.org/@aws-sdk/invalid-dependency/-/invalid-dependency-3.190.0.tgz",
|
||||
"integrity": "sha512-crCh63e8d/Uw9y3dQlVTPja7+IZiXpNXyH6oSuAadTDQwMq6KK87Av1/SDzVf6bAo2KgAOo41MyO2joaCEk0dQ=="
|
||||
"version": "3.215.0",
|
||||
"resolved": "https://registry.npmjs.org/@aws-sdk/invalid-dependency/-/invalid-dependency-3.215.0.tgz",
|
||||
"integrity": "sha512-++bK4BUQe8/CL/YcLZcQB8qPOhiXxhbuhYzfFS7PNVvW1QOLqKRZL/lKs24gzjcOmw7IhAbCybDZwvu2TM4DAg=="
|
||||
},
|
||||
"@aws-sdk/is-array-buffer": {
|
||||
"version": "3.188.0",
|
||||
"resolved": "https://registry.npmjs.org/@aws-sdk/is-array-buffer/-/is-array-buffer-3.188.0.tgz",
|
||||
"integrity": "sha512-n69N4zJZCNd87Rf4NzufPzhactUeM877Y0Tp/F3KiHqGeTnVjYUa4Lv1vLBjqtfjYb2HWT3NKlYn5yzrhaEwiQ=="
|
||||
"version": "3.201.0",
|
||||
"resolved": "https://registry.npmjs.org/@aws-sdk/is-array-buffer/-/is-array-buffer-3.201.0.tgz",
|
||||
"integrity": "sha512-UPez5qLh3dNgt0DYnPD/q0mVJY84rA17QE26hVNOW3fAji8W2wrwrxdacWOxyXvlxWsVRcKmr+lay1MDqpAMfg=="
|
||||
},
|
||||
"@aws-sdk/middleware-content-length": {
|
||||
"version": "3.190.0",
|
||||
"resolved": "https://registry.npmjs.org/@aws-sdk/middleware-content-length/-/middleware-content-length-3.190.0.tgz",
|
||||
"integrity": "sha512-sSU347SuC6I8kWum1jlJlpAqeV23KP7enG+ToWcEcgFrJhm3AvuqB//NJxDbkKb2DNroRvJjBckBvrwNAjQnBQ=="
|
||||
"version": "3.215.0",
|
||||
"resolved": "https://registry.npmjs.org/@aws-sdk/middleware-content-length/-/middleware-content-length-3.215.0.tgz",
|
||||
"integrity": "sha512-zKJRb6jDLFl9nl/muSFbiQHA4uK3skinuDRcyLbpMvvzhuK/PVodv9QI1+wIUsFdXkaSxAlva1oG4bL8ZFi+sQ=="
|
||||
},
|
||||
"@aws-sdk/middleware-endpoint": {
|
||||
"version": "3.215.0",
|
||||
"resolved": "https://registry.npmjs.org/@aws-sdk/middleware-endpoint/-/middleware-endpoint-3.215.0.tgz",
|
||||
"integrity": "sha512-W0QXL5emcN9IXtMbnWT/abLxBFH2tGIfnre2jPNmZ9M7uVFxUwwv5OTUXxNLGNehJHKhiJPwhfQvMy20IDzVcw=="
|
||||
},
|
||||
"@aws-sdk/middleware-host-header": {
|
||||
"version": "3.190.0",
|
||||
"resolved": "https://registry.npmjs.org/@aws-sdk/middleware-host-header/-/middleware-host-header-3.190.0.tgz",
|
||||
"integrity": "sha512-cL7Vo/QSpGx/DDmFxjeV0Qlyi1atvHQDPn3MLBBmi1icu+3GKZkCMAJwzsrV3U4+WoVoDYT9FJ9yMQf2HaIjeQ=="
|
||||
"version": "3.215.0",
|
||||
"resolved": "https://registry.npmjs.org/@aws-sdk/middleware-host-header/-/middleware-host-header-3.215.0.tgz",
|
||||
"integrity": "sha512-GOqI7VwoENZwn+6tIMrrJ4SipIqL2JCh+BNvORVcy7CQxn1ViKkna7iaCx+QMjpg/kn9cR6kfY0n1FmgZR1w9A=="
|
||||
},
|
||||
"@aws-sdk/middleware-logger": {
|
||||
"version": "3.190.0",
|
||||
"resolved": "https://registry.npmjs.org/@aws-sdk/middleware-logger/-/middleware-logger-3.190.0.tgz",
|
||||
"integrity": "sha512-rrfLGYSZCBtiXNrIa8pJ2uwUoUMyj6Q82E8zmduTvqKWviCr6ZKes0lttGIkWhjvhql2m4CbjG5MPBnY7RXL4A=="
|
||||
"version": "3.215.0",
|
||||
"resolved": "https://registry.npmjs.org/@aws-sdk/middleware-logger/-/middleware-logger-3.215.0.tgz",
|
||||
"integrity": "sha512-0h4GGF0rV3jnY3jxmcAWsOdqHCYf25s0biSjmgTei+l/5S+geOGrovRPCNep0LLg0i9D8bkZsXISojilETbf+g=="
|
||||
},
|
||||
"@aws-sdk/middleware-recursion-detection": {
|
||||
"version": "3.190.0",
|
||||
"resolved": "https://registry.npmjs.org/@aws-sdk/middleware-recursion-detection/-/middleware-recursion-detection-3.190.0.tgz",
|
||||
"integrity": "sha512-5tc1AIIZe5jDNdyuJW+7vIFmQOxz3q031ZVrEtUEIF7cz2ySho2lkOWziz+v+UGSLhjHGKMz3V26+aN1FLZNxQ=="
|
||||
"version": "3.215.0",
|
||||
"resolved": "https://registry.npmjs.org/@aws-sdk/middleware-recursion-detection/-/middleware-recursion-detection-3.215.0.tgz",
|
||||
"integrity": "sha512-KQ+kiEsaluM4i6opjusUukxY78+UhfR7vzXHDkzZK/GplQ1hY0B+rwVO1eaULmlnmf3FK+Wd6lwrPV7xS2W+EA=="
|
||||
},
|
||||
"@aws-sdk/middleware-retry": {
|
||||
"version": "3.190.0",
|
||||
"resolved": "https://registry.npmjs.org/@aws-sdk/middleware-retry/-/middleware-retry-3.190.0.tgz",
|
||||
"integrity": "sha512-h1bPopkncf2ue/erJdhqvgR2AEh0bIvkNsIHhx93DckWKotZd/GAVDq0gpKj7/f/7B+teHH8Fg5GDOwOOGyKcg=="
|
||||
"version": "3.215.0",
|
||||
"resolved": "https://registry.npmjs.org/@aws-sdk/middleware-retry/-/middleware-retry-3.215.0.tgz",
|
||||
"integrity": "sha512-I/dnUPVg2Kp3lW+MywBoPp06EOng8IfuaS9ph4bcJpQKrhNU5ekRgCHH2C4k1A6GcP8uyHxQ5TVV6j+l0QPIsA=="
|
||||
},
|
||||
"@aws-sdk/middleware-sdk-sts": {
|
||||
"version": "3.192.0",
|
||||
"resolved": "https://registry.npmjs.org/@aws-sdk/middleware-sdk-sts/-/middleware-sdk-sts-3.192.0.tgz",
|
||||
"integrity": "sha512-xzTV7MyG5ipWYTvekWX1tQc5ExsUvCYsDTBCD3LR5hBrP8assUDPo52zGSe+QMcjgnQv7BcYIzeikTkLEG0dUw=="
|
||||
"version": "3.215.0",
|
||||
"resolved": "https://registry.npmjs.org/@aws-sdk/middleware-sdk-sts/-/middleware-sdk-sts-3.215.0.tgz",
|
||||
"integrity": "sha512-wJRxoDf+2egbRgochaQL8+zzADx8FM/2W0spKNj8x+t/3iqw70QwxCfuEKW/uFQ3ph6eaIrv7gYc8RRjwhD8rg=="
|
||||
},
|
||||
"@aws-sdk/middleware-serde": {
|
||||
"version": "3.190.0",
|
||||
"resolved": "https://registry.npmjs.org/@aws-sdk/middleware-serde/-/middleware-serde-3.190.0.tgz",
|
||||
"integrity": "sha512-S132hEOK4jwbtZ1bGAgSuQ0DMFG4TiD4ulAwbQRBYooC7tiWZbRiR0Pkt2hV8d7WhOHgUpg7rvqlA7/HXXBAsA=="
|
||||
"version": "3.215.0",
|
||||
"resolved": "https://registry.npmjs.org/@aws-sdk/middleware-serde/-/middleware-serde-3.215.0.tgz",
|
||||
"integrity": "sha512-+uhLXdKvvQZcRRFc3UmemSr/YUHA4Jc+1YMjHxc3v8vvfztFJBb0wgBx999myOi8PmkYThlRBQDzXy9UCIhIJw=="
|
||||
},
|
||||
"@aws-sdk/middleware-signing": {
|
||||
"version": "3.192.0",
|
||||
"resolved": "https://registry.npmjs.org/@aws-sdk/middleware-signing/-/middleware-signing-3.192.0.tgz",
|
||||
"integrity": "sha512-qTRIU/TL/dvtTrNj+AkZkgYeTIFslib3Y3XnQNNM6RCm4cMxIgs2K/lnhaUmLdbzHrpOQb4cISkY8yiHo+pNsw=="
|
||||
"version": "3.215.0",
|
||||
"resolved": "https://registry.npmjs.org/@aws-sdk/middleware-signing/-/middleware-signing-3.215.0.tgz",
|
||||
"integrity": "sha512-3BqzYqkmdPeOxjI8DVQE7Bm7J5QIvDy30abglXqrDg6npw6KonKI2Q3FIPFf+oLpZTMStwkoQOnwXHTPrSZ6Tg=="
|
||||
},
|
||||
"@aws-sdk/middleware-stack": {
|
||||
"version": "3.190.0",
|
||||
"resolved": "https://registry.npmjs.org/@aws-sdk/middleware-stack/-/middleware-stack-3.190.0.tgz",
|
||||
"integrity": "sha512-h1mqiWNJdi1OTSEY8QovpiHgDQEeRG818v8yShpqSYXJKEqdn54MA3Z1D2fg/Wv/8ZJsFrBCiI7waT1JUYOmCg=="
|
||||
"version": "3.215.0",
|
||||
"resolved": "https://registry.npmjs.org/@aws-sdk/middleware-stack/-/middleware-stack-3.215.0.tgz",
|
||||
"integrity": "sha512-rdSVL7LxRgjlvoluqwODD4ypBy2k/YVl6FrDplyCMSi8m2WHZG99FzdmR9bpnWK+0DGzYZSMRYx6ynJ9N9PsSw=="
|
||||
},
|
||||
"@aws-sdk/middleware-user-agent": {
|
||||
"version": "3.190.0",
|
||||
"resolved": "https://registry.npmjs.org/@aws-sdk/middleware-user-agent/-/middleware-user-agent-3.190.0.tgz",
|
||||
"integrity": "sha512-y/2cTE1iYHKR0nkb3DvR3G8vt12lcTP95r/iHp8ZO+Uzpc25jM/AyMHWr2ZjqQiHKNlzh8uRw1CmQtgg4sBxXQ=="
|
||||
"version": "3.215.0",
|
||||
"resolved": "https://registry.npmjs.org/@aws-sdk/middleware-user-agent/-/middleware-user-agent-3.215.0.tgz",
|
||||
"integrity": "sha512-X6GfoMNoEITTw7rGL/gWs8UZ0cmmmezvKcl+KtHsA642R05OR4mY5G7LdbWAw0bcrwKsuKOGmwUrC9lzGqbWUw=="
|
||||
},
|
||||
"@aws-sdk/node-config-provider": {
|
||||
"version": "3.190.0",
|
||||
"resolved": "https://registry.npmjs.org/@aws-sdk/node-config-provider/-/node-config-provider-3.190.0.tgz",
|
||||
"integrity": "sha512-TJPUchyeK5KeEXWrwb6oW5/OkY3STCSGR1QIlbPcaTGkbo4kXAVyQmmZsY4KtRPuDM6/HlfUQV17bD716K65rQ=="
|
||||
"version": "3.215.0",
|
||||
"resolved": "https://registry.npmjs.org/@aws-sdk/node-config-provider/-/node-config-provider-3.215.0.tgz",
|
||||
"integrity": "sha512-notckD94QwwxC0GsfpTxB7VH8SREIIlMsUSddqGtpModa0cq/wRb9rqnydZSoznbYpK1ND6h0C9hr/2PNz89zw=="
|
||||
},
|
||||
"@aws-sdk/node-http-handler": {
|
||||
"version": "3.190.0",
|
||||
"resolved": "https://registry.npmjs.org/@aws-sdk/node-http-handler/-/node-http-handler-3.190.0.tgz",
|
||||
"integrity": "sha512-3Klkr73TpZkCzcnSP+gmFF0Baluzk3r7BaWclJHqt2LcFUWfIJzYlnbBQNZ4t3EEq7ZlBJX85rIDHBRlS+rUyA=="
|
||||
"version": "3.215.0",
|
||||
"resolved": "https://registry.npmjs.org/@aws-sdk/node-http-handler/-/node-http-handler-3.215.0.tgz",
|
||||
"integrity": "sha512-btKWSR7m0UuWIN3p5MfSIvhqeYik7xri7U6nWuVI5GVzIYjzxEZOMvPAinDLDxL5wipodi0ZvTUNdDJdm7BcGQ=="
|
||||
},
|
||||
"@aws-sdk/property-provider": {
|
||||
"version": "3.190.0",
|
||||
"resolved": "https://registry.npmjs.org/@aws-sdk/property-provider/-/property-provider-3.190.0.tgz",
|
||||
"integrity": "sha512-uzdKjHE2blbuceTC5zeBgZ0+Uo/hf9pH20CHpJeVNtrrtF3GALtu4Y1Gu5QQVIQBz8gjHnqANx0XhfYzorv69Q=="
|
||||
"version": "3.215.0",
|
||||
"resolved": "https://registry.npmjs.org/@aws-sdk/property-provider/-/property-provider-3.215.0.tgz",
|
||||
"integrity": "sha512-dDPjMCCopkRURAmOJCMSlpIQ5BGWCpYj0+FIfZ5qWQs24fn1PAkQHecOiBhJO0ZSVuQy3xcIyWsAp1NE5e+7ug=="
|
||||
},
|
||||
"@aws-sdk/protocol-http": {
|
||||
"version": "3.190.0",
|
||||
"resolved": "https://registry.npmjs.org/@aws-sdk/protocol-http/-/protocol-http-3.190.0.tgz",
|
||||
"integrity": "sha512-s5MVfeONpfZYRzCSbqQ+wJ3GxKED+aSS7+CQoeaYoD6HDTDxaMGNv9aiPxVCzW02sgG7py7f29Q6Vw+5taZXZA=="
|
||||
"version": "3.215.0",
|
||||
"resolved": "https://registry.npmjs.org/@aws-sdk/protocol-http/-/protocol-http-3.215.0.tgz",
|
||||
"integrity": "sha512-qp6Y6v4S534LAjadiVl9p7ErK7ImphOKq6yhFyQwxko6iITLcz8ib3yU27fs4QJcnNj5ZooqW/YlL/0EikDxCQ=="
|
||||
},
|
||||
"@aws-sdk/querystring-builder": {
|
||||
"version": "3.190.0",
|
||||
"resolved": "https://registry.npmjs.org/@aws-sdk/querystring-builder/-/querystring-builder-3.190.0.tgz",
|
||||
"integrity": "sha512-w9mTKkCsaLIBC8EA4RAHrqethNGbf60CbpPzN/QM7yCV3ZZJAXkppFfjTVVOMbPaI8GUEOptJtzgqV68CRB7ow=="
|
||||
"version": "3.215.0",
|
||||
"resolved": "https://registry.npmjs.org/@aws-sdk/querystring-builder/-/querystring-builder-3.215.0.tgz",
|
||||
"integrity": "sha512-eilk8CqG37BVhQklLif00K2dOJgDzacUi8h3KVQ72ry1V3h345i4HsmaFIxvnz8XtNyDvV8qFAzeYg9n2P9RQA=="
|
||||
},
|
||||
"@aws-sdk/querystring-parser": {
|
||||
"version": "3.190.0",
|
||||
"resolved": "https://registry.npmjs.org/@aws-sdk/querystring-parser/-/querystring-parser-3.190.0.tgz",
|
||||
"integrity": "sha512-vCKP0s33VtS47LSYzEWRRr2aTbi3qNkUuQyIrc5LMqBfS5hsy79P1HL4Q7lCVqZB5fe61N8fKzOxDxWRCF0sXg=="
|
||||
"version": "3.215.0",
|
||||
"resolved": "https://registry.npmjs.org/@aws-sdk/querystring-parser/-/querystring-parser-3.215.0.tgz",
|
||||
"integrity": "sha512-8h/9H8dWM4fZO27UGzo8W5JXln4yJMugPyUl4qFA437gzPgNFN95+oLJWXtHMlfCHC5T/PDKetY9TarMDgBD0Q=="
|
||||
},
|
||||
"@aws-sdk/service-error-classification": {
|
||||
"version": "3.190.0",
|
||||
"resolved": "https://registry.npmjs.org/@aws-sdk/service-error-classification/-/service-error-classification-3.190.0.tgz",
|
||||
"integrity": "sha512-g+s6xtaMa5fCMA2zJQC4BiFGMP7FN5/L1V/UwxCnKy8skCwaN0K5A1tFffBjjbYiPI7Gu7LVorWD2A0Y4xl01Q=="
|
||||
"version": "3.215.0",
|
||||
"resolved": "https://registry.npmjs.org/@aws-sdk/service-error-classification/-/service-error-classification-3.215.0.tgz",
|
||||
"integrity": "sha512-SKBvClGFGzMPsjBBKjneaUazLCNr6bSxe9eFvOr3gCwuwE2jPQwW3VE1mb62howuvm6cLthEDwLQp/FsT1gMsw=="
|
||||
},
|
||||
"@aws-sdk/shared-ini-file-loader": {
|
||||
"version": "3.190.0",
|
||||
"resolved": "https://registry.npmjs.org/@aws-sdk/shared-ini-file-loader/-/shared-ini-file-loader-3.190.0.tgz",
|
||||
"integrity": "sha512-CZC/xsGReUEl5w+JgfancrxfkaCbEisyIFy6HALUYrioWQe80WMqLAdUMZSXHWjIaNK9mH0J/qvcSV2MuIoMzQ=="
|
||||
"version": "3.215.0",
|
||||
"resolved": "https://registry.npmjs.org/@aws-sdk/shared-ini-file-loader/-/shared-ini-file-loader-3.215.0.tgz",
|
||||
"integrity": "sha512-unzQeLOyUiYHr8WxxandHo0OaCj31gx0wpt8dn2cZcHm/MdCqHcHcsQqOVnQsWQrrxY/XZ27cPyMVQeicNKYwQ=="
|
||||
},
|
||||
"@aws-sdk/signature-v4": {
|
||||
"version": "3.190.0",
|
||||
"resolved": "https://registry.npmjs.org/@aws-sdk/signature-v4/-/signature-v4-3.190.0.tgz",
|
||||
"integrity": "sha512-L/R/1X2T+/Kg2k/sjoYyDFulVUGrVcRfyEKKVFIUNg0NwUtw5UKa1/gS7geTKcg4q8M2pd/v+OCBrge2X7phUw=="
|
||||
"version": "3.215.0",
|
||||
"resolved": "https://registry.npmjs.org/@aws-sdk/signature-v4/-/signature-v4-3.215.0.tgz",
|
||||
"integrity": "sha512-Rc73uUCi3eJneO25DydLTfJYamXeuKS9YIhNMTKlpvcN1UQAmAnUbAmCuEmqvkYOiGD1i4/kd8kBga708iIikQ=="
|
||||
},
|
||||
"@aws-sdk/smithy-client": {
|
||||
"version": "3.190.0",
|
||||
"resolved": "https://registry.npmjs.org/@aws-sdk/smithy-client/-/smithy-client-3.190.0.tgz",
|
||||
"integrity": "sha512-f5EoCwjBLXMyuN491u1NmEutbolL0cJegaJbtgK9OJw2BLuRHiBknjDF4OEVuK/WqK0kz2JLMGi9xwVPl4BKCA=="
|
||||
"version": "3.215.0",
|
||||
"resolved": "https://registry.npmjs.org/@aws-sdk/smithy-client/-/smithy-client-3.215.0.tgz",
|
||||
"integrity": "sha512-PiZfCdZkPohzMPrRmJ46TPOf2Tr/dhKYdwQArRnOOIsJABUGXjlzCUE8vysDN35XZYRx5f9hd+/U7kayhniq2w=="
|
||||
},
|
||||
"@aws-sdk/token-providers": {
|
||||
"version": "3.216.0",
|
||||
"resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.216.0.tgz",
|
||||
"integrity": "sha512-cEmOfG7njWl0OA5lR65Sp2SW1i8ZLjf7C95TZ1e6t2Oo5aUFeN3aKBxMOV//1yc+BNzcFBnoHP/f29GhWxUOxA=="
|
||||
},
|
||||
"@aws-sdk/types": {
|
||||
"version": "3.190.0",
|
||||
"resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.190.0.tgz",
|
||||
"integrity": "sha512-mkeZ+vJZzElP6OdRXvuLKWHSlDQxZP9u8BjQB9N0Rw0pCXTzYS0vzIhN1pL0uddWp5fMrIE68snto9xNR6BQuA=="
|
||||
"version": "3.215.0",
|
||||
"resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.215.0.tgz",
|
||||
"integrity": "sha512-eRbCVjwzTYd9C5e2mceScJ6D2kYDDEC3PLkYfJa+1wH9iiF2JlbiYozAokyeYBHQ+AjmD93MK58RBoM8iZfH0Q=="
|
||||
},
|
||||
"@aws-sdk/url-parser": {
|
||||
"version": "3.190.0",
|
||||
"resolved": "https://registry.npmjs.org/@aws-sdk/url-parser/-/url-parser-3.190.0.tgz",
|
||||
"integrity": "sha512-FKFDtxA9pvHmpfWmNVK5BAVRpDgkWMz3u4Sg9UzB+WAFN6UexRypXXUZCFAo8S04FbPKfYOR3O0uVlw7kzmj9g=="
|
||||
"version": "3.215.0",
|
||||
"resolved": "https://registry.npmjs.org/@aws-sdk/url-parser/-/url-parser-3.215.0.tgz",
|
||||
"integrity": "sha512-r/qIk3TUlV36JvoRjTErFm0LzzgNKLB1YUG8zVZCGAc2TEATi8OVEmsZvi+KfTmsbszulITJVcjZKbHLbGoUzg=="
|
||||
},
|
||||
"@aws-sdk/util-base64-browser": {
|
||||
"version": "3.188.0",
|
||||
"resolved": "https://registry.npmjs.org/@aws-sdk/util-base64-browser/-/util-base64-browser-3.188.0.tgz",
|
||||
"integrity": "sha512-qlH+5NZBLiyKziL335BEPedYxX6j+p7KFRWXvDQox9S+s+gLCayednpK+fteOhBenCcR9fUZOVuAPScy1I8qCg=="
|
||||
},
|
||||
"@aws-sdk/util-base64-node": {
|
||||
"version": "3.188.0",
|
||||
"resolved": "https://registry.npmjs.org/@aws-sdk/util-base64-node/-/util-base64-node-3.188.0.tgz",
|
||||
"integrity": "sha512-r1dccRsRjKq+OhVRUfqFiW3sGgZBjHbMeHLbrAs9jrOjU2PTQ8PSzAXLvX/9lmp7YjmX17Qvlsg0NCr1tbB9OA=="
|
||||
"@aws-sdk/util-base64": {
|
||||
"version": "3.208.0",
|
||||
"resolved": "https://registry.npmjs.org/@aws-sdk/util-base64/-/util-base64-3.208.0.tgz",
|
||||
"integrity": "sha512-PQniZph5A6N7uuEOQi+1hnMz/FSOK/8kMFyFO+4DgA1dZ5pcKcn5wiFwHkcTb/BsgVqQa3Jx0VHNnvhlS8JyTg=="
|
||||
},
|
||||
"@aws-sdk/util-body-length-browser": {
|
||||
"version": "3.188.0",
|
||||
@@ -277,59 +287,64 @@
|
||||
"integrity": "sha512-8VpnwFWXhnZ/iRSl9mTf+VKOX9wDE8QtN4bj9pBfxwf90H1X7E8T6NkiZD3k+HubYf2J94e7DbeHs7fuCPW5Qg=="
|
||||
},
|
||||
"@aws-sdk/util-body-length-node": {
|
||||
"version": "3.188.0",
|
||||
"resolved": "https://registry.npmjs.org/@aws-sdk/util-body-length-node/-/util-body-length-node-3.188.0.tgz",
|
||||
"integrity": "sha512-XwqP3vxk60MKp4YDdvDeCD6BPOiG2e+/Ou4AofZOy5/toB6NKz2pFNibQIUg2+jc7mPMnGnvOW3MQEgSJ+gu/Q=="
|
||||
"version": "3.208.0",
|
||||
"resolved": "https://registry.npmjs.org/@aws-sdk/util-body-length-node/-/util-body-length-node-3.208.0.tgz",
|
||||
"integrity": "sha512-3zj50e5g7t/MQf53SsuuSf0hEELzMtD8RX8C76f12OSRo2Bca4FLLYHe0TZbxcfQHom8/hOaeZEyTyMogMglqg=="
|
||||
},
|
||||
"@aws-sdk/util-buffer-from": {
|
||||
"version": "3.188.0",
|
||||
"resolved": "https://registry.npmjs.org/@aws-sdk/util-buffer-from/-/util-buffer-from-3.188.0.tgz",
|
||||
"integrity": "sha512-NX1WXZ8TH20IZb4jPFT2CnLKSqZWddGxtfiWxD9M47YOtq/SSQeR82fhqqVjJn4P8w2F5E28f+Du4ntg/sGcxA=="
|
||||
"version": "3.208.0",
|
||||
"resolved": "https://registry.npmjs.org/@aws-sdk/util-buffer-from/-/util-buffer-from-3.208.0.tgz",
|
||||
"integrity": "sha512-7L0XUixNEFcLUGPeBF35enCvB9Xl+K6SQsmbrPk1P3mlV9mguWSDQqbOBwY1Ir0OVbD6H/ZOQU7hI/9RtRI0Zw=="
|
||||
},
|
||||
"@aws-sdk/util-config-provider": {
|
||||
"version": "3.188.0",
|
||||
"resolved": "https://registry.npmjs.org/@aws-sdk/util-config-provider/-/util-config-provider-3.188.0.tgz",
|
||||
"integrity": "sha512-LBA7tLbi7v4uvbOJhSnjJrxbcRifKK/1ZVK94JTV2MNSCCyNkFotyEI5UWDl10YKriTIUyf7o5cakpiDZ3O4xg=="
|
||||
"version": "3.208.0",
|
||||
"resolved": "https://registry.npmjs.org/@aws-sdk/util-config-provider/-/util-config-provider-3.208.0.tgz",
|
||||
"integrity": "sha512-DSRqwrERUsT34ug+anlMBIFooBEGwM8GejC7q00Y/9IPrQy50KnG5PW2NiTjuLKNi7pdEOlwTSEocJE15eDZIg=="
|
||||
},
|
||||
"@aws-sdk/util-defaults-mode-browser": {
|
||||
"version": "3.190.0",
|
||||
"resolved": "https://registry.npmjs.org/@aws-sdk/util-defaults-mode-browser/-/util-defaults-mode-browser-3.190.0.tgz",
|
||||
"integrity": "sha512-FKxTU4tIbFk2pdUbBNneStF++j+/pB4NYJ1HRSEAb/g4D2+kxikR/WKIv3p0JTVvAkwcuX/ausILYEPUyDZ4HQ=="
|
||||
"version": "3.215.0",
|
||||
"resolved": "https://registry.npmjs.org/@aws-sdk/util-defaults-mode-browser/-/util-defaults-mode-browser-3.215.0.tgz",
|
||||
"integrity": "sha512-MiNfZgB0I4dR8CBxH163W7c9KvE38sgCHNPWopMqSX5ezz7cuCPohCU0XsWd4I7K31PvzuqmKgOiKBAZraQJMA=="
|
||||
},
|
||||
"@aws-sdk/util-defaults-mode-node": {
|
||||
"version": "3.190.0",
|
||||
"resolved": "https://registry.npmjs.org/@aws-sdk/util-defaults-mode-node/-/util-defaults-mode-node-3.190.0.tgz",
|
||||
"integrity": "sha512-qBiIMjNynqAP7p6urG1+ZattYkFaylhyinofVcLEiDvM9a6zGt6GZsxru2Loq0kRAXXGew9E9BWGt45HcDc20g=="
|
||||
"version": "3.215.0",
|
||||
"resolved": "https://registry.npmjs.org/@aws-sdk/util-defaults-mode-node/-/util-defaults-mode-node-3.215.0.tgz",
|
||||
"integrity": "sha512-mSp3R8GljQ+4UT3QMOksQk9L0cWbFLvR7bBmAlt4+GobgTjpRfzFjBP3uwrCqFa3BKDUR3FeJq3qwo+xeY1Krg=="
|
||||
},
|
||||
"@aws-sdk/util-endpoints": {
|
||||
"version": "3.216.0",
|
||||
"resolved": "https://registry.npmjs.org/@aws-sdk/util-endpoints/-/util-endpoints-3.216.0.tgz",
|
||||
"integrity": "sha512-uHje4H6Qj/z/op8UZoSuvGpEZhz/r+AGY0rCihFo7XjhT4RYVxb2Eb9uHRK/IAeHU4kjHAdpQiWGMSmnT/UacA=="
|
||||
},
|
||||
"@aws-sdk/util-hex-encoding": {
|
||||
"version": "3.188.0",
|
||||
"resolved": "https://registry.npmjs.org/@aws-sdk/util-hex-encoding/-/util-hex-encoding-3.188.0.tgz",
|
||||
"integrity": "sha512-QyWovTtjQ2RYxqVM+STPh65owSqzuXURnfoof778spyX4iQ4z46wOge1YV2ZtwS8w5LWd9eeVvDrLu5POPYOnA=="
|
||||
"version": "3.201.0",
|
||||
"resolved": "https://registry.npmjs.org/@aws-sdk/util-hex-encoding/-/util-hex-encoding-3.201.0.tgz",
|
||||
"integrity": "sha512-7t1vR1pVxKx0motd3X9rI3m/xNp78p3sHtP5yo4NP4ARpxyJ0fokBomY8ScaH2D/B+U5o9ARxldJUdMqyBlJcA=="
|
||||
},
|
||||
"@aws-sdk/util-locate-window": {
|
||||
"version": "3.188.0",
|
||||
"resolved": "https://registry.npmjs.org/@aws-sdk/util-locate-window/-/util-locate-window-3.188.0.tgz",
|
||||
"integrity": "sha512-SxobBVLZkkLSawTCfeQnhVX3Azm9O+C2dngZVe1+BqtF8+retUbVTs7OfYeWBlawVkULKF2e781lTzEHBBjCzw=="
|
||||
"version": "3.208.0",
|
||||
"resolved": "https://registry.npmjs.org/@aws-sdk/util-locate-window/-/util-locate-window-3.208.0.tgz",
|
||||
"integrity": "sha512-iua1A2+P7JJEDHVgvXrRJSvsnzG7stYSGQnBVphIUlemwl6nN5D+QrgbjECtrbxRz8asYFHSzhdhECqN+tFiBg=="
|
||||
},
|
||||
"@aws-sdk/util-middleware": {
|
||||
"version": "3.190.0",
|
||||
"resolved": "https://registry.npmjs.org/@aws-sdk/util-middleware/-/util-middleware-3.190.0.tgz",
|
||||
"integrity": "sha512-qzTJ/qhFDzHZS+iXdHydQ/0sWAuNIB5feeLm55Io/I8Utv3l3TKYOhbgGwTsXY+jDk7oD+YnAi7hLN5oEBCwpg=="
|
||||
"version": "3.215.0",
|
||||
"resolved": "https://registry.npmjs.org/@aws-sdk/util-middleware/-/util-middleware-3.215.0.tgz",
|
||||
"integrity": "sha512-DfHGlFlQCr+T/xhjS36HH8JEThDVB5lg5NZ6x4Cibhyeps9YX/4ovLAIx3B19H34sdWhZi7q6LfslCHLRu2+7Q=="
|
||||
},
|
||||
"@aws-sdk/util-uri-escape": {
|
||||
"version": "3.188.0",
|
||||
"resolved": "https://registry.npmjs.org/@aws-sdk/util-uri-escape/-/util-uri-escape-3.188.0.tgz",
|
||||
"integrity": "sha512-4Y6AYZMT483Tiuq8dxz5WHIiPNdSFPGrl6tRTo2Oi2FcwypwmFhqgEGcqxeXDUJktvaCBxeA08DLr/AemVhPCg=="
|
||||
"version": "3.201.0",
|
||||
"resolved": "https://registry.npmjs.org/@aws-sdk/util-uri-escape/-/util-uri-escape-3.201.0.tgz",
|
||||
"integrity": "sha512-TeTWbGx4LU2c5rx0obHeDFeO9HvwYwQtMh1yniBz00pQb6Qt6YVOETVQikRZ+XRQwEyCg/dA375UplIpiy54mA=="
|
||||
},
|
||||
"@aws-sdk/util-user-agent-browser": {
|
||||
"version": "3.190.0",
|
||||
"resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-browser/-/util-user-agent-browser-3.190.0.tgz",
|
||||
"integrity": "sha512-c074wjsD+/u9vT7DVrBLkwVhn28I+OEHuHaqpTVCvAIjpueZ3oms0e99YJLfpdpEgdLavOroAsNFtAuRrrTZZw=="
|
||||
"version": "3.215.0",
|
||||
"resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-browser/-/util-user-agent-browser-3.215.0.tgz",
|
||||
"integrity": "sha512-uZz6BJWr8sJcA+onveS1lFqnbIXBHwvkyHLgCuuGhAxd5yY6YNLhpJBnhy9Fb8/aSbk6yao3qxlokqw9gthmAw=="
|
||||
},
|
||||
"@aws-sdk/util-user-agent-node": {
|
||||
"version": "3.190.0",
|
||||
"resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-node/-/util-user-agent-node-3.190.0.tgz",
|
||||
"integrity": "sha512-R36BMvvPX8frqFhU4lAsrOJ/2PJEHH/Jz1WZzO3GWmVSEAQQdHmo8tVPE3KOM7mZWe5Hj1dZudFAIxWHHFYKJA=="
|
||||
"version": "3.215.0",
|
||||
"resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-node/-/util-user-agent-node-3.215.0.tgz",
|
||||
"integrity": "sha512-4lrdd1oGRwJEwfvgvg1jcJ2O0bwElsvtiqZfTRHN6MNTFUqsKl0xHlgFChQsz3Hfrc1niWtZCmbqQKGdO5ARpw=="
|
||||
},
|
||||
"@aws-sdk/util-utf8-browser": {
|
||||
"version": "3.188.0",
|
||||
@@ -337,14 +352,14 @@
|
||||
"integrity": "sha512-jt627x0+jE+Ydr9NwkFstg3cUvgWh56qdaqAMDsqgRlKD21md/6G226z/Qxl7lb1VEW2LlmCx43ai/37Qwcj2Q=="
|
||||
},
|
||||
"@aws-sdk/util-utf8-node": {
|
||||
"version": "3.188.0",
|
||||
"resolved": "https://registry.npmjs.org/@aws-sdk/util-utf8-node/-/util-utf8-node-3.188.0.tgz",
|
||||
"integrity": "sha512-hCgP4+C0Lekjpjt2zFJ2R/iHes5sBGljXa5bScOFAEkRUc0Qw0VNgTv7LpEbIOAwGmqyxBoCwBW0YHPW1DfmYQ=="
|
||||
"version": "3.208.0",
|
||||
"resolved": "https://registry.npmjs.org/@aws-sdk/util-utf8-node/-/util-utf8-node-3.208.0.tgz",
|
||||
"integrity": "sha512-jKY87Acv0yWBdFxx6bveagy5FYjz+dtV8IPT7ay1E2WPWH1czoIdMAkc8tSInK31T6CRnHWkLZ1qYwCbgRfERQ=="
|
||||
},
|
||||
"@types/node": {
|
||||
"version": "18.11.2",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-18.11.2.tgz",
|
||||
"integrity": "sha512-BWN3M23gLO2jVG8g/XHIRFWiiV4/GckeFIqbU/C4V3xpoBBWSMk4OZomouN0wCkfQFPqgZikyLr7DOYDysIkkw=="
|
||||
"version": "18.11.9",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-18.11.9.tgz",
|
||||
"integrity": "sha512-CRpX21/kGdzjOpFsZSkcrXMGIBWMGNIHXXBVFSH+ggkftxg+XYP20TESbh+zFvFj3EQOl5byk0HTRn1IL6hbqg=="
|
||||
},
|
||||
"@types/webidl-conversions": {
|
||||
"version": "7.0.0",
|
||||
@@ -376,11 +391,6 @@
|
||||
"resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz",
|
||||
"integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ=="
|
||||
},
|
||||
"denque": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/denque/-/denque-2.1.0.tgz",
|
||||
"integrity": "sha512-HVQE3AAb/pxF8fQAoiqpvg9i3evqug3hoiwakOyZAwJm+6vZehbkYXZ0l4JxS+I3QxM97v5aaRNhj8v5oBhekw=="
|
||||
},
|
||||
"fast-xml-parser": {
|
||||
"version": "4.0.11",
|
||||
"resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-4.0.11.tgz",
|
||||
@@ -402,14 +412,14 @@
|
||||
"integrity": "sha512-ZS4Bp4r/Zoeq6+NLJpP+0Zzm0pR8whtGPf1XExKLJBAczGMnSi3It14OiNCStjQjM6NU1okjQGSxgEZN8eBYKg=="
|
||||
},
|
||||
"mongodb": {
|
||||
"version": "4.11.0",
|
||||
"resolved": "https://registry.npmjs.org/mongodb/-/mongodb-4.11.0.tgz",
|
||||
"integrity": "sha512-9l9n4Nk2BYZzljW3vHah3Z0rfS5npKw6ktnkmFgTcnzaXH1DRm3pDl6VMHu84EVb1lzmSaJC4OzWZqTkB5i2wg=="
|
||||
"version": "4.12.1",
|
||||
"resolved": "https://registry.npmjs.org/mongodb/-/mongodb-4.12.1.tgz",
|
||||
"integrity": "sha512-koT87tecZmxPKtxRQD8hCKfn+ockEL2xBiUvx3isQGI6mFmagWt4f4AyCE9J4sKepnLhMacoCTQQA6SLAI2L6w=="
|
||||
},
|
||||
"mongodb-connection-string-url": {
|
||||
"version": "2.5.4",
|
||||
"resolved": "https://registry.npmjs.org/mongodb-connection-string-url/-/mongodb-connection-string-url-2.5.4.tgz",
|
||||
"integrity": "sha512-SeAxuWs0ez3iI3vvmLk/j2y+zHwigTDKQhtdxTgt5ZCOQQS5+HW4g45/Xw5vzzbn7oQXCNQ24Z40AkJsizEy7w=="
|
||||
"version": "2.6.0",
|
||||
"resolved": "https://registry.npmjs.org/mongodb-connection-string-url/-/mongodb-connection-string-url-2.6.0.tgz",
|
||||
"integrity": "sha512-WvTZlI9ab0QYtTYnuMLgobULWhokRjtC7db9LtcVfJ+Hsnyr5eo6ZtNAt3Ly24XZScGMelOcGtm7lSn0332tPQ=="
|
||||
},
|
||||
"punycode": {
|
||||
"version": "2.1.1",
|
||||
@@ -447,9 +457,9 @@
|
||||
"integrity": "sha512-l7FvfAHlcmulp8kr+flpQZmVwtu7nfRV7NZujtN0OqES8EL4O4e0qqzL0DC5gAvx/ZC/9lk6rhcUwYvkBnBnYA=="
|
||||
},
|
||||
"tslib": {
|
||||
"version": "2.4.0",
|
||||
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.0.tgz",
|
||||
"integrity": "sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ=="
|
||||
"version": "2.4.1",
|
||||
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.1.tgz",
|
||||
"integrity": "sha512-tGyy4dAjRIEwI7BzsB0lynWgOpfqjUdq91XXAlIWD2OwKBH7oCl/GZG/HT4BOHrTlPMOASlMQ7veyTqpmRcrNA=="
|
||||
},
|
||||
"uuid": {
|
||||
"version": "8.3.2",
|
||||
|
||||
@@ -3,12 +3,12 @@
|
||||
|
||||
Package.describe({
|
||||
summary: "Wrapper around the mongo npm package",
|
||||
version: '4.11.0',
|
||||
version: '4.12.1',
|
||||
documentation: null
|
||||
});
|
||||
|
||||
Npm.depends({
|
||||
mongodb: "4.11.0"
|
||||
mongodb: "4.12.1"
|
||||
});
|
||||
|
||||
Package.onUse(function (api) {
|
||||
|
||||
@@ -136,7 +136,7 @@ OAuth._checkRedirectUrlOrigin = redirectUrl => {
|
||||
);
|
||||
};
|
||||
|
||||
const middleware = (req, res, next) => {
|
||||
const middleware = async (req, res, next) => {
|
||||
let requestData;
|
||||
|
||||
// Make sure to catch any exceptions because otherwise we'd crash
|
||||
@@ -168,7 +168,7 @@ const middleware = (req, res, next) => {
|
||||
requestData = req.body;
|
||||
}
|
||||
|
||||
handler(service, requestData, res);
|
||||
await handler(service, requestData, res);
|
||||
} catch (err) {
|
||||
// if we got thrown an error, save it off, it will get passed to
|
||||
// the appropriate login call (if any) and reported there.
|
||||
@@ -473,3 +473,31 @@ OAuth.openSecrets = (serviceData, userId) => {
|
||||
);
|
||||
return result;
|
||||
};
|
||||
|
||||
OAuth._addValuesToQueryParams = (
|
||||
values = {},
|
||||
queryParams = new URLSearchParams()
|
||||
) => {
|
||||
Object.entries(values).forEach(([key, value]) => {
|
||||
queryParams.set(key, `${value}`);
|
||||
});
|
||||
return queryParams;
|
||||
};
|
||||
|
||||
OAuth._fetch = async (
|
||||
url,
|
||||
method = 'GET',
|
||||
{ headers = {}, queryParams = {}, body, ...options } = {}
|
||||
) => {
|
||||
const urlWithParams = new URL(url);
|
||||
|
||||
OAuth._addValuesToQueryParams(queryParams, urlWithParams.searchParams);
|
||||
|
||||
const requestOptions = {
|
||||
method: method.toUpperCase(),
|
||||
headers,
|
||||
...(body ? { body } : {}),
|
||||
...options,
|
||||
};
|
||||
return fetch(urlWithParams.toString(), requestOptions);
|
||||
};
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
Package.describe({
|
||||
summary: "Common code for OAuth-based services",
|
||||
version: "2.1.2"
|
||||
version: "2.1.3"
|
||||
});
|
||||
|
||||
Package.onUse(api => {
|
||||
@@ -11,6 +11,7 @@ Package.onUse(api => {
|
||||
api.use(['reload', 'base64'], 'client');
|
||||
|
||||
api.use('oauth-encryption', 'server', {weak: true});
|
||||
api.use('fetch', 'server');
|
||||
|
||||
|
||||
api.export('OAuth');
|
||||
|
||||
@@ -19,12 +19,12 @@ export class OAuth1Binding {
|
||||
this._urls = urls;
|
||||
}
|
||||
|
||||
prepareRequestToken(callbackUrl) {
|
||||
async prepareRequestToken(callbackUrl) {
|
||||
const headers = this._buildHeader({
|
||||
oauth_callback: callbackUrl
|
||||
});
|
||||
|
||||
const response = this._call('POST', this._urls.requestToken, headers);
|
||||
const response = await this._call({method: 'POST', url: this._urls.requestToken, headers});
|
||||
const tokens = querystring.parse(response.content);
|
||||
|
||||
if (! tokens.oauth_callback_confirmed)
|
||||
@@ -35,7 +35,7 @@ export class OAuth1Binding {
|
||||
this.requestTokenSecret = tokens.oauth_token_secret;
|
||||
}
|
||||
|
||||
prepareAccessToken(query, requestTokenSecret) {
|
||||
async prepareAccessToken(query, requestTokenSecret) {
|
||||
// support implementations that use request token secrets. This is
|
||||
// read by this._call.
|
||||
//
|
||||
@@ -50,7 +50,7 @@ export class OAuth1Binding {
|
||||
oauth_verifier: query.oauth_verifier
|
||||
});
|
||||
|
||||
const response = this._call('POST', this._urls.accessToken, headers);
|
||||
const response = await this._call({ method: 'POST', url: this._urls.accessToken, headers });
|
||||
const tokens = querystring.parse(response.content);
|
||||
|
||||
if (! tokens.oauth_token || ! tokens.oauth_token_secret) {
|
||||
@@ -66,7 +66,7 @@ export class OAuth1Binding {
|
||||
this.accessTokenSecret = tokens.oauth_token_secret;
|
||||
}
|
||||
|
||||
call(method, url, params, callback) {
|
||||
async callAsync(method, url, params, callback) {
|
||||
const headers = this._buildHeader({
|
||||
oauth_token: this.accessToken
|
||||
});
|
||||
@@ -75,14 +75,29 @@ export class OAuth1Binding {
|
||||
params = {};
|
||||
}
|
||||
|
||||
return this._call(method, url, headers, params, callback);
|
||||
return this._call({ method, url, headers, params, callback });
|
||||
}
|
||||
|
||||
async getAsync(url, params, callback) {
|
||||
return this.callAsync('GET', url, params, callback);
|
||||
}
|
||||
|
||||
async postAsync(url, params, callback) {
|
||||
return this.callAsync('POST', url, params, callback);
|
||||
}
|
||||
|
||||
call(method, url, params, callback) {
|
||||
// Require changes when remove Fibers. Exposed to public api.
|
||||
return Promise.await(this.callAsync(method, url, params, callback));
|
||||
}
|
||||
|
||||
get(url, params, callback) {
|
||||
// Require changes when remove Fibers. Exposed to public api.
|
||||
return this.call('GET', url, params, callback);
|
||||
}
|
||||
|
||||
post(url, params, callback) {
|
||||
// Require changes when remove Fibers. Exposed to public api.
|
||||
return this.call('POST', url, params, callback);
|
||||
}
|
||||
|
||||
@@ -118,7 +133,7 @@ export class OAuth1Binding {
|
||||
return crypto.createHmac('SHA1', signingKey).update(signatureBase).digest('base64');
|
||||
};
|
||||
|
||||
_call(method, url, headers = {}, params = {}, callback) {
|
||||
async _call({method, url, headers = {}, params = {}, callback}) {
|
||||
// all URLs to be functions to support parameters/customization
|
||||
if(typeof url === "function") {
|
||||
url = url(this);
|
||||
@@ -141,29 +156,52 @@ export class OAuth1Binding {
|
||||
|
||||
// Make a authorization string according to oauth1 spec
|
||||
const authString = this._getAuthHeaderString(headers);
|
||||
|
||||
// Make signed request
|
||||
try {
|
||||
const response = HTTP.call(method, url, {
|
||||
params,
|
||||
headers: {
|
||||
Authorization: authString
|
||||
return OAuth._fetch(url, method, {
|
||||
headers: {
|
||||
Authorization: authString,
|
||||
...(method.toUpperCase() === 'POST' ? { 'Content-Type': 'application/x-www-form-urlencoded' } : {})
|
||||
},
|
||||
...(method.toUpperCase() === 'POST' ?
|
||||
{ body: OAuth._addValuesToQueryParams(params).toString() }
|
||||
: { queryParams: params })
|
||||
}).then((res) =>
|
||||
res.text().then((content) => {
|
||||
const responseHeaders = Array.from(res.headers.entries()).reduce(
|
||||
(acc, [key, val]) => {
|
||||
return { ...acc, [key.toLowerCase()]: val };
|
||||
},
|
||||
{}
|
||||
);
|
||||
const data = responseHeaders['content-type'].includes('application/json') ?
|
||||
JSON.parse(content) : undefined;
|
||||
return {
|
||||
content: data ? '' : content,
|
||||
data,
|
||||
headers: { ...responseHeaders, nonce: headers.oauth_nonce },
|
||||
redirected: res.redirected,
|
||||
ok: res.ok,
|
||||
statusCode: res.status,
|
||||
};
|
||||
})
|
||||
)
|
||||
.then((response) => {
|
||||
if (callback) {
|
||||
callback(undefined, response);
|
||||
}
|
||||
}, callback && ((error, response) => {
|
||||
if (! error) {
|
||||
response.nonce = headers.oauth_nonce;
|
||||
return response;
|
||||
})
|
||||
.catch((err) => {
|
||||
if (callback) {
|
||||
callback(err);
|
||||
}
|
||||
callback(error, response);
|
||||
}));
|
||||
// We store nonce so that JWTs can be validated
|
||||
if (response)
|
||||
response.nonce = headers.oauth_nonce;
|
||||
return response;
|
||||
} catch (err) {
|
||||
throw Object.assign(new Error(`Failed to send OAuth1 request to ${url}. ${err.message}`),
|
||||
{response: err.response});
|
||||
}
|
||||
};
|
||||
console.log({ err });
|
||||
throw Object.assign(
|
||||
new Error(`Failed to send OAuth1 request to ${url}. ${err.message}`),
|
||||
{ response: err.response }
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
_encodeHeader(header) {
|
||||
return Object.keys(header).reduce((memo, key) => {
|
||||
|
||||
@@ -6,7 +6,7 @@ OAuth._queryParamsWithAuthTokenUrl = (authUrl, oauthBinding, params = {}, whitel
|
||||
|
||||
Object.assign(
|
||||
redirectUrlObj.query,
|
||||
whitelistedQueryParams.reduce((prev, param) =>
|
||||
whitelistedQueryParams.reduce((prev, param) =>
|
||||
params.query[param] ? { ...prev, param: params.query[param] } : prev,
|
||||
{}
|
||||
),
|
||||
@@ -25,7 +25,7 @@ OAuth._queryParamsWithAuthTokenUrl = (authUrl, oauthBinding, params = {}, whitel
|
||||
};
|
||||
|
||||
// connect middleware
|
||||
OAuth._requestHandlers['1'] = (service, query, res) => {
|
||||
OAuth._requestHandlers['1'] = async (service, query, res) => {
|
||||
const config = ServiceConfiguration.configurations.findOne({service: service.serviceName});
|
||||
if (! config) {
|
||||
throw new ServiceConfiguration.ConfigError(service.serviceName);
|
||||
@@ -45,7 +45,7 @@ OAuth._requestHandlers['1'] = (service, query, res) => {
|
||||
});
|
||||
|
||||
// Get a request token to start auth process
|
||||
oauthBinding.prepareRequestToken(callbackUrl);
|
||||
await oauthBinding.prepareRequestToken(callbackUrl);
|
||||
|
||||
// Keep track of request token so we can verify it on the next step
|
||||
OAuth._storeRequestToken(
|
||||
@@ -91,10 +91,10 @@ OAuth._requestHandlers['1'] = (service, query, res) => {
|
||||
// subsequent call to the `login` method will be immediate.
|
||||
|
||||
// Get the access token for signing requests
|
||||
oauthBinding.prepareAccessToken(query, requestTokenInfo.requestTokenSecret);
|
||||
await oauthBinding.prepareAccessToken(query, requestTokenInfo.requestTokenSecret);
|
||||
|
||||
// Run service-specific handler.
|
||||
const oauthResult = service.handleOauthRequest(
|
||||
const oauthResult = await service.handleOauthRequest(
|
||||
oauthBinding, { query: query });
|
||||
|
||||
const credentialToken = OAuth._credentialTokenFromQuery(query);
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import http from 'http';
|
||||
import { OAuth1Binding } from './oauth1_binding';
|
||||
|
||||
const testPendingCredential = (test, method) => {
|
||||
const testPendingCredential = async (test, method) => {
|
||||
const twitterfooId = Random.id();
|
||||
const twitterfooName = `nickname${Random.id()}`;
|
||||
const twitterfooAccessToken = Random.id();
|
||||
@@ -17,8 +17,8 @@ const testPendingCredential = (test, method) => {
|
||||
authenticate: "https://example.com/oauth/authenticate"
|
||||
};
|
||||
|
||||
OAuth1Binding.prototype.prepareRequestToken = () => {};
|
||||
OAuth1Binding.prototype.prepareAccessToken = function() {
|
||||
OAuth1Binding.prototype.prepareRequestToken = async () => {};
|
||||
OAuth1Binding.prototype.prepareAccessToken = async function() {
|
||||
this.accessToken = twitterfooAccessToken;
|
||||
this.accessTokenSecret = twitterfooAccessTokenSecret;
|
||||
};
|
||||
@@ -27,7 +27,7 @@ const testPendingCredential = (test, method) => {
|
||||
|
||||
try {
|
||||
// register a fake login service
|
||||
OAuth.registerService(serviceName, 1, urls, query => ({
|
||||
OAuth.registerService(serviceName, 1, urls, async query => ({
|
||||
serviceData: {
|
||||
id: twitterfooId,
|
||||
screenName: twitterfooName,
|
||||
@@ -71,7 +71,7 @@ const testPendingCredential = (test, method) => {
|
||||
respData += args[0];
|
||||
return end.apply(this, arguments);
|
||||
};
|
||||
OAuthTest.middleware(req, res);
|
||||
await OAuthTest.middleware(req, res);
|
||||
const credentialSecret = respData;
|
||||
|
||||
// Test that the result for the token is available
|
||||
@@ -94,17 +94,17 @@ const testPendingCredential = (test, method) => {
|
||||
}
|
||||
};
|
||||
|
||||
Tinytest.add("oauth1 - pendingCredential is stored and can be retrieved (without oauth encryption)", test => {
|
||||
Tinytest.addAsync("oauth1 - pendingCredential is stored and can be retrieved (without oauth encryption)", async test => {
|
||||
OAuthEncryption.loadKey(null);
|
||||
testPendingCredential(test, "GET");
|
||||
testPendingCredential(test, "POST");
|
||||
await testPendingCredential(test, "GET");
|
||||
await testPendingCredential(test, "POST");
|
||||
});
|
||||
|
||||
Tinytest.add("oauth1 - pendingCredential is stored and can be retrieved (with oauth encryption)", test => {
|
||||
Tinytest.addAsync("oauth1 - pendingCredential is stored and can be retrieved (with oauth encryption)", async test => {
|
||||
try {
|
||||
OAuthEncryption.loadKey(Buffer.from([1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16]).toString("base64"));
|
||||
testPendingCredential(test, "GET");
|
||||
testPendingCredential(test, "POST");
|
||||
await testPendingCredential(test, "GET");
|
||||
await testPendingCredential(test, "POST");
|
||||
} finally {
|
||||
OAuthEncryption.loadKey(null);
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
Package.describe({
|
||||
summary: "Common code for OAuth1-based login services",
|
||||
version: "1.5.0",
|
||||
version: "1.5.1",
|
||||
});
|
||||
|
||||
Package.onUse(api => {
|
||||
@@ -8,10 +8,7 @@ Package.onUse(api => {
|
||||
api.use('random');
|
||||
api.use('service-configuration', ['client', 'server']);
|
||||
api.use('oauth', ['client', 'server']);
|
||||
api.use([
|
||||
'check',
|
||||
'http@1.4.4 || 2.0.0'
|
||||
], 'server');
|
||||
api.use('check', 'server');
|
||||
|
||||
api.use('mongo');
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// connect middleware
|
||||
OAuth._requestHandlers['2'] = (service, query, res) => {
|
||||
OAuth._requestHandlers['2'] = async (service, query, res) => {
|
||||
let credentialSecret;
|
||||
|
||||
// check if user authorized access
|
||||
@@ -7,7 +7,7 @@ OAuth._requestHandlers['2'] = (service, query, res) => {
|
||||
// Prepare the login results before returning.
|
||||
|
||||
// Run service-specific handler.
|
||||
const oauthResult = service.handleOauthRequest(query);
|
||||
const oauthResult = await service.handleOauthRequest(query);
|
||||
credentialSecret = Random.secret();
|
||||
|
||||
const credentialToken = OAuth._credentialTokenFromQuery(query);
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import http from 'http';
|
||||
|
||||
const testPendingCredential = function (test, method) {
|
||||
const testPendingCredential = async function (test, method) {
|
||||
const foobookId = Random.id();
|
||||
const foobookOption1 = Random.id();
|
||||
const credentialToken = Random.id();
|
||||
@@ -51,7 +51,7 @@ const testPendingCredential = function (test, method) {
|
||||
return end.apply(this, args);
|
||||
};
|
||||
|
||||
OAuthTest.middleware(req, res);
|
||||
await OAuthTest.middleware(req, res);
|
||||
const credentialSecret = respData;
|
||||
|
||||
// Test that the result for the token is available
|
||||
@@ -72,17 +72,17 @@ const testPendingCredential = function (test, method) {
|
||||
}
|
||||
};
|
||||
|
||||
Tinytest.add("oauth2 - pendingCredential is stored and can be retrieved (without oauth encryption)", test => {
|
||||
Tinytest.addAsync("oauth2 - pendingCredential is stored and can be retrieved (without oauth encryption)", async test => {
|
||||
OAuthEncryption.loadKey(null);
|
||||
testPendingCredential(test, "GET");
|
||||
testPendingCredential(test, "POST");
|
||||
await testPendingCredential(test, "GET");
|
||||
await testPendingCredential(test, "POST");
|
||||
});
|
||||
|
||||
Tinytest.add("oauth2 - pendingCredential is stored and can be retrieved (with oauth encryption)", test => {
|
||||
Tinytest.addAsync("oauth2 - pendingCredential is stored and can be retrieved (with oauth encryption)", async test => {
|
||||
try {
|
||||
OAuthEncryption.loadKey(Buffer.from([1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16]).toString("base64"));
|
||||
testPendingCredential(test, "GET");
|
||||
testPendingCredential(test, "POST");
|
||||
await testPendingCredential(test, "GET");
|
||||
await testPendingCredential(test, "POST");
|
||||
} finally {
|
||||
OAuthEncryption.loadKey(null);
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
Package.describe({
|
||||
summary: "Common code for OAuth2-based login services",
|
||||
version: "1.3.1",
|
||||
version: "1.3.2",
|
||||
});
|
||||
|
||||
Package.onUse(api => {
|
||||
|
||||
@@ -464,14 +464,14 @@ Tinytest.add("package-version-parser - Invalid in 0.9.2", function (test) {
|
||||
var invalidVersions =
|
||||
["1.0.0_1", "1.0.0 || 2.0.0", "1.0.0-rc1_1",
|
||||
"3.4.0-rc1 || =1.0.0"];
|
||||
_.each(invalidVersions, function (v) {
|
||||
invalidVersions.forEach(function (v) {
|
||||
test.isTrue(PackageVersion.invalidFirstFormatConstraint(v));
|
||||
});
|
||||
|
||||
// These are all valid in 0.9.2.
|
||||
var validVersions =
|
||||
["1.0.0", "2.0.0-rc1", "=2.5.0"];
|
||||
_.each(validVersions, function (v) {
|
||||
validVersions.forEach(function (v) {
|
||||
test.isFalse(PackageVersion.invalidFirstFormatConstraint(v));
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
Package.describe({
|
||||
summary: "Parses Meteor Smart Package version strings",
|
||||
version: "3.2.0"
|
||||
version: "3.2.1"
|
||||
});
|
||||
|
||||
Npm.depends({
|
||||
@@ -14,7 +14,6 @@ Package.onUse(function (api) {
|
||||
});
|
||||
|
||||
Package.onTest(function (api) {
|
||||
api.use('package-version-parser');
|
||||
api.use(['tinytest', 'underscore']);
|
||||
api.use(['package-version-parser', 'tinytest']);
|
||||
api.addFiles('package-version-parser-tests.js', 'server');
|
||||
});
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
Package.describe({
|
||||
name: "promise",
|
||||
version: "0.12.1",
|
||||
version: "0.12.2",
|
||||
summary: "ECMAScript 2015 Promise polyfill with Fiber support",
|
||||
git: "https://github.com/meteor/promise",
|
||||
documentation: "README.md"
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
require("./extensions.js");
|
||||
|
||||
require("meteor-promise").makeCompatible(
|
||||
Promise,
|
||||
// Allow every Promise callback to run in a Fiber drawn from a pool of
|
||||
// reusable Fibers.
|
||||
require("fibers")
|
||||
);
|
||||
if (!process.env.DISABLE_FIBERS) {
|
||||
require("meteor-promise").makeCompatible(
|
||||
Promise,
|
||||
// Allow every Promise callback to run in a Fiber drawn from a pool of
|
||||
// reusable Fibers.
|
||||
require("fibers")
|
||||
);
|
||||
}
|
||||
|
||||
// Reference: https://caniuse.com/#feat=promises
|
||||
require("meteor/modern-browsers").setMinimumBrowserVersions({
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
Package.describe({
|
||||
name: 'standard-minifier-css',
|
||||
version: '1.8.2',
|
||||
version: '1.8.3',
|
||||
summary: 'Standard css minifier used with Meteor apps by default.',
|
||||
documentation: 'README.md'
|
||||
});
|
||||
|
||||
@@ -60,7 +60,7 @@ class CssToolsMinifier {
|
||||
path: 'merged-stylesheets.css'
|
||||
}];
|
||||
} else {
|
||||
const minifiedFiles = CssTools.minifyCss(merged.code);
|
||||
const minifiedFiles = await CssTools.minifyCssAsync(merged.code);
|
||||
|
||||
result = minifiedFiles.map(minified => ({
|
||||
data: minified
|
||||
|
||||
@@ -142,8 +142,13 @@ testAsyncMulti = function (name, funcs, { isOnly = false } = {}) {
|
||||
test.extraDetails.asyncBlock = i++;
|
||||
|
||||
new Promise(resolve => {
|
||||
resolve(func.apply(context, [test, _.bind(em.expect, em)]));
|
||||
}).then(result => {
|
||||
const result = func.apply(context, [test, _.bind(em.expect, em)]);
|
||||
if (result && typeof result.then === "function") {
|
||||
return result.then((r) => resolve(r))
|
||||
}
|
||||
|
||||
return resolve(result);
|
||||
}).then(() => {
|
||||
em.done();
|
||||
}, exception => {
|
||||
if (em.cancel()) {
|
||||
@@ -191,3 +196,24 @@ pollUntil = function (expect, f, timeout, step, noFail) {
|
||||
step
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* Helper that is used on the async tests.
|
||||
* Just run the function and assert if we have an error or not.
|
||||
* @param fn
|
||||
* @param test
|
||||
* @param shouldErrorOut
|
||||
* @returns {Promise<*>}
|
||||
*/
|
||||
runAndThrowIfNeeded = async (fn, test, shouldErrorOut) => {
|
||||
let err, result;
|
||||
try {
|
||||
result = await fn();
|
||||
} catch (e) {
|
||||
err = e;
|
||||
}
|
||||
|
||||
test[shouldErrorOut ? "isTrue" : "isFalse"](err);
|
||||
|
||||
return result;
|
||||
};
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user