Revert "Merge pull request #12273 from meteor/release-2.9"

This reverts commit 19e9e2082e, reversing
changes made to 644592cedc.
This commit is contained in:
denihs
2022-12-12 09:53:19 -04:00
parent 90236e7ccf
commit 5d19fec09b
171 changed files with 1748 additions and 7983 deletions

View File

@@ -1,4 +1,4 @@
version: 2.1
version: 2
# A reusable "run" snippet which is ran before each test to setup the
# environment for user-limits, core-dumps, etc.
@@ -96,30 +96,6 @@ 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
@@ -191,7 +167,6 @@ jobs:
path: /tmp/memuse.txt
Isolated Tests:
<<: *can_disable_fibers
<<: *build_machine_environment
steps:
- run:
@@ -200,7 +175,6 @@ jobs:
<<: *run_env_change
- attach_workspace:
at: .
- run: *set_fibers_env
- run:
name: "Print environment"
command: printenv
@@ -235,7 +209,6 @@ jobs:
path: /tmp/memuse.txt
Test Group 0:
<<: *can_disable_fibers
<<: *build_machine_environment
steps:
- run:
@@ -244,7 +217,6 @@ jobs:
<<: *run_env_change
- attach_workspace:
at: .
- run: *set_fibers_env
- run:
name: "Print environment"
command: printenv
@@ -277,7 +249,6 @@ jobs:
path: /tmp/memuse.txt
Test Group 1:
<<: *can_disable_fibers
<<: *build_machine_environment
steps:
- run:
@@ -286,7 +257,6 @@ jobs:
<<: *run_env_change
- attach_workspace:
at: .
- run: *set_fibers_env
- run:
name: "Print environment"
command: printenv
@@ -319,7 +289,6 @@ jobs:
path: /tmp/memuse.txt
Test Group 2:
<<: *can_disable_fibers
<<: *build_machine_environment
steps:
- run:
@@ -328,7 +297,6 @@ jobs:
<<: *run_env_change
- attach_workspace:
at: .
- run: *set_fibers_env
- run:
name: "Print environment"
command: printenv
@@ -361,7 +329,6 @@ jobs:
path: /tmp/memuse.txt
Test Group 3:
<<: *can_disable_fibers
<<: *build_machine_environment
steps:
- run:
@@ -370,7 +337,6 @@ jobs:
<<: *run_env_change
- attach_workspace:
at: .
- run: *set_fibers_env
- run:
name: "Print environment"
command: printenv
@@ -403,7 +369,6 @@ jobs:
path: /tmp/memuse.txt
Test Group 4:
<<: *can_disable_fibers
<<: *build_machine_environment
steps:
- run:
@@ -412,7 +377,6 @@ jobs:
<<: *run_env_change
- attach_workspace:
at: .
- run: *set_fibers_env
- run:
name: "Print environment"
command: printenv
@@ -445,7 +409,6 @@ jobs:
path: /tmp/memuse.txt
Test Group 5:
<<: *can_disable_fibers
<<: *build_machine_environment
steps:
- run:
@@ -454,7 +417,6 @@ jobs:
<<: *run_env_change
- attach_workspace:
at: .
- run: *set_fibers_env
- run:
name: "Print environment"
command: printenv
@@ -487,7 +449,6 @@ jobs:
path: /tmp/memuse.txt
Test Group 6:
<<: *can_disable_fibers
<<: *build_machine_environment
steps:
- run:
@@ -496,7 +457,6 @@ jobs:
<<: *run_env_change
- attach_workspace:
at: .
- run: *set_fibers_env
- run:
name: "Print environment"
command: printenv
@@ -529,7 +489,6 @@ jobs:
path: /tmp/memuse.txt
Test Group 7:
<<: *can_disable_fibers
<<: *build_machine_environment
steps:
- run:
@@ -538,7 +497,6 @@ jobs:
<<: *run_env_change
- attach_workspace:
at: .
- run: *set_fibers_env
- run:
name: "Print environment"
command: printenv
@@ -571,7 +529,6 @@ jobs:
path: /tmp/memuse.txt
Test Group 8:
<<: *can_disable_fibers
<<: *build_machine_environment
steps:
- run:
@@ -580,7 +537,6 @@ jobs:
<<: *run_env_change
- attach_workspace:
at: .
- run: *set_fibers_env
- run:
name: "Print environment"
command: printenv
@@ -613,7 +569,6 @@ jobs:
path: /tmp/memuse.txt
Test Group 9:
<<: *can_disable_fibers
<<: *build_machine_environment
steps:
- run:
@@ -622,7 +577,6 @@ jobs:
<<: *run_env_change
- attach_workspace:
at: .
- run: *set_fibers_env
- run:
name: "Print environment"
command: printenv
@@ -655,7 +609,6 @@ jobs:
path: /tmp/memuse.txt
Test Group 10:
<<: *can_disable_fibers
<<: *build_machine_environment
steps:
- run:
@@ -664,7 +617,6 @@ jobs:
<<: *run_env_change
- attach_workspace:
at: .
- run: *set_fibers_env
- run:
name: "Print environment"
command: printenv
@@ -775,7 +727,6 @@ jobs:
npm test
Clean Up:
<<: *can_disable_fibers
<<: *build_machine_environment
steps:
- attach_workspace:
@@ -858,58 +809,45 @@ 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

View File

@@ -8,17 +8,13 @@ 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:
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
- CXX=g++-4.8
addons:
apt:
sources:

View File

@@ -1,104 +1,3 @@
## 2.9, 2022-XX-XX
### Highlights
* TypeScript update to v4.6.4 [PR](https://github.com/meteor/meteor/pull/12204)
* 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 some 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 default 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)
* [package-version-parser] Remove underscore[PR](https://github.com/meteor/meteor/pull/12248) by [harryadel](https://github.com/harryadel)
* updated mongo [PR](https://github.com/meteor/meteor/pull/12333) by [Grubba27](https://github.com/Grubba27)
* feat: vue3-skel [PR](https://github.com/meteor/meteor/pull/12302) by [henriquealbert](https://github.com/henriquealbert)
#### Breaking Changes
* Most of OAuth related code has been moved from `accounts-base` to `accounts-oauth`
#### Migration Steps
#### 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
@@ -116,6 +15,9 @@ N/A
#### Meteor Version Release
* `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
#### Special thanks to
- [@henriquealbert](https://github.com/henriquealbert)
@@ -124,7 +26,6 @@ N/A
For making this great framework even better!
## 2.8.1, 2022-11-14
#### Highlights
@@ -231,7 +132,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`
@@ -240,6 +141,7 @@ _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
@@ -282,16 +184,7 @@ 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`:

View File

@@ -82,28 +82,6 @@ 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" %}

View File

@@ -129,19 +129,7 @@ Create a basic [Blaze](https://blazejs.org/) app.
`--vue`
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/)
Create a basic vue-based app. See the [Vue guide](https://guide.meteor.com/vue.html)
for more information.
`--svelte`
@@ -158,350 +146,43 @@ 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-2` | `--svelte` | `--tailwind` | `--chakra-ui` | `--solid` | `--vue` |
|------------------------------------------------------------------------------------------------------|:-------------------:|:--------:|:--------:|:-----------:|:---------:|:----------:|:---------:|:----------:|:------------:|:-------------:|:---------:|:-------:|
| [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 | 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 |
| [insecure](https://atmospherejs.com/meteor/insecure) | X | | | | 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 | 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 | | | | | |
| [reactive-var](https://atmospherejs.com/meteor/reactive-var) | X | 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 | 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 | | | | |
| [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 | | |
| [vite:bundler](https://atmospherejs.com/vite/bundler) | | | | | | | | | | | X | 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.
running
```bash
meteor generate customer
```
It will generate the following code in ``/imports/api``
![Screenshot 2022-11-09 at 11 28 29](https://user-images.githubusercontent.com/70247653/200856551-71c100f5-8714-4b34-9678-4f08780dcc8b.png)
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:
![Screenshot 2022-11-09 at 11 32 39](https://user-images.githubusercontent.com/70247653/200857560-a4874e4c-1078-4b7a-9381-4c6590d2f63b.png)
<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.
![Screenshot 2022-11-09 at 11 38 29](https://user-images.githubusercontent.com/70247653/200859087-a2ef63b6-7ac1-492b-8918-0630cbd30686.png)
---
<h3 id="meteorgenerate-templating"> Using your own template </h3>
`--templatePath`
```bash
meteor generate feed --templatePath=/scaffolds-ts
```
![Screenshot 2022-11-09 at 11 42 47](https://user-images.githubusercontent.com/70247653/200860178-2341befe-bcfd-422f-a4bd-7c9918abfd97.png)
> 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);
}
```
| | 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 | |
<h2 id="meteorloginlogout">meteor login / logout</h2>
@@ -923,8 +604,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)

View File

@@ -5,7 +5,6 @@ edit_branch: 'devel'
edit_path: 'guide'
content_root: 'content'
versions:
- '2.9'
- '2.8'
- '2.7'
- '2.6'
@@ -38,7 +37,7 @@ sidebar_categories:
- index
- code-style
- structure
- 2.9-migration
- 2.8-migration
Data:
- collections
- data-loading

View File

@@ -1,102 +0,0 @@
---
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="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)

2
meteor
View File

@@ -1,6 +1,6 @@
#!/usr/bin/env bash
BUNDLE_VERSION=14.21.1.2
BUNDLE_VERSION=14.21.1.0
# OS Check. Put here because here is where we download the precompiled
# bundles that are arch specific.

View File

@@ -1,6 +1,6 @@
{
"name": "eslint-plugin-meteor",
"version": "7.4.0",
"version": "7.3.0",
"author": "Dominik Ferber <dominik.ferber+npm@gmail.com>",
"description": "Meteor specific linting rules for ESLint",
"main": "lib/index.js",

View File

@@ -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.6.4",
"@meteorjs/babel": "7.17.1-beta.0",
typescript: "4.5.4",
"@meteorjs/babel": "7.16.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",

View File

@@ -80,7 +80,11 @@ exports.getDefaults = function getDefaults(features) {
function maybeAddReactPlugins(features, options) {
if (features && features.react) {
options.presets.push(require("@babel/preset-react"));
options.presets.push(
[require("@babel/preset-react"), {
runtime: "automatic"
}]
);
options.plugins.push(
[require("@babel/plugin-proposal-class-properties"), {
loose: true
@@ -185,13 +189,11 @@ function getDefaultsForNode8(features) {
// Ensure that async functions run in a Fiber, while also taking
// full advantage of native async/await support in Node 8.
if (!process.env.DISABLE_FIBERS) {
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
}]);
}
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
}]);
// Enable async generator functions proposal.
combined.plugins.push(require("@babel/plugin-proposal-async-generator-functions"));

File diff suppressed because it is too large Load Diff

View File

@@ -1,7 +1,7 @@
{
"name": "@meteorjs/babel",
"author": "Meteor <dev@meteor.com>",
"version": "7.17.2-beta.0",
"version": "7.16.1-beta.0",
"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.6.4"
"typescript": "^4.5.4"
},
"devDependencies": {
"@babel/plugin-proposal-decorators": "7.14.5",

View File

@@ -11,21 +11,19 @@ Module.prototype.resolve = function (id) {
require("@meteorjs/reify/lib/runtime").enable(Module.prototype);
if (!process.env.DISABLE_FIBERS) {
require("meteor-promise").makeCompatible(
global.Promise = global.Promise ||
require("promise/lib/es6-extensions"),
require("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);
};
}

View File

@@ -1,7 +1,7 @@
const path = require('path');
const os = require('os');
const METEOR_LATEST_VERSION = '2.9.0';
const METEOR_LATEST_VERSION = '2.8.1';
const sudoUser = process.env.SUDO_USER || '';
function isRoot() {
return process.getuid && process.getuid() === 0;

View File

@@ -1,6 +1,6 @@
{
"name": "meteor",
"version": "2.9.0",
"version": "2.8.2",
"description": "Install Meteor",
"main": "install.js",
"scripts": {

View File

@@ -798,11 +798,6 @@ 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

View File

@@ -94,20 +94,6 @@ 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) => {
@@ -164,7 +150,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, () => {
@@ -192,38 +178,6 @@ 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',

View File

@@ -79,6 +79,40 @@ 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];
}
});
}
});
}
/**
@@ -136,18 +170,6 @@ 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.
//
@@ -242,7 +264,6 @@ 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}`);
}
});
@@ -397,15 +418,6 @@ 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
@@ -418,6 +430,9 @@ 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;

View File

@@ -1,5 +1,4 @@
import crypto from 'crypto';
import { Meteor } from 'meteor/meteor'
import {
AccountsCommon,
EXPIRE_TOKENS_INTERVAL_MS,
@@ -435,7 +434,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.
//
async _attemptLogin(
_attemptLogin(
methodInvocation,
methodName,
methodArgs,
@@ -495,18 +494,18 @@ export class AccountsServer extends AccountsCommon {
// Ensure that thrown exceptions are caught and that login hook
// callbacks are still called.
//
async _loginMethod(
_loginMethod(
methodInvocation,
methodName,
methodArgs,
type,
fn
) {
return await this._attemptLogin(
return this._attemptLogin(
methodInvocation,
methodName,
methodArgs,
await tryLoginMethod(type, fn)
tryLoginMethod(type, fn)
);
};
@@ -583,10 +582,11 @@ 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.
async _runLoginHandlers(methodInvocation, options) {
_runLoginHandlers(methodInvocation, options) {
for (let handler of this._loginHandlers) {
const result = await tryLoginMethod(handler.name, async () =>
await handler.handler.call(methodInvocation, options)
const result = tryLoginMethod(
handler.name,
() => handler.handler.call(methodInvocation, options)
);
if (result) {
@@ -594,10 +594,7 @@ 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");
}
}
@@ -642,15 +639,14 @@ 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 = async function (options) {
methods.login = 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 = await accounts._runLoginHandlers(this, options);
//console.log({result});
const result = accounts._runLoginHandlers(this, options);
return await accounts._attemptLogin(this, "login", arguments, result);
return accounts._attemptLogin(this, "login", arguments, result);
};
methods.logout = function () {
@@ -725,19 +721,14 @@ export class AccountsServer extends AccountsCommon {
throw new Meteor.Error(403, "Service unknown");
}
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`);
const { ServiceConfiguration } = Package['service-configuration'];
if (ServiceConfiguration.configurations.findOne({service: options.service}))
throw new Meteor.Error(403, `Service ${options.service} already configured`);
if (Package["oauth-encryption"]) {
const { OAuthEncryption } = Package["oauth-encryption"]
if (hasOwn.call(options, 'secret') && OAuthEncryption.keyIsLoaded())
options.secret = OAuthEncryption.seal(options.secret);
}
if (hasOwn.call(options, 'secret') && usingOAuthEncryption())
options.secret = OAuthEncryption.seal(options.secret);
ServiceConfiguration.configurations.insert(options);
}
ServiceConfiguration.configurations.insert(options);
};
accounts._server.methods(methods);
@@ -762,10 +753,8 @@ export class AccountsServer extends AccountsCommon {
// Publish all login service configuration fields other than secret.
this._server.publish("meteor.loginServiceConfiguration", () => {
if (Package['service-configuration']) {
const { ServiceConfiguration } = Package['service-configuration'];
return ServiceConfiguration.configurations.find({}, {fields: {secret: 0}});
}
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
@@ -1518,10 +1507,10 @@ const cloneAttemptWithConnection = (connection, attempt) => {
return clonedAttempt;
};
const tryLoginMethod = async (type, fn) => {
const tryLoginMethod = (type, fn) => {
let result;
try {
result = await fn();
result = fn();
}
catch (e) {
result = {error: e};
@@ -1690,7 +1679,17 @@ const setExpireTokensInterval = accounts => {
}, EXPIRE_TOKENS_INTERVAL_MS);
};
const OAuthEncryption = Package["oauth-encryption"]?.OAuthEncryption;
///
/// OAuth Encryption Support
///
const OAuthEncryption =
Package["oauth-encryption"] &&
Package["oauth-encryption"].OAuthEncryption;
const usingOAuthEncryption = () => {
return OAuthEncryption && OAuthEncryption.keyIsLoaded();
};
// OAuth service data is temporarily stored in the pending credentials
// collection during the oauth authentication process. Sensitive data
@@ -1702,12 +1701,44 @@ const OAuthEncryption = Package["oauth-encryption"]?.OAuthEncryption;
const pinEncryptedFieldsToUser = (serviceData, userId) => {
Object.keys(serviceData).forEach(key => {
let value = serviceData[key];
if (OAuthEncryption?.isSealed(value))
if (OAuthEncryption && 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) => {

View File

@@ -604,62 +604,6 @@ 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 => {

View File

@@ -1,6 +1,6 @@
Package.describe({
summary: 'A user account system',
version: '2.2.6',
version: '2.2.5',
});
Package.onUse(api => {
@@ -15,6 +15,10 @@ 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']);

View File

@@ -1,24 +1,3 @@
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 = {};
@@ -52,37 +31,3 @@ 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];
}
});
}
});

View File

@@ -1,5 +1,3 @@
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 => {
@@ -57,44 +55,3 @@ 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)
}
});
});
});

View File

@@ -1,6 +1,6 @@
Package.describe({
summary: "Common code for OAuth-based login services",
version: "1.4.2",
version: "1.4.1",
});
Package.onUse(api => {
@@ -9,11 +9,6 @@ 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');

View File

@@ -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.2',
version: '2.3.1',
});
Npm.depends({

View File

@@ -7,10 +7,12 @@ 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: [
{

View File

@@ -1,5 +1,8 @@
import { hash as bcryptHash, compare as bcryptCompare } from 'bcrypt';
import { Accounts } from "meteor/accounts-base";
import bcrypt from 'bcrypt'
import {Accounts} from "meteor/accounts-base";
const bcryptHash = Meteor.wrapAsync(bcrypt.hash);
const bcryptCompare = Meteor.wrapAsync(bcrypt.compare);
// Utility for grabbing user
const getUserById = (id, options) => Meteor.users.findOne(id, Accounts._addDefaultFieldSelector(options));
@@ -45,9 +48,9 @@ const getPasswordString = password => {
// SHA256 before bcrypt) or an object with properties `digest` and
// `algorithm` (in which case we bcrypt `password.digest`).
//
const hashPassword = async password => {
const hashPassword = password => {
password = getPasswordString(password);
return await bcryptHash(password, Accounts._bcryptRounds());
return bcryptHash(password, Accounts._bcryptRounds());
};
// Extract the number of rounds used in the specified bcrypt hash.
@@ -71,7 +74,7 @@ const getRoundsFromBcryptHash = hash => {
// The user parameter needs at least user._id and user.services
Accounts._checkPasswordUserFields = {_id: 1, services: 1};
//
const checkPasswordAsync = async (user, password) => {
Accounts._checkPassword = (user, password) => {
const result = {
userId: user._id
};
@@ -80,16 +83,15 @@ const checkPasswordAsync = async (user, password) => {
const hash = user.services.password.bcrypt;
const hashRounds = getRoundsFromBcryptHash(hash);
if (! await bcryptCompare(formattedPassword, hash)) {
if (! 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(async () => {
Meteor.defer(() => {
Meteor.users.update({ _id: user._id }, {
$set: {
'services.password.bcrypt':
await bcryptHash(formattedPassword, Accounts._bcryptRounds())
bcryptHash(formattedPassword, Accounts._bcryptRounds())
}
});
});
@@ -97,13 +99,7 @@ const checkPasswordAsync = async (user, password) => {
return result;
};
const checkPassword = (user, password) => {
return Promise.await(checkPasswordAsync(user, password));
};
Accounts._checkPassword = checkPassword;
Accounts._checkPasswordAsync = checkPasswordAsync;
const checkPassword = Accounts._checkPassword;
///
/// LOGIN
@@ -167,7 +163,7 @@ const passwordValidator = Match.OneOf(
//
// Note that neither password option is secure without SSL.
//
Accounts.registerLoginHandler("password", async options => {
Accounts.registerLoginHandler("password", options => {
if (!options.password)
return undefined; // don't handle
@@ -192,7 +188,7 @@ Accounts.registerLoginHandler("password", async options => {
Accounts._handleError("User has no password set");
}
const result = await checkPasswordAsync(user, options.password);
const result = checkPassword(user, options.password);
// This method is added by the package accounts-2fa
// First the login is validated, then the code situation is checked
if (
@@ -262,7 +258,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: async function (oldPassword, newPassword) {
Meteor.methods({changePassword: function (oldPassword, newPassword) {
check(oldPassword, passwordValidator);
check(newPassword, passwordValidator);
@@ -282,12 +278,12 @@ Meteor.methods({changePassword: async function (oldPassword, newPassword) {
Accounts._handleError("User has no password set");
}
const result = await checkPasswordAsync(user, oldPassword);
const result = checkPassword(user, oldPassword);
if (result.error) {
throw result.error;
}
const hashed = await hashPassword(newPassword);
const hashed = 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
@@ -320,10 +316,10 @@ Meteor.methods({changePassword: async function (oldPassword, newPassword) {
* @param {Object} options.logout Logout all current connections with this userId (default: true)
* @importFromPackage accounts-base
*/
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 }));
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 }))
options = { logout: true , ...options };
const user = getUserById(userId, {fields: {_id: 1}});
@@ -335,7 +331,7 @@ Accounts.setPasswordAsync = async (userId, newPlaintextPassword, options) => {
$unset: {
'services.password.reset': 1
},
$set: {'services.password.bcrypt': await hashPassword(newPlaintextPassword)}
$set: {'services.password.bcrypt': hashPassword(newPlaintextPassword)}
};
if (options.logout) {
@@ -345,19 +341,6 @@ Accounts.setPasswordAsync = async (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
@@ -577,15 +560,15 @@ Accounts.sendEnrollmentEmail = (userId, email, extraTokenData, extraParams) => {
// Take token from sendResetPasswordEmail or sendEnrollmentEmail, change
// the users password, and log them in.
Meteor.methods({resetPassword: async function (...args) {
Meteor.methods({resetPassword: function (...args) {
const token = args[0];
const newPassword = args[1];
return await Accounts._loginMethod(
return Accounts._loginMethod(
this,
"resetPassword",
args,
"password",
async () => {
() => {
check(token, String);
check(newPassword, passwordValidator);
@@ -634,7 +617,7 @@ Meteor.methods({resetPassword: async function (...args) {
error: new Meteor.Error(403, "Token has invalid email address")
};
const hashed = await hashPassword(newPassword);
const hashed = 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
@@ -729,9 +712,9 @@ Accounts.sendVerificationEmail = (userId, email, extraTokenData, extraParams) =>
// Take token from sendVerificationEmail, mark the email as verified,
// and log them in.
Meteor.methods({verifyEmail: async function (...args) {
Meteor.methods({verifyEmail: function (...args) {
const token = args[0];
return await Accounts._loginMethod(
return Accounts._loginMethod(
this,
"verifyEmail",
args,
@@ -905,7 +888,7 @@ Accounts.removeEmail = (userId, email) => {
// does the actual user insertion.
//
// returns the user id
const createUser = async options => {
const createUser = options => {
// Unknown keys allowed, because a onCreateUserHook can take arbitrary
// options.
check(options, Match.ObjectIncluding({
@@ -920,22 +903,22 @@ const createUser = async options => {
const user = {services: {}};
if (password) {
const hashed = await hashPassword(password);
const hashed = 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: async function (...args) {
Meteor.methods({createUser: function (...args) {
const options = args[0];
return await Accounts._loginMethod(
return Accounts._loginMethod(
this,
"createUser",
args,
"password",
async () => {
() => {
// createUser() above does more checking.
check(options, Object);
if (Accounts._options.forbidClientAccountCreation)
@@ -943,7 +926,7 @@ Meteor.methods({createUser: async function (...args) {
error: new Meteor.Error(403, "Signups forbidden")
};
const userId = await Accounts.createUserVerifyingEmail(options);
const userId = Accounts.createUserVerifyingEmail(options);
// client gets logged in as the new user afterwards.
return {userId: userId};
@@ -965,10 +948,10 @@ Meteor.methods({createUser: async function (...args) {
* @param {Object} options.profile The user's profile, typically including the `name` field.
* @importFromPackage accounts-base
* */
Accounts.createUserVerifyingEmail = async (options) => {
Accounts.createUserVerifyingEmail = (options) => {
options = { ...options };
// Create user. result contains id and token.
const userId = await createUser(options);
const userId = 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)
@@ -993,15 +976,14 @@ Accounts.createUserVerifyingEmail = async (options) => {
// Unlike the client version, this does not log you in as this user
// after creation.
//
// returns Promise<userId> or throws an error if it can't create
// 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.createUserAsync = async (options, callback) => {
Accounts.createUser = (options, callback) => {
options = { ...options };
// XXX allow an optional callback?
@@ -1012,23 +994,6 @@ Accounts.createUserAsync = async (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
///

View File

@@ -1747,7 +1747,7 @@ if (Meteor.isServer) (() => {
Tinytest.addAsync(
'passwords - allow custom bcrypt rounds',
async (test, done) => {
(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;
await Accounts._checkPasswordAsync(user1, password);
Accounts._checkPassword(user1, password);
Meteor.setTimeout(() => {
user1 = Meteor.users.findOne(userId1);
rounds = getUserHashRounds(user1);

File diff suppressed because it is too large Load Diff

View File

@@ -1,11 +1,11 @@
Package.describe({
name: "babel-compiler",
summary: "Parser/transpiler for ECMAScript 2015+ syntax",
version: '7.10.1'
version: '7.9.0'
});
Npm.depends({
'@meteorjs/babel': '7.17.2-beta.0',
'@meteorjs/babel': '7.16.1-beta.0',
'json5': '2.1.1'
});

View File

@@ -1,6 +1,6 @@
Package.describe({
name: 'ecmascript',
version: '0.16.4',
version: '0.16.3',
summary: 'Compiler plugin that supports ES2015+ in all .js files',
documentation: 'README.md',
});

View File

@@ -2,6 +2,7 @@ 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';
@@ -24,7 +25,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:') {
@@ -59,7 +60,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;
@@ -109,7 +110,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
@@ -137,40 +138,40 @@ const getTransport = function () {
};
let nextDevModeMailId = 0;
EmailTest._getAndIncNextDevModeMailId = function () {
return nextDevModeMailId++;
};
let output_stream = process.stdout;
// Testing hooks
EmailTest.resetNextDevModeMailId = function () {
EmailTest.overrideOutputStream = function(stream) {
nextDevModeMailId = 0;
output_stream = stream;
};
const devModeSendAsync = function (mail, options) {
const stream = options?.stream || process.stdout;
return new Promise((resolve, reject) => {
let devModeMailId = EmailTest._getAndIncNextDevModeMailId();
EmailTest.restoreOutputStream = function() {
output_stream = process.stdout;
};
// 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 ' +
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 ' +
'environment variable.)\n'
);
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));
);
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();
});
future.wait();
};
const smtpSend = function (transport, mail) {
const smtpSend = function(transport, mail) {
transport._syncSendMail(mail);
};
@@ -185,7 +186,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);
};
@@ -230,75 +231,23 @@ Email.customTransport = undefined;
* You can create a `MailComposer` object via
* `new EmailInternals.NpmModules.mailcomposer.module`.
*/
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;
Email.send = function(options) {
if (options.mailComposer) {
options = options.mailComposer.mail;
}
// 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(email);
sendHooks.forEach(hook => {
send = hook(options);
return send;
});
if (!send) {
return;
}
if (!send) return;
if (Email.customTransport) {
const customTransport = Email.customTransport;
if (customTransport) {
const packageSettings = Meteor.settings.packages?.email || {};
return Email.customTransport({ packageSettings, ...email });
customTransport({ packageSettings, ...options });
return;
}
const mailUrlEnv = process.env.MAIL_URL;
@@ -314,8 +263,8 @@ Email.sendAsync = async function (options) {
if (mailUrlEnv || mailUrlSettings) {
const transport = getTransport();
smtpSend(transport, email);
smtpSend(transport, options);
return;
}
return devModeSendAsync(email, options);
devModeSend(options);
};

View File

@@ -1,21 +0,0 @@
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');
};

View File

@@ -1,85 +1,304 @@
import { Email } from 'meteor/email';
import { smokeEmailTest } from './email_test_helpers';
import { TEST_CASES } from './email_tests_data';
import streamBuffers from 'stream-buffers';
const CUSTOM_TRANSPORT_SETTINGS = {
email: { service: '1on1', user: 'test', password: 'pwd' },
};
const devWarningBanner = "(Mail not sent; to enable " +
"sending, set the MAIL_URL environment variable.)\n";
const sleep = (ms) => {
return new Promise((resolve) => setTimeout(resolve, ms));
};
function smokeEmailTest(testFunction) {
// This only tests dev mode, so don't run the test if this is deployed.
if (process.env.MAIL_URL) return;
// 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);
});
});
});
});
try {
const stream = new streamBuffers.WritableStreamBuffer;
EmailTest.overrideOutputStream(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());
});
});
});
testFunction(stream);
// 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;
} finally {
EmailTest.restoreOutputStream();
}
);
}
Tinytest.add('[Sync] email - hooks stop the sending', function (test) {
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) {
// Register hooks
const hook1 = Email.hookSend((options) => {
// Test that we get options through
@@ -94,218 +313,17 @@ Tinytest.add('[Sync] 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',
stream,
from: "foo@example.com",
to: "bar@example.com",
text: "*Cool*, man",
html: "<i>Cool</i>, man",
});
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',
});
}
);

View File

@@ -1,254 +0,0 @@
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'
);
},
},
},
];

View File

@@ -1,6 +1,6 @@
Package.describe({
summary: 'Send email messages',
version: '2.2.3',
version: '2.2.2',
});
Npm.depends({

View File

@@ -4,13 +4,13 @@ import { Accounts } from 'meteor/accounts-base';
const API_VERSION = Meteor.settings?.public?.packages?.['facebook-oauth']?.apiVersion || '13.0';
Facebook.handleAuthFromAccessToken = async (accessToken, expiresAt) => {
Facebook.handleAuthFromAccessToken = (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 = await getIdentity(accessToken, whitelisted);
const identity = 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, async query => {
const response = await getTokenResponse(query);
OAuth.registerService('facebook', 2, null, query => {
const response = 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,86 +61,73 @@ function getAbsoluteUrlOptions(query) {
}
}
/**
* @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();
// 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();
const absoluteUrlOptions = getAbsoluteUrlOptions(query);
const redirectUri = OAuth._redirectUri('facebook', config, undefined, absoluteUrlOptions);
let responseContent;
try {
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 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
};
};
const getIdentity = async (accessToken, fields) => {
const config = ServiceConfiguration.configurations.findOne({
service: 'facebook',
});
if (!config) throw new ServiceConfiguration.ConfigError();
const getIdentity = (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);
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 }
);
});
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 },
);
}
};
Facebook.retrieveCredential = (credentialToken, credentialSecret) =>

View File

@@ -1,12 +1,13 @@
Package.describe({
summary: "Facebook OAuth flow",
version: '1.11.2'
version: '1.11.1'
});
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']);

View File

@@ -1,9 +1,12 @@
Github = {};
OAuth.registerService('github', 2, null, async (query) => {
const accessToken = await getAccessToken(query);
const identity = await getIdentity(accessToken);
const emails = await getEmails(accessToken);
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);
const primaryEmail = emails.find((email) => email.primary);
return {
@@ -28,7 +31,7 @@ OAuth.registerService('github', 2, null, async (query) => {
let userAgent = 'Meteor';
if (Meteor.release) userAgent += `/${Meteor.release}`;
const getAccessToken = async (query) => {
const getAccessToken = async (query, callback) => {
const config = ServiceConfiguration.configurations.findOne({
service: 'github'
});
@@ -65,16 +68,18 @@ const getAccessToken = async (query) => {
);
}
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) => {
const getIdentity = async (accessToken, callback) => {
try {
const request = await fetch('https://api.github.com/user', {
method: 'GET',
@@ -84,8 +89,11 @@ const getIdentity = async (accessToken) => {
Authorization: `token ${accessToken}`
} // http://developer.github.com/v3/#user-agent-required
});
return await request.json();
const response = await request.json();
callback(null, response);
return response;
} catch (err) {
callback(err.message);
throw Object.assign(
new Error(`Failed to fetch identity from Github. ${err.message}`),
{ response: err.response }
@@ -93,7 +101,7 @@ const getIdentity = async (accessToken) => {
}
};
const getEmails = async (accessToken) => {
const getEmails = async (accessToken, callback) => {
try {
const request = await fetch('https://api.github.com/user/emails', {
method: 'GET',
@@ -103,8 +111,11 @@ const getEmails = async (accessToken) => {
Authorization: `token ${accessToken}`
} // http://developer.github.com/v3/#user-agent-required
});
return await request.json();
const response = await request.json();
callback(null, response);
return response;
} catch (err) {
callback(err.message, []);
return [];
}
};

View File

@@ -1,6 +1,6 @@
Package.describe({
summary: 'GitHub OAuth flow',
version: '1.4.1'
version: '1.4.0'
});
Package.onUse(api => {

View File

@@ -5,46 +5,40 @@ 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 = async (tokens, callback) => {
const getServiceDataFromTokens = tokens => {
const { accessToken, idToken } = tokens;
const scopes = await getScopes(accessToken).catch((err) => {
const error = Object.assign(
const scopesCall = Meteor.wrapAsync(getScopes);
let scopes;
try {
scopes = scopesCall(accessToken);
} catch (err) {
throw Object.assign(
new Error(`Failed to fetch tokeninfo from Google. ${err.message}`),
{ response: err.response }
);
callback && callback(error);
throw error;
});
let identity = await getIdentity(accessToken).catch((err) => {
const error = Object.assign(
}
const identityCall = Meteor.wrapAsync(getIdentity);
let identity;
try {
identity = identityCall(accessToken);
} catch (err) {
throw 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);
@@ -62,25 +56,22 @@ const getServiceDataFromTokens = async (tokens, callback) => {
if (tokens.refreshToken) {
serviceData.refreshToken = tokens.refreshToken;
}
const returnValue = {
return {
serviceData,
options: {
profile: {
name: identity.name,
},
},
name: identity.name
}
}
};
callback && callback(undefined, returnValue);
return returnValue;
};
Accounts.registerLoginHandler(async (request) => {
Accounts.registerLoginHandler(request => {
if (request.googleSignIn !== true) {
return;
}
console.log({ request });
const tokens = {
accessToken: request.accessToken,
refreshToken: request.refreshToken,
@@ -88,38 +79,29 @@ Accounts.registerLoginHandler(async (request) => {
};
if (request.serverAuthCode) {
Object.assign(
tokens,
await getTokens({
code: request.serverAuthCode,
})
);
Object.assign(tokens, getTokens({
code: request.serverAuthCode
}));
}
let result;
try {
result = await getServiceDataFromTokens(tokens);
result = 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 }
);
}
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
);
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:
@@ -127,48 +109,45 @@ Accounts.registerLoginHandler(async (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',
});
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,
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 response = await request.json();
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}`
);
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}`);
} else {
const data = {
accessToken: response.access_token,
refreshToken: response.refresh_token,
expiresIn: response.expires_in,
idToken: response.id_token,
idToken: response.id_token
};
callback && callback(undefined, data);
callback(undefined, data);
return data;
}
};
const getServiceData = async (query) =>
getServiceDataFromTokens(await getTokens(query));
const getTokensCall = Meteor.wrapAsync(getTokens);
const getServiceData = query => getServiceDataFromTokens(getTokensCall(query));
OAuth.registerService('google', 2, null, getServiceData);
@@ -180,15 +159,14 @@ 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 && callback(e);
callback(e);
throw new Meteor.Error(e.reason);
}
callback && callback(undefined, response);
callback(undefined, response);
return response;
};
@@ -200,15 +178,14 @@ 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 && callback(e);
callback(e);
throw new Meteor.Error(e.reason);
}
callback && callback(undefined, response.scope.split(' '));
callback(undefined, response.scope.split(' '));
return response.scope.split(' ');
};

View File

@@ -1,6 +1,6 @@
Package.describe({
summary: "Google OAuth flow",
version: "1.4.3",
version: "1.4.2",
});
Cordova.depends({

View File

@@ -1,10 +1,10 @@
Meetup = {};
OAuth.registerService('meetup', 2, null, async query => {
const response = await getAccessToken(query);
OAuth.registerService('meetup', 2, null, query => {
const response = getAccessToken(query);
const accessToken = response.access_token;
const expiresAt = (+new Date) + (1000 * response.expires_in);
const identity = await getIdentity(accessToken);
const identity = getIdentity(accessToken);
const {
id,
name,
@@ -33,63 +33,50 @@ OAuth.registerService('meetup', 2, null, async query => {
};
});
const getAccessToken = async query => {
const getAccessToken = query => {
const config = ServiceConfiguration.configurations.findOne({service: 'meetup'});
if (!config)
throw new ServiceConfiguration.ConfigError();
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
});
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 }
);
}
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 },
);
});
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;
}
};
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 => {
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) {
throw Object.assign(
new Error(`Failed to fetch identity from Meetup. ${err.message}`),
{ response: err.response }
);
});
}
};
Meetup.retrieveCredential = (credentialToken, credentialSecret) =>
OAuth.retrieveCredential(credentialToken, credentialSecret);

View File

@@ -1,12 +1,13 @@
Package.describe({
summary: 'Meetup OAuth flow',
version: '1.1.2'
version: '1.1.1'
});
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']);

View File

@@ -1,7 +1,7 @@
OAuth.registerService("meteor-developer", 2, null, async query => {
const response = await getTokens(query);
OAuth.registerService("meteor-developer", 2, null, query => {
const response = getTokens(query);
const { accessToken } = response;
const identity = await getIdentity(accessToken);
const identity = getIdentity(accessToken);
const serviceData = {
accessToken: OAuth.sealSecret(accessToken),
@@ -28,77 +28,69 @@ OAuth.registerService("meteor-developer", 2, null, async 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 = async (query) => {
const getTokens = 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)
}
}
);
} catch (err) {
throw Object.assign(
new Error(
"Failed to complete OAuth handshake with Meteor developer accounts. "
+ err.message
),
{response: err.response}
);
}
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();
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 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')
);
const getIdentity = accessToken => {
try {
return HTTP.get(
`${MeteorDeveloperAccounts._server}/api/v1/identity`,
{
headers: { Authorization: `Bearer ${accessToken}`}
}
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 }
);
});
).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);

View File

@@ -1,11 +1,12 @@
Package.describe({
summary: 'Meteor developer accounts OAuth flow',
version: '1.3.2'
version: '1.3.1'
});
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');

View File

@@ -1,6 +1,6 @@
Package.describe({
summary: 'The Meteor command-line tool',
version: '2.9.0',
version: '2.8.2',
});
Package.includeTool();

View File

@@ -1,22 +0,0 @@
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);
});
};

View File

@@ -71,38 +71,6 @@ 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.
@@ -203,3 +171,5 @@ function logErr(err) {
);
}
}
Meteor._isFibersEnabled = global._isFibersEnabled;

View File

@@ -147,19 +147,12 @@ export namespace Meteor {
}): void;
/**
* Invokes a method with a sync stub, passing any number of arguments.
* Invokes a method 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
@@ -441,14 +434,7 @@ export namespace Meteor {
*/
function publish(
name: string | null,
func: (
this: Subscription,
...args: any[]
) =>
| void
| Mongo.Cursor<any>
| Mongo.Cursor<any>[]
| Promise<void | Mongo.Cursor<any> | Mongo.Cursor<any>[]>,
func: (this: Subscription, ...args: any[]) => void,
options?: { is_auto: boolean }
): void;

View File

@@ -2,7 +2,7 @@
Package.describe({
summary: "Core Meteor environment",
version: '1.10.3'
version: '1.10.2'
});
Package.registerBuildPlugin({
@@ -33,7 +33,6 @@ 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']);
@@ -55,6 +54,8 @@ Package.onUse(function (api) {
// People expect process.exit() to not swallow console output.
// On Windows, it sometimes does, so we fix it for all apps and packages
api.addFiles('flush-buffers-on-exit-in-windows.js', 'server');
api.addAssets('meteor.d.ts', 'server');
});
Package.onTest(function (api) {

View File

@@ -1,51 +0,0 @@
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);
}
);

View File

@@ -1,5 +1,6 @@
import path from 'path';
import url from 'url';
import Future from 'fibers/future';
import postcss from 'postcss';
import cssnano from 'cssnano';
@@ -64,21 +65,23 @@ const CssTools = {
* @return {String[]} Array containing the minified CSS.
*/
minifyCss(cssText) {
return Promise.await(CssTools.minifyCssAsync(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();
/**
* 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]);
// 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];
},
/**
@@ -184,7 +187,6 @@ if (typeof Profile !== 'undefined') {
'parseCss',
'stringifyCss',
'minifyCss',
'minifyCssAsync',
'mergeCssAsts',
'rewriteCssUrls',
].forEach(funcName => {

View File

@@ -1,6 +1,6 @@
Package.describe({
summary: 'CSS minifier',
version: '1.6.2'
version: '1.6.1'
});
Npm.depends({
@@ -19,7 +19,6 @@ Package.onTest(function (api) {
api.use('tinytest');
api.addFiles([
'minifier-tests.js',
'minifier-async-tests.js',
'urlrewriting-tests.js'
], 'server');
});

View File

@@ -39,11 +39,7 @@ export default class Cursor {
}
/**
* @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.
* @summary Returns the number of documents that match a query.
* @memberOf Mongo.Cursor
* @method count
* @instance

View File

@@ -39,14 +39,6 @@ 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}

View File

@@ -1,6 +1,6 @@
Package.describe({
summary: "Meteor's client-side datastore: a port of MongoDB to Javascript",
version: '1.9.1'
version: '1.9.0'
});
Package.onUse(api => {

View File

@@ -319,33 +319,6 @@ 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 {};

View File

@@ -19,14 +19,3 @@ 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({});
}
});
});

View File

@@ -826,18 +826,6 @@ 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) {

View File

@@ -36,7 +36,7 @@ function join(prefix, key) {
return prefix ? `${prefix}.${key}` : key;
}
const arrayOperatorKeyRegex = /^(a|[su]\d+)$/;
const arrayOperatorKeyRegex = /^(a|u\d+)$/;
function isArrayOperatorKey(field) {
return arrayOperatorKeyRegex.test(field);
@@ -96,9 +96,7 @@ function convertOplogDiff(oplogEntry, diff, prefix) {
}
const positionKey = join(join(prefix, key), position.slice(1));
if (position[0] === 's') {
convertOplogDiff(oplogEntry, value, positionKey);
} else if (value === null) {
if (value === null) {
oplogEntry.$unset ??= {};
oplogEntry.$unset[positionKey] = true;
} else {

View File

@@ -77,71 +77,6 @@ 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) {

View File

@@ -9,7 +9,7 @@
Package.describe({
summary: "Adaptor for using MongoDB and Minimongo over DDP",
version: '1.16.3'
version: '1.16.2'
});
Npm.depends({

View File

@@ -4,28 +4,13 @@ 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 = {};
REMOTE_COLLECTION_METHODS.forEach(
['find', 'findOne', 'insert', 'update', 'upsert',
'remove', '_ensureIndex', 'createIndex', '_dropIndex', '_createCappedCollection',
'dropCollection', 'rawCollection'].forEach(
function (m) {
ret[m] = _.bind(self.mongo[m], self.mongo, name);
});

View File

@@ -62,224 +62,214 @@
}
},
"@aws-sdk/abort-controller": {
"version": "3.215.0",
"resolved": "https://registry.npmjs.org/@aws-sdk/abort-controller/-/abort-controller-3.215.0.tgz",
"integrity": "sha512-HTvL542nawhVqe0oC1AJchdcomEOmPivJEzYUT1LqiG3e8ikxMNa2KWSqqLPeKi2t0A/cfQy7wDUyg9+BZhDSQ=="
"version": "3.190.0",
"resolved": "https://registry.npmjs.org/@aws-sdk/abort-controller/-/abort-controller-3.190.0.tgz",
"integrity": "sha512-M6qo2exTzEfHT5RuW7K090OgesUojhb2JyWiV4ulu7ngY4DWBUBMKUqac696sHRUZvGE5CDzSi0606DMboM+kA=="
},
"@aws-sdk/client-cognito-identity": {
"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=="
"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=="
},
"@aws-sdk/client-sso": {
"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=="
"version": "3.190.0",
"resolved": "https://registry.npmjs.org/@aws-sdk/client-sso/-/client-sso-3.190.0.tgz",
"integrity": "sha512-joEKRjJEzgvXnEih/x2UDDCPlvXWCO3MAHmqi44yJ36Ph4YsFS299mOjPdVLuzUtpQ+cST1nRO7hXNFrulW2jQ=="
},
"@aws-sdk/client-sts": {
"version": "3.218.0",
"resolved": "https://registry.npmjs.org/@aws-sdk/client-sts/-/client-sts-3.218.0.tgz",
"integrity": "sha512-0A81eHvryKFEPq7IeY34Opzh5b9bVhhLlf2fDy5VuZjCFf4R9vD2ceOANvFSJeMsmdlqVDq8U1mHYl0E6FRUug=="
"version": "3.192.0",
"resolved": "https://registry.npmjs.org/@aws-sdk/client-sts/-/client-sts-3.192.0.tgz",
"integrity": "sha512-iv72dmRxbZ1cN5jGn4KIVzzu11eduS2fXHbNgd7JsFd5hLBV5TvJaugQzUdXNmy2gN4HiRJr+qa9WkD5b39lsA=="
},
"@aws-sdk/config-resolver": {
"version": "3.215.0",
"resolved": "https://registry.npmjs.org/@aws-sdk/config-resolver/-/config-resolver-3.215.0.tgz",
"integrity": "sha512-DxX4R+YYLQOtg0qfceKBrjVD4t1mQBG1eb7IVr2QSlckFCX8ztUNymFMuaSEo3938Jyy/NpgfUDpFqPDaSKnng=="
"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=="
},
"@aws-sdk/credential-provider-cognito-identity": {
"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=="
"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=="
},
"@aws-sdk/credential-provider-env": {
"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=="
"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=="
},
"@aws-sdk/credential-provider-imds": {
"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=="
"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=="
},
"@aws-sdk/credential-provider-ini": {
"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=="
"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=="
},
"@aws-sdk/credential-provider-node": {
"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=="
"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=="
},
"@aws-sdk/credential-provider-process": {
"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=="
"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=="
},
"@aws-sdk/credential-provider-sso": {
"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=="
"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=="
},
"@aws-sdk/credential-provider-web-identity": {
"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=="
"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=="
},
"@aws-sdk/credential-providers": {
"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=="
"version": "3.192.0",
"resolved": "https://registry.npmjs.org/@aws-sdk/credential-providers/-/credential-providers-3.192.0.tgz",
"integrity": "sha512-iBTrEPkfOHlfgQyk7EeUCmZnhUKXsGcc/hhxBbc6Z/Xc7Y8LqRVLbEmHq9lruXraFuvs26xV9oZi1s1UMXneQA=="
},
"@aws-sdk/fetch-http-handler": {
"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=="
"version": "3.190.0",
"resolved": "https://registry.npmjs.org/@aws-sdk/fetch-http-handler/-/fetch-http-handler-3.190.0.tgz",
"integrity": "sha512-5riRpKydARXAPLesTZm6eP6QKJ4HJGQ3k0Tepi3nvxHVx3UddkRNoX0pLS3rvbajkykWPNC2qdfRGApWlwOYsA=="
},
"@aws-sdk/hash-node": {
"version": "3.215.0",
"resolved": "https://registry.npmjs.org/@aws-sdk/hash-node/-/hash-node-3.215.0.tgz",
"integrity": "sha512-MkSRuZvo1RCRmI0VNEmRYCGGD/DkMd9lqnLtOyglMPnSX1mhyD4/DyXmcc3rYa7PsjDRAfykGWJRiMqpoMLjiQ=="
"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=="
},
"@aws-sdk/invalid-dependency": {
"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=="
"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=="
},
"@aws-sdk/is-array-buffer": {
"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=="
"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=="
},
"@aws-sdk/middleware-content-length": {
"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=="
"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=="
},
"@aws-sdk/middleware-host-header": {
"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=="
"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=="
},
"@aws-sdk/middleware-logger": {
"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=="
"version": "3.190.0",
"resolved": "https://registry.npmjs.org/@aws-sdk/middleware-logger/-/middleware-logger-3.190.0.tgz",
"integrity": "sha512-rrfLGYSZCBtiXNrIa8pJ2uwUoUMyj6Q82E8zmduTvqKWviCr6ZKes0lttGIkWhjvhql2m4CbjG5MPBnY7RXL4A=="
},
"@aws-sdk/middleware-recursion-detection": {
"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=="
"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=="
},
"@aws-sdk/middleware-retry": {
"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=="
"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=="
},
"@aws-sdk/middleware-sdk-sts": {
"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=="
"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=="
},
"@aws-sdk/middleware-serde": {
"version": "3.215.0",
"resolved": "https://registry.npmjs.org/@aws-sdk/middleware-serde/-/middleware-serde-3.215.0.tgz",
"integrity": "sha512-+uhLXdKvvQZcRRFc3UmemSr/YUHA4Jc+1YMjHxc3v8vvfztFJBb0wgBx999myOi8PmkYThlRBQDzXy9UCIhIJw=="
"version": "3.190.0",
"resolved": "https://registry.npmjs.org/@aws-sdk/middleware-serde/-/middleware-serde-3.190.0.tgz",
"integrity": "sha512-S132hEOK4jwbtZ1bGAgSuQ0DMFG4TiD4ulAwbQRBYooC7tiWZbRiR0Pkt2hV8d7WhOHgUpg7rvqlA7/HXXBAsA=="
},
"@aws-sdk/middleware-signing": {
"version": "3.215.0",
"resolved": "https://registry.npmjs.org/@aws-sdk/middleware-signing/-/middleware-signing-3.215.0.tgz",
"integrity": "sha512-3BqzYqkmdPeOxjI8DVQE7Bm7J5QIvDy30abglXqrDg6npw6KonKI2Q3FIPFf+oLpZTMStwkoQOnwXHTPrSZ6Tg=="
"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=="
},
"@aws-sdk/middleware-stack": {
"version": "3.215.0",
"resolved": "https://registry.npmjs.org/@aws-sdk/middleware-stack/-/middleware-stack-3.215.0.tgz",
"integrity": "sha512-rdSVL7LxRgjlvoluqwODD4ypBy2k/YVl6FrDplyCMSi8m2WHZG99FzdmR9bpnWK+0DGzYZSMRYx6ynJ9N9PsSw=="
"version": "3.190.0",
"resolved": "https://registry.npmjs.org/@aws-sdk/middleware-stack/-/middleware-stack-3.190.0.tgz",
"integrity": "sha512-h1mqiWNJdi1OTSEY8QovpiHgDQEeRG818v8yShpqSYXJKEqdn54MA3Z1D2fg/Wv/8ZJsFrBCiI7waT1JUYOmCg=="
},
"@aws-sdk/middleware-user-agent": {
"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=="
"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=="
},
"@aws-sdk/node-config-provider": {
"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=="
"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=="
},
"@aws-sdk/node-http-handler": {
"version": "3.215.0",
"resolved": "https://registry.npmjs.org/@aws-sdk/node-http-handler/-/node-http-handler-3.215.0.tgz",
"integrity": "sha512-btKWSR7m0UuWIN3p5MfSIvhqeYik7xri7U6nWuVI5GVzIYjzxEZOMvPAinDLDxL5wipodi0ZvTUNdDJdm7BcGQ=="
"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=="
},
"@aws-sdk/property-provider": {
"version": "3.215.0",
"resolved": "https://registry.npmjs.org/@aws-sdk/property-provider/-/property-provider-3.215.0.tgz",
"integrity": "sha512-dDPjMCCopkRURAmOJCMSlpIQ5BGWCpYj0+FIfZ5qWQs24fn1PAkQHecOiBhJO0ZSVuQy3xcIyWsAp1NE5e+7ug=="
"version": "3.190.0",
"resolved": "https://registry.npmjs.org/@aws-sdk/property-provider/-/property-provider-3.190.0.tgz",
"integrity": "sha512-uzdKjHE2blbuceTC5zeBgZ0+Uo/hf9pH20CHpJeVNtrrtF3GALtu4Y1Gu5QQVIQBz8gjHnqANx0XhfYzorv69Q=="
},
"@aws-sdk/protocol-http": {
"version": "3.215.0",
"resolved": "https://registry.npmjs.org/@aws-sdk/protocol-http/-/protocol-http-3.215.0.tgz",
"integrity": "sha512-qp6Y6v4S534LAjadiVl9p7ErK7ImphOKq6yhFyQwxko6iITLcz8ib3yU27fs4QJcnNj5ZooqW/YlL/0EikDxCQ=="
"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=="
},
"@aws-sdk/querystring-builder": {
"version": "3.215.0",
"resolved": "https://registry.npmjs.org/@aws-sdk/querystring-builder/-/querystring-builder-3.215.0.tgz",
"integrity": "sha512-eilk8CqG37BVhQklLif00K2dOJgDzacUi8h3KVQ72ry1V3h345i4HsmaFIxvnz8XtNyDvV8qFAzeYg9n2P9RQA=="
"version": "3.190.0",
"resolved": "https://registry.npmjs.org/@aws-sdk/querystring-builder/-/querystring-builder-3.190.0.tgz",
"integrity": "sha512-w9mTKkCsaLIBC8EA4RAHrqethNGbf60CbpPzN/QM7yCV3ZZJAXkppFfjTVVOMbPaI8GUEOptJtzgqV68CRB7ow=="
},
"@aws-sdk/querystring-parser": {
"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=="
"version": "3.190.0",
"resolved": "https://registry.npmjs.org/@aws-sdk/querystring-parser/-/querystring-parser-3.190.0.tgz",
"integrity": "sha512-vCKP0s33VtS47LSYzEWRRr2aTbi3qNkUuQyIrc5LMqBfS5hsy79P1HL4Q7lCVqZB5fe61N8fKzOxDxWRCF0sXg=="
},
"@aws-sdk/service-error-classification": {
"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=="
"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=="
},
"@aws-sdk/shared-ini-file-loader": {
"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=="
"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=="
},
"@aws-sdk/signature-v4": {
"version": "3.215.0",
"resolved": "https://registry.npmjs.org/@aws-sdk/signature-v4/-/signature-v4-3.215.0.tgz",
"integrity": "sha512-Rc73uUCi3eJneO25DydLTfJYamXeuKS9YIhNMTKlpvcN1UQAmAnUbAmCuEmqvkYOiGD1i4/kd8kBga708iIikQ=="
"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=="
},
"@aws-sdk/smithy-client": {
"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=="
"version": "3.190.0",
"resolved": "https://registry.npmjs.org/@aws-sdk/smithy-client/-/smithy-client-3.190.0.tgz",
"integrity": "sha512-f5EoCwjBLXMyuN491u1NmEutbolL0cJegaJbtgK9OJw2BLuRHiBknjDF4OEVuK/WqK0kz2JLMGi9xwVPl4BKCA=="
},
"@aws-sdk/types": {
"version": "3.215.0",
"resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.215.0.tgz",
"integrity": "sha512-eRbCVjwzTYd9C5e2mceScJ6D2kYDDEC3PLkYfJa+1wH9iiF2JlbiYozAokyeYBHQ+AjmD93MK58RBoM8iZfH0Q=="
"version": "3.190.0",
"resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.190.0.tgz",
"integrity": "sha512-mkeZ+vJZzElP6OdRXvuLKWHSlDQxZP9u8BjQB9N0Rw0pCXTzYS0vzIhN1pL0uddWp5fMrIE68snto9xNR6BQuA=="
},
"@aws-sdk/url-parser": {
"version": "3.215.0",
"resolved": "https://registry.npmjs.org/@aws-sdk/url-parser/-/url-parser-3.215.0.tgz",
"integrity": "sha512-r/qIk3TUlV36JvoRjTErFm0LzzgNKLB1YUG8zVZCGAc2TEATi8OVEmsZvi+KfTmsbszulITJVcjZKbHLbGoUzg=="
"version": "3.190.0",
"resolved": "https://registry.npmjs.org/@aws-sdk/url-parser/-/url-parser-3.190.0.tgz",
"integrity": "sha512-FKFDtxA9pvHmpfWmNVK5BAVRpDgkWMz3u4Sg9UzB+WAFN6UexRypXXUZCFAo8S04FbPKfYOR3O0uVlw7kzmj9g=="
},
"@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-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-body-length-browser": {
"version": "3.188.0",
@@ -287,64 +277,59 @@
"integrity": "sha512-8VpnwFWXhnZ/iRSl9mTf+VKOX9wDE8QtN4bj9pBfxwf90H1X7E8T6NkiZD3k+HubYf2J94e7DbeHs7fuCPW5Qg=="
},
"@aws-sdk/util-body-length-node": {
"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=="
"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=="
},
"@aws-sdk/util-buffer-from": {
"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=="
"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=="
},
"@aws-sdk/util-config-provider": {
"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=="
"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=="
},
"@aws-sdk/util-defaults-mode-browser": {
"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=="
"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=="
},
"@aws-sdk/util-defaults-mode-node": {
"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=="
"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=="
},
"@aws-sdk/util-hex-encoding": {
"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=="
"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=="
},
"@aws-sdk/util-locate-window": {
"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=="
"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=="
},
"@aws-sdk/util-middleware": {
"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=="
"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=="
},
"@aws-sdk/util-uri-escape": {
"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=="
"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=="
},
"@aws-sdk/util-user-agent-browser": {
"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=="
"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=="
},
"@aws-sdk/util-user-agent-node": {
"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=="
"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=="
},
"@aws-sdk/util-utf8-browser": {
"version": "3.188.0",
@@ -352,14 +337,14 @@
"integrity": "sha512-jt627x0+jE+Ydr9NwkFstg3cUvgWh56qdaqAMDsqgRlKD21md/6G226z/Qxl7lb1VEW2LlmCx43ai/37Qwcj2Q=="
},
"@aws-sdk/util-utf8-node": {
"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=="
"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=="
},
"@types/node": {
"version": "18.11.9",
"resolved": "https://registry.npmjs.org/@types/node/-/node-18.11.9.tgz",
"integrity": "sha512-CRpX21/kGdzjOpFsZSkcrXMGIBWMGNIHXXBVFSH+ggkftxg+XYP20TESbh+zFvFj3EQOl5byk0HTRn1IL6hbqg=="
"version": "18.11.2",
"resolved": "https://registry.npmjs.org/@types/node/-/node-18.11.2.tgz",
"integrity": "sha512-BWN3M23gLO2jVG8g/XHIRFWiiV4/GckeFIqbU/C4V3xpoBBWSMk4OZomouN0wCkfQFPqgZikyLr7DOYDysIkkw=="
},
"@types/webidl-conversions": {
"version": "7.0.0",
@@ -391,6 +376,11 @@
"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",
@@ -412,14 +402,14 @@
"integrity": "sha512-ZS4Bp4r/Zoeq6+NLJpP+0Zzm0pR8whtGPf1XExKLJBAczGMnSi3It14OiNCStjQjM6NU1okjQGSxgEZN8eBYKg=="
},
"mongodb": {
"version": "4.12.1",
"resolved": "https://registry.npmjs.org/mongodb/-/mongodb-4.12.1.tgz",
"integrity": "sha512-koT87tecZmxPKtxRQD8hCKfn+ockEL2xBiUvx3isQGI6mFmagWt4f4AyCE9J4sKepnLhMacoCTQQA6SLAI2L6w=="
"version": "4.11.0",
"resolved": "https://registry.npmjs.org/mongodb/-/mongodb-4.11.0.tgz",
"integrity": "sha512-9l9n4Nk2BYZzljW3vHah3Z0rfS5npKw6ktnkmFgTcnzaXH1DRm3pDl6VMHu84EVb1lzmSaJC4OzWZqTkB5i2wg=="
},
"mongodb-connection-string-url": {
"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=="
"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=="
},
"punycode": {
"version": "2.1.1",
@@ -457,9 +447,9 @@
"integrity": "sha512-l7FvfAHlcmulp8kr+flpQZmVwtu7nfRV7NZujtN0OqES8EL4O4e0qqzL0DC5gAvx/ZC/9lk6rhcUwYvkBnBnYA=="
},
"tslib": {
"version": "2.4.1",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.1.tgz",
"integrity": "sha512-tGyy4dAjRIEwI7BzsB0lynWgOpfqjUdq91XXAlIWD2OwKBH7oCl/GZG/HT4BOHrTlPMOASlMQ7veyTqpmRcrNA=="
"version": "2.4.0",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.0.tgz",
"integrity": "sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ=="
},
"uuid": {
"version": "8.3.2",

View File

@@ -3,12 +3,12 @@
Package.describe({
summary: "Wrapper around the mongo npm package",
version: '4.12.1',
version: '4.11.0',
documentation: null
});
Npm.depends({
mongodb: "4.12.1"
mongodb: "4.11.0"
});
Package.onUse(function (api) {

View File

@@ -136,7 +136,7 @@ OAuth._checkRedirectUrlOrigin = redirectUrl => {
);
};
const middleware = async (req, res, next) => {
const middleware = (req, res, next) => {
let requestData;
// Make sure to catch any exceptions because otherwise we'd crash
@@ -168,7 +168,7 @@ const middleware = async (req, res, next) => {
requestData = req.body;
}
await handler(service, requestData, res);
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,31 +473,3 @@ 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);
};

View File

@@ -1,6 +1,6 @@
Package.describe({
summary: "Common code for OAuth-based services",
version: "2.1.3"
version: "2.1.2"
});
Package.onUse(api => {
@@ -11,7 +11,6 @@ Package.onUse(api => {
api.use(['reload', 'base64'], 'client');
api.use('oauth-encryption', 'server', {weak: true});
api.use('fetch', 'server');
api.export('OAuth');

View File

@@ -19,12 +19,12 @@ export class OAuth1Binding {
this._urls = urls;
}
async prepareRequestToken(callbackUrl) {
prepareRequestToken(callbackUrl) {
const headers = this._buildHeader({
oauth_callback: callbackUrl
});
const response = await this._call({method: 'POST', url: this._urls.requestToken, headers});
const response = this._call('POST', 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;
}
async prepareAccessToken(query, requestTokenSecret) {
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 = await this._call({ method: 'POST', url: this._urls.accessToken, headers });
const response = this._call('POST', 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;
}
async callAsync(method, url, params, callback) {
call(method, url, params, callback) {
const headers = this._buildHeader({
oauth_token: this.accessToken
});
@@ -75,29 +75,14 @@ export class OAuth1Binding {
params = {};
}
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));
return this._call(method, url, headers, 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);
}
@@ -133,7 +118,7 @@ export class OAuth1Binding {
return crypto.createHmac('SHA1', signingKey).update(signatureBase).digest('base64');
};
async _call({method, url, headers = {}, params = {}, callback}) {
_call(method, url, headers = {}, params = {}, callback) {
// all URLs to be functions to support parameters/customization
if(typeof url === "function") {
url = url(this);
@@ -156,52 +141,29 @@ export class OAuth1Binding {
// Make a authorization string according to oauth1 spec
const authString = this._getAuthHeaderString(headers);
// Make signed request
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);
try {
const response = HTTP.call(method, url, {
params,
headers: {
Authorization: authString
}
return response;
})
.catch((err) => {
if (callback) {
callback(err);
}, callback && ((error, response) => {
if (! error) {
response.nonce = headers.oauth_nonce;
}
console.log({ err });
throw Object.assign(
new Error(`Failed to send OAuth1 request to ${url}. ${err.message}`),
{ response: err.response }
);
});
}
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});
}
};
_encodeHeader(header) {
return Object.keys(header).reduce((memo, key) => {

View File

@@ -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'] = async (service, query, res) => {
OAuth._requestHandlers['1'] = (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'] = async (service, query, res) => {
});
// Get a request token to start auth process
await oauthBinding.prepareRequestToken(callbackUrl);
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'] = async (service, query, res) => {
// subsequent call to the `login` method will be immediate.
// Get the access token for signing requests
await oauthBinding.prepareAccessToken(query, requestTokenInfo.requestTokenSecret);
oauthBinding.prepareAccessToken(query, requestTokenInfo.requestTokenSecret);
// Run service-specific handler.
const oauthResult = await service.handleOauthRequest(
const oauthResult = service.handleOauthRequest(
oauthBinding, { query: query });
const credentialToken = OAuth._credentialTokenFromQuery(query);

View File

@@ -1,7 +1,7 @@
import http from 'http';
import { OAuth1Binding } from './oauth1_binding';
const testPendingCredential = async (test, method) => {
const testPendingCredential = (test, method) => {
const twitterfooId = Random.id();
const twitterfooName = `nickname${Random.id()}`;
const twitterfooAccessToken = Random.id();
@@ -17,8 +17,8 @@ const testPendingCredential = async (test, method) => {
authenticate: "https://example.com/oauth/authenticate"
};
OAuth1Binding.prototype.prepareRequestToken = async () => {};
OAuth1Binding.prototype.prepareAccessToken = async function() {
OAuth1Binding.prototype.prepareRequestToken = () => {};
OAuth1Binding.prototype.prepareAccessToken = function() {
this.accessToken = twitterfooAccessToken;
this.accessTokenSecret = twitterfooAccessTokenSecret;
};
@@ -27,7 +27,7 @@ const testPendingCredential = async (test, method) => {
try {
// register a fake login service
OAuth.registerService(serviceName, 1, urls, async query => ({
OAuth.registerService(serviceName, 1, urls, query => ({
serviceData: {
id: twitterfooId,
screenName: twitterfooName,
@@ -71,7 +71,7 @@ const testPendingCredential = async (test, method) => {
respData += args[0];
return end.apply(this, arguments);
};
await OAuthTest.middleware(req, res);
OAuthTest.middleware(req, res);
const credentialSecret = respData;
// Test that the result for the token is available
@@ -94,17 +94,17 @@ const testPendingCredential = async (test, method) => {
}
};
Tinytest.addAsync("oauth1 - pendingCredential is stored and can be retrieved (without oauth encryption)", async test => {
Tinytest.add("oauth1 - pendingCredential is stored and can be retrieved (without oauth encryption)", test => {
OAuthEncryption.loadKey(null);
await testPendingCredential(test, "GET");
await testPendingCredential(test, "POST");
testPendingCredential(test, "GET");
testPendingCredential(test, "POST");
});
Tinytest.addAsync("oauth1 - pendingCredential is stored and can be retrieved (with oauth encryption)", async test => {
Tinytest.add("oauth1 - pendingCredential is stored and can be retrieved (with oauth encryption)", test => {
try {
OAuthEncryption.loadKey(Buffer.from([1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16]).toString("base64"));
await testPendingCredential(test, "GET");
await testPendingCredential(test, "POST");
testPendingCredential(test, "GET");
testPendingCredential(test, "POST");
} finally {
OAuthEncryption.loadKey(null);
}

View File

@@ -1,6 +1,6 @@
Package.describe({
summary: "Common code for OAuth1-based login services",
version: "1.5.1",
version: "1.5.0",
});
Package.onUse(api => {
@@ -8,7 +8,10 @@ Package.onUse(api => {
api.use('random');
api.use('service-configuration', ['client', 'server']);
api.use('oauth', ['client', 'server']);
api.use('check', 'server');
api.use([
'check',
'http@1.4.4 || 2.0.0'
], 'server');
api.use('mongo');

View File

@@ -1,5 +1,5 @@
// connect middleware
OAuth._requestHandlers['2'] = async (service, query, res) => {
OAuth._requestHandlers['2'] = (service, query, res) => {
let credentialSecret;
// check if user authorized access
@@ -7,7 +7,7 @@ OAuth._requestHandlers['2'] = async (service, query, res) => {
// Prepare the login results before returning.
// Run service-specific handler.
const oauthResult = await service.handleOauthRequest(query);
const oauthResult = service.handleOauthRequest(query);
credentialSecret = Random.secret();
const credentialToken = OAuth._credentialTokenFromQuery(query);

View File

@@ -1,6 +1,6 @@
import http from 'http';
const testPendingCredential = async function (test, method) {
const testPendingCredential = function (test, method) {
const foobookId = Random.id();
const foobookOption1 = Random.id();
const credentialToken = Random.id();
@@ -51,7 +51,7 @@ const testPendingCredential = async function (test, method) {
return end.apply(this, args);
};
await OAuthTest.middleware(req, res);
OAuthTest.middleware(req, res);
const credentialSecret = respData;
// Test that the result for the token is available
@@ -72,17 +72,17 @@ const testPendingCredential = async function (test, method) {
}
};
Tinytest.addAsync("oauth2 - pendingCredential is stored and can be retrieved (without oauth encryption)", async test => {
Tinytest.add("oauth2 - pendingCredential is stored and can be retrieved (without oauth encryption)", test => {
OAuthEncryption.loadKey(null);
await testPendingCredential(test, "GET");
await testPendingCredential(test, "POST");
testPendingCredential(test, "GET");
testPendingCredential(test, "POST");
});
Tinytest.addAsync("oauth2 - pendingCredential is stored and can be retrieved (with oauth encryption)", async test => {
Tinytest.add("oauth2 - pendingCredential is stored and can be retrieved (with oauth encryption)", test => {
try {
OAuthEncryption.loadKey(Buffer.from([1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16]).toString("base64"));
await testPendingCredential(test, "GET");
await testPendingCredential(test, "POST");
testPendingCredential(test, "GET");
testPendingCredential(test, "POST");
} finally {
OAuthEncryption.loadKey(null);
}

View File

@@ -1,6 +1,6 @@
Package.describe({
summary: "Common code for OAuth2-based login services",
version: "1.3.2",
version: "1.3.1",
});
Package.onUse(api => {

View File

@@ -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"];
invalidVersions.forEach(function (v) {
_.each(invalidVersions, 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"];
validVersions.forEach(function (v) {
_.each(validVersions, function (v) {
test.isFalse(PackageVersion.invalidFirstFormatConstraint(v));
});
});

View File

@@ -1,6 +1,6 @@
Package.describe({
summary: "Parses Meteor Smart Package version strings",
version: "3.2.1"
version: "3.2.0"
});
Npm.depends({
@@ -14,6 +14,7 @@ Package.onUse(function (api) {
});
Package.onTest(function (api) {
api.use(['package-version-parser', 'tinytest']);
api.use('package-version-parser');
api.use(['tinytest', 'underscore']);
api.addFiles('package-version-parser-tests.js', 'server');
});

View File

@@ -1,6 +1,6 @@
Package.describe({
name: "promise",
version: "0.12.2",
version: "0.12.1",
summary: "ECMAScript 2015 Promise polyfill with Fiber support",
git: "https://github.com/meteor/promise",
documentation: "README.md"

View File

@@ -1,13 +1,11 @@
require("./extensions.js");
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")
);
}
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({

View File

@@ -1,6 +1,6 @@
Package.describe({
name: 'standard-minifier-css',
version: '1.8.3',
version: '1.8.2',
summary: 'Standard css minifier used with Meteor apps by default.',
documentation: 'README.md'
});

View File

@@ -60,7 +60,7 @@ class CssToolsMinifier {
path: 'merged-stylesheets.css'
}];
} else {
const minifiedFiles = await CssTools.minifyCssAsync(merged.code);
const minifiedFiles = CssTools.minifyCss(merged.code);
result = minifiedFiles.map(minified => ({
data: minified

View File

@@ -142,13 +142,8 @@ testAsyncMulti = function (name, funcs, { isOnly = false } = {}) {
test.extraDetails.asyncBlock = i++;
new Promise(resolve => {
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(() => {
resolve(func.apply(context, [test, _.bind(em.expect, em)]));
}).then(result => {
em.done();
}, exception => {
if (em.cancel()) {
@@ -196,24 +191,3 @@ 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;
};

View File

@@ -1,6 +1,6 @@
Package.describe({
summary: "Utility functions for tests",
version: '1.3.1'
version: '1.3.0'
});
Package.onUse(function (api) {
@@ -28,8 +28,7 @@ Package.onUse(function (api) {
'SeededRandom', 'clickElement', 'blurElement',
'focusElement', 'simulateEvent', 'getStyleProperty', 'canonicalizeHtml',
'renderToDiv', 'clickIt',
'withCallbackLogger', 'testAsyncMulti',
'simplePoll', 'runAndThrowIfNeeded',
'withCallbackLogger', 'testAsyncMulti', 'simplePoll',
'makeTestConnection', 'DomUtils']);
api.addFiles('try_all_permutations.js');

View File

@@ -451,7 +451,7 @@ Template.test.helpers({
eventsArray: function() {
var events = this.events.filter(function(e) {
return e.type != "finish";
return e[type] != "finish";
});
var partitionBy = function(seq, func) {
@@ -583,4 +583,4 @@ Template.event.helpers({
is_debuggable: function() {
return !!this.cookie;
}
});
});

View File

@@ -1,6 +1,6 @@
Package.describe({
summary: "Run tests interactively in the browser",
version: '1.3.2',
version: '1.3.1',
documentation: null
});

View File

@@ -1,16 +1,12 @@
const puppeteer = require('../../dev_bundle/lib/node_modules/puppeteer');
let testNumber = 0;
async function runNextUrl(browser) {
const page = await browser.newPage();
page.on('console', msg => {
// this is a way to make sure the travis does not timeout
// if the test is running for too long without any output to the console (10 minutes)
if (msg._text !== undefined) console.log(msg._text);
else console.log(`Test number: ${ testNumber }`);
testNumber++;
if (msg._text !== undefined) {
console.log(msg._text);
}
});
if (!process.env.URL) {
@@ -23,15 +19,11 @@ async function runNextUrl(browser) {
async function poll() {
if (await isDone(page)) {
let failCount = await getFailCount(page);
console.log(`
The number of tests from Test number may be different because
of the way the test is written. causing the test to fail or
to run more than once. in the console. Test number total: ${ testNumber }`);
console.log(`Tests complete with ${ failCount } failures`);
console.log(`Tests complete with ${ await getPassCount(page) } passes`);
console.log(`Tests complete with ${failCount} failures`);
console.log(`Tests complete with ${await getPassCount(page)} passes`);
if (failCount > 0) {
const failed = await getFailed(page);
failed.map((f) => console.log(`${ f.name } failed: ${ f.info }`));
failed.map( (f) => console.log(`${f.name} failed: ${f.info}`));
await page.close();
await browser.close();
process.exit(1);
@@ -54,7 +46,7 @@ async function runNextUrl(browser) {
* @return {Promise<boolean>}
*/
async function isDone(page) {
return await page.evaluate(function () {
return await page.evaluate(function() {
if (typeof TEST_STATUS !== 'undefined') {
return TEST_STATUS.DONE;
}
@@ -69,7 +61,7 @@ async function isDone(page) {
* @return {Promise<number>}
*/
async function getPassCount(page) {
return await page.evaluate(function () {
return await page.evaluate(function() {
if (typeof TEST_STATUS !== 'undefined') {
return TEST_STATUS.PASSED;
}
@@ -84,7 +76,7 @@ async function getPassCount(page) {
* @return {Promise<number>}
*/
async function getFailCount(page) {
return await page.evaluate(function () {
return await page.evaluate(function() {
if (typeof TEST_STATUS !== 'undefined') {
return TEST_STATUS.FAILURES;
}
@@ -103,7 +95,7 @@ async function getFailCount(page) {
* @return {Promise<[{name: string, info: string}]>}
*/
async function getFailed(page) {
return await page.evaluate(function () {
return await page.evaluate(function() {
if (typeof TEST_STATUS !== 'undefined') {
return TEST_STATUS.WHERE_FAILED;
}
@@ -112,11 +104,11 @@ async function getFailed(page) {
}
async function runTests() {
console.log(`Running test with Puppeteer at ${ process.env.URL }`);
console.log(`Running test with Puppeteer at ${process.env.URL}`);
// --no-sandbox and --disable-setuid-sandbox must be disabled for CI compatibility
const browser = await puppeteer.launch({ args: ['--no-sandbox', '--disable-setuid-sandbox'] });
console.log(`Using version: ${ await browser.version() }`);
console.log(`Using version: ${await browser.version()}`);
runNextUrl(browser);
}

View File

@@ -1,6 +1,6 @@
Package.describe({
summary: "Tiny testing framework",
version: '1.2.2'
version: '1.2.1'
});
Package.onUse(function (api) {

View File

@@ -1,3 +1,5 @@
const Future = Meteor.isServer && require('fibers/future');
/******************************************************************************/
/* TestCaseResults */
/******************************************************************************/
@@ -184,43 +186,6 @@ export class TestCaseResults {
this.ok();
}
_assertActual(actual, predicate, message) {
if (actual && predicate(actual))
this.ok();
else
this.fail({
type: "throws",
message: (actual ?
"wrong error thrown: " + actual.message :
"did not throw an error as expected") + (message ? ": " + message : ""),
});
}
_guessPredicate(expected) {
let predicate;
if (expected === undefined) {
predicate = function () {
return true;
};
} else if (typeof expected === "string") {
predicate = function (actual) {
return typeof actual.message === "string" &&
actual.message.indexOf(expected) !== -1;
};
} else if (expected instanceof RegExp) {
predicate = function (actual) {
return expected.test(actual.message);
};
} else if (typeof expected === 'function') {
predicate = expected;
} else {
throw new Error('expected should be a string, regexp, or predicate function');
}
return predicate;
}
// expected can be:
// undefined: accept any exception.
// string: pass if the string is a substring of the exception message.
@@ -239,8 +204,26 @@ export class TestCaseResults {
// particular class, use a predicate function.
//
throws(f, expected, message) {
let actual;
const predicate = this._guessPredicate(expected);
var actual, predicate;
if (expected === undefined) {
predicate = function (actual) {
return true;
};
} else if (typeof expected === "string") {
predicate = function (actual) {
return typeof actual.message === "string" &&
actual.message.indexOf(expected) !== -1;
};
} else if (expected instanceof RegExp) {
predicate = function (actual) {
return expected.test(actual.message);
};
} else if (typeof expected === 'function') {
predicate = expected;
} else {
throw new Error('expected should be a string, regexp, or predicate function');
}
try {
f();
@@ -248,27 +231,15 @@ export class TestCaseResults {
actual = exception;
}
this._assertActual(actual, predicate, message);
}
/**
* Same as throw, but accepts an async function as a parameter.
* @param f
* @param expected
* @param message
* @returns {Promise<void>}
*/
async throwsAsync(f, expected, message) {
let actual;
const predicate = this._guessPredicate(expected);
try {
await f();
} catch (exception) {
actual = exception;
}
this._assertActual(actual, predicate, message);
if (actual && predicate(actual))
this.ok();
else
this.fail({
type: "throws",
message: (actual ?
"wrong error thrown: " + actual.message :
"did not throw an error as expected") + (message ? ": " + message : ""),
});
}
isTrue(v, msg) {
@@ -338,7 +309,7 @@ export class TestCaseResults {
pass = true;
}
} else {
/* fail -- not something that contains other things */
/* fail -- not something that contains other things */;
}
if (pass === ! not) {
@@ -575,37 +546,37 @@ export class TestRun {
}
if (Meteor.isServer) {
// On the server, ensure that only one test runs at a time, even
// with multiple clients.
this.manager.testQueue.queueTask(() => {
// On the server, ensure that only one test runs at a time, even
// with multiple clients.
let hasRan = false;
const timeoutPromise = new Promise((resolve) => {
Meteor.setTimeout(() => {
if (!hasRan) {
test.timedOut = true;
this._report(test, {
type: "exception",
details: {
message: "test timed out"
}
});
}
resolve();
}, 3 * 60 * 1000);
});
const runnerPromise = new Promise((resolve) => {
this._runTest(test, () => {
if (!hasRan) {
hasRan = true;
}
resolve();
}, stop_at_offset);
});
Promise.race([runnerPromise, timeoutPromise]).finally(() => {
onComplete && onComplete();
});
// The future resolves when the test completes or times out.
var future = new Future();
Meteor.setTimeout(
() => {
if (future.isResolved())
// If the future has resolved the test has completed.
return;
test.timedOut = true;
this._report(test, {
type: "exception",
details: {
message: "test timed out"
}
});
future['return']();
},
3 * 60 * 1000 // 3 minutes
);
this._runTest(test, () => {
// The test can complete after it has timed out (it might
// just be slow), so only resolve the future if the test
// hasn't timed out.
if (! future.isResolved())
future['return']();
}, stop_at_offset);
// Wait for the test to complete or time out.
future.wait();
onComplete && onComplete();
});
} else {
// client

View File

@@ -9,7 +9,7 @@ import {
export { Tinytest };
const Fiber = Meteor._isFibersEnabled && require('fibers');
const Fiber = require('fibers');
const handlesForRun = new Map;
const reportsForRun = new Map;
@@ -58,7 +58,7 @@ Meteor.methods({
}
function onReport(report) {
if (Fiber && !Fiber.current) {
if (! Fiber.current) {
Meteor._debug("Trying to report a test not in a fiber! "+
"You probably forgot to wrap a callback in bindEnvironment.");
console.trace();

View File

@@ -1,6 +1,6 @@
Package.describe({
summary: "Twitter OAuth flow",
version: '1.3.2'
version: '1.3.1'
});
Package.onUse(function(api) {

View File

@@ -15,9 +15,9 @@ var urls = {
// https://dev.twitter.com/docs/api/1.1/get/account/verify_credentials
Twitter.whitelistedFields = ['profile_image_url', 'profile_image_url_https', 'lang', 'email'];
OAuth.registerService('twitter', 1, urls, async function(oauthBinding) {
const response = await oauthBinding.getAsync('https://api.twitter.com/1.1/account/verify_credentials.json?include_email=true');
const { data: identity } = response;
OAuth.registerService('twitter', 1, urls, function(oauthBinding) {
var identity = oauthBinding.get('https://api.twitter.com/1.1/account/verify_credentials.json?include_email=true').data;
var serviceData = {
id: identity.id_str,
screenName: identity.screen_name,

View File

@@ -1,6 +1,6 @@
Package.describe({
name: 'typescript',
version: '4.6.4',
version: '4.5.4',
summary:
'Compiler plugin that compiles TypeScript and ECMAScript in .ts and .tsx files',
documentation: 'README.md',

View File

@@ -1,12 +1,13 @@
Package.describe({
summary: "Weibo OAuth flow",
version: "1.3.2",
version: "1.3.1",
});
Package.onUse(api => {
api.use('oauth1', ['client', 'server']);
api.use('oauth', ['client', 'server']);
api.use('random', 'client');
api.use('http@1.4.4 || 2.0.0', 'server');
api.use(['service-configuration', 'ecmascript'], ['client', 'server']);
api.addFiles('weibo_client.js', 'client');

View File

@@ -1,8 +1,8 @@
Weibo = {};
OAuth.registerService('weibo', 2, null, async query => {
OAuth.registerService('weibo', 2, null, query => {
const response = await getTokenResponse(query);
const response = getTokenResponse(query);
const uid = parseInt(response.uid, 10);
// different parts of weibo's api seem to expect numbers, or strings
@@ -11,7 +11,7 @@ OAuth.registerService('weibo', 2, null, async query => {
throw new Error(`Expected 'uid' to parse to an integer: ${JSON.stringify(response)}`);
}
const identity = await getIdentity(response.access_token, uid);
const identity = getIdentity(response.access_token, uid);
return {
serviceData: {
@@ -31,48 +31,46 @@ OAuth.registerService('weibo', 2, null, async query => {
// - uid
// - access_token
// - expires_in: lifetime of this token in seconds (5 years(!) right now)
const getTokenResponse = async (query) => {
const config = ServiceConfiguration.configurations.findOne({
service: 'weibo',
});
if (!config) throw new ServiceConfiguration.ConfigError();
const getTokenResponse = query => {
const config = ServiceConfiguration.configurations.findOne({service: 'weibo'});
if (!config)
throw new ServiceConfiguration.ConfigError();
return OAuth._fetch('https://api.weibo.com/oauth2/access_token', 'POST', {
queryParams: {
code: query.code,
client_id: config.clientId,
client_secret: OAuth.openSecret(config.secret),
redirect_uri: OAuth._redirectUri('weibo', config, null, {
replaceLocalhost: true,
}),
grant_type: 'authorization_code',
},
})
.then((res) => res.json())
.catch((err) => {
throw Object.assign(
new Error(
`Failed to complete OAuth handshake with Weibo. ${err.message}`
),
{ response: err.response }
);
});
let response;
try {
response = HTTP.post(
"https://api.weibo.com/oauth2/access_token", {params: {
code: query.code,
client_id: config.clientId,
client_secret: OAuth.openSecret(config.secret),
redirect_uri: OAuth._redirectUri('weibo', config, null, {replaceLocalhost: true}),
grant_type: 'authorization_code'
}});
} catch (err) {
throw Object.assign(new Error(`Failed to complete OAuth handshake with Weibo. ${err.message}`),
{response: err.response});
}
// result.headers["content-type"] is 'text/plain;charset=UTF-8', so
// the http package doesn't automatically populate result.data
response.data = JSON.parse(response.content);
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 Weibo. ${response.data.error}`);
} else {
return response.data;
}
};
const getIdentity = async (accessToken, userId) => {
return OAuth._fetch('https://api.weibo.com/2/users/show.json', 'GET', {
queryParams: {
access_token: accessToken,
uid: userId,
},
})
.then((res) => res.json())
.catch((err) => {
throw Object.assign(
new Error('Failed to fetch identity from Weibo. ' + err.message),
{ response: err.response }
);
});
const getIdentity = (accessToken, userId) => {
try {
return HTTP.get(
"https://api.weibo.com/2/users/show.json",
{params: {access_token: accessToken, uid: userId}}).data;
} catch (err) {
throw Object.assign(new Error("Failed to fetch identity from Weibo. " + err.message),
{response: err.response});
}
};
Weibo.retrieveCredential = (credentialToken, credentialSecret) =>

View File

@@ -1,6 +1,6 @@
{
"track": "METEOR",
"version": "2.9.0",
"version": "2.8.1-rc.0",
"recommended": false,
"official": false,
"description": "Meteor experimental release"

View File

@@ -1,6 +1,6 @@
{
"track": "METEOR",
"version": "2.9.0",
"version": "2.8.2",
"recommended": false,
"official": true,
"description": "The Official Meteor Distribution"

View File

@@ -15,7 +15,7 @@ var packageJson = {
"node-gyp": "8.0.0",
"node-pre-gyp": "0.15.0",
typescript: "4.5.4",
"@meteorjs/babel": "7.17.2-beta.0",
"@meteorjs/babel": "7.16.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",

View File

@@ -883,7 +883,7 @@ main.registerCommand({
relConf.packages = {};
var toPublish = [];
Console.info(`Will publish new version for MeteorJS: ${relConf.version}`);
main.captureAndExit("=> Errors in release packages:", function () {
_.each(allPackages, function (packageName) {
buildmessage.enterJob("checking consistency of " + packageName, function () {

View File

@@ -12,13 +12,6 @@ var archinfo = require('../utils/archinfo');
var catalog = require('../packaging/catalog/catalog.js');
var stats = require('../meteor-services/stats.js');
var Console = require('../console/console.js').Console;
const {
blue,
green,
purple,
red,
yellow
} = require('../console/console.js').colors;
var projectContextModule = require('../project-context.js');
var release = require('../packaging/release.js');
@@ -540,14 +533,12 @@ main.registerCommand({
blaze: { type: Boolean },
react: { type: Boolean },
vue: { type: Boolean },
'vue-2': { type: Boolean },
typescript: { type: Boolean },
apollo: { type: Boolean },
svelte: { type: Boolean },
tailwind: { type: Boolean },
'chakra-ui': { type: Boolean },
solid: { type: Boolean },
prototype: { type: Boolean }
},
catalogRefresh: new catalog.Refresh.Never()
}, function (options) {
@@ -556,13 +547,7 @@ main.registerCommand({
// latest release to create a package if we are inside an app)
if (options.package) {
var packageName = options.args[0];
if (options.prototype) {
Console.error(
`The ${Console.command('--prototype')} option is no longer supported for packages.`
);
Console.error();
throw new main.ShowUsage;
}
if (options.list || options.example) {
Console.error("No package examples exist at this time.");
Console.error();
@@ -805,22 +790,6 @@ main.registerCommand({
return transform(f);
},
transformContents: function (contents, f) {
// check if this app is just for prototyping if it is then we need to add autopublish and insecure in the packages file
if ((/packages/).test(f)) {
const prototypePackages =
() =>
'autopublish # Publish all data to the clients (for prototyping)\n' +
'insecure # Allow all DB writes from clients (for prototyping)';
// XXX: if there is the need to add more options maybe we should have a better abstraction for this if-else
if (options.prototype) {
return Buffer.from(contents.toString().replace(/~prototype~/g, prototypePackages()))
} else {
return Buffer.from(contents.toString().replace(/~prototype~/g, ''))
}
}
if ((/(\.html|\.[jt]sx?|\.css)/).test(f)) {
return Buffer.from(transform(contents.toString()));
} else {
@@ -936,8 +905,7 @@ main.registerCommand({
cmd("meteor create --minimal # to create an app with as few Meteor packages as possible");
cmd("meteor create --full # to create a more complete scaffolded app");
cmd("meteor create --react # to create a basic React-based app");
cmd("meteor create --vue # to create a basic Vue3-based app");
cmd("meteor create --vue-2 # to create a basic Vue2-based app");
cmd("meteor create --vue # to create a basic Vue-based app");
cmd("meteor create --apollo # to create a basic Apollo + React app");
cmd("meteor create --svelte # to create a basic Svelte app");
cmd("meteor create --typescript # to create an app using TypeScript and React");
@@ -2540,298 +2508,6 @@ main.registerCommand({
});
///////////////////////////////////////////////////////////////////////////////
// generate
///////////////////////////////////////////////////////////////////////////////
/**
*
* @param question
* @returns {function(string): Promise<string>}
*/
const createPrompt = () => {
const readline = require('readline')
.createInterface({ input: process.stdin, output: process.stdout });
return async (question) => new Promise((resolve, reject) => {
readline.question(question, (answer) => {
resolve(answer);
})
})
}
const sanitizeBoolAnswer = (string) => {
if (string === '') return true;
if (string.toLowerCase() === 'y' || string.toLowerCase() === 'yes') return true;
if (string.toLowerCase() === 'n' || string.toLowerCase() === 'no' ) return false;
Console.error(red('You must provide a valid answer'));
Console.error(yellow('it should be either (y)es or (n)o or just press enter to accept the default value'));
throw new main.ExitWithCode(2);
}
/**
* simple verification for the name
* @param scaffoldName {string}
*/
const checkScaffoldName = (scaffoldName) => {
if (scaffoldName === '') {
Console.error(red('You must provide a name for your model.'));
Console.error(yellow('Model names should not be empty.'));
throw new main.ExitWithCode(2);
}
if (scaffoldName.includes('/')) {
Console.error(red('You must provide a valid name for your model.'));
Console.error(yellow('Model names should not contain slashes.'));
throw new main.ExitWithCode(2);
}
const allNonWordRegex = /[^a-zA-Z0-9_-]/g; // all numbers and letters plus _ and -
if (allNonWordRegex.test(scaffoldName)) {
Console.error(red('You must provide a valid name for your model.'));
Console.error(yellow('Model names should not contain special characters except _ and -'));
throw new main.ExitWithCode(2);
}
}
main.registerCommand({
name: 'generate',
maxArgs: 1,
minArgs: 0,
options: {
path: { type: String },
methods: { type: Boolean },
publications: { type: Boolean },
templatePath : { type: String },
replaceFn : { type: String },
},
pretty: false,
catalogRefresh: new catalog.Refresh.Never()
}, async function (options) {
const { args, appDir } = options;
const setup = async (arg0) => {
if (arg0 === undefined) {
const ask = createPrompt();
// the ANSI color chart is here: https://en.wikipedia.org/wiki/ANSI_escape_code#Colors
const scaffoldName = await ask(`What is the name of your ${yellow('model')}? `);
checkScaffoldName(scaffoldName);
const areMethods = await ask(`There will be methods [${green('Y')}/${red('n')}]? press enter for ${green('yes')} `);
const methods = sanitizeBoolAnswer(areMethods);
const arePublications = await ask(`There will be publications [${green('Y')}/${red('n')}]? press enter for ${green('yes')} `);
const publications = sanitizeBoolAnswer(arePublications);
const path = await ask(`Where it will be placed? press enter for ${yellow('./imports/api/')} `);
return {
isWizard: true,
scaffoldName,
path,
methods,
publications,
}
}
const {
path,
methods,
publications
} = options;
return {
isWizard: false,
scaffoldName: arg0,
path,
methods,
publications,
}
}
/**
* @type{string}
*/
const {
isWizard,
scaffoldName,
path,
methods,
publications
} = await setup(args[0]);
checkScaffoldName(scaffoldName);
// get directory where we will place our files
const scaffoldPath = path ||`${ appDir }/imports/api/${ scaffoldName }`;
/**
*
* @param appDir
* @returns {string[]}
*/
const getFilesInDir = (appDir) => {
const appPath = files.pathResolve(appDir);
return files.readdir(appPath);
}
const getExtension = () => {
const rootFiles = getFilesInDir(appDir);
if (rootFiles.includes('tsconfig.json')) return 'ts'
else return 'js'
}
/**
*
* @returns {string}
*/
const userTransformFilenameFn = (filename) => {
const path = files.pathResolve(files.pathJoin(appDir, options.replaceFn));
const replaceFn = require(path).transformFilename;
if (typeof replaceFn !== 'function') {
Console.error(red('You must provide a valid function transformFilename.'));
Console.error(yellow('The function should be named transformFilename and should be exported.'));
throw new main.ExitWithCode(2);
}
return replaceFn(scaffoldName, filename);
}
/**
*
* @returns {string}
*/
const userTransformContentsFn = (contents, fileName) => {
const path = files.pathResolve(files.pathJoin(appDir, options.replaceFn));
const replaceFn = require(path).transformContents;
if (typeof replaceFn !== 'function') {
Console.error(red('You must provide a valid function transformContents.'));
Console.error(yellow('The function should be named transformContents and should be exported.'));
throw new main.ExitWithCode(2);
}
return replaceFn(scaffoldName, contents, fileName);
}
/**
* if contains - turns into pascal
* @param str{string}
* @returns {string}
*/
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('');
}
/**
*
* @param name {string}
*/
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);
})
}
/**
*
* @param content{string}
* @param fileName{string}
* @returns {string}
*/
const removeUnusedLines = (content, fileName) => {
if (methods && publications) return content;
if (!methods && !publications) return content;
if(!fileName.startsWith('index')) return content;
return content
.split('\n')
.filter(line => {
if (!methods && line.includes('methods')) return false;
if (!publications && line.includes('publications')) return false;
return true;
})
.join('\n');
}
/// Program
const rootFiles = getFilesInDir(appDir);
if (!rootFiles.includes('.meteor')) {
Console.error(red('You must be in a Meteor project to run this command'));
Console.error(yellow('You can create a new Meteor project with `meteor create`'));
throw new main.ExitWithCode(2);
}
const extension = getExtension()
const assetsPath = () => {
if (options.templatePath){
const templatePath = files.pathJoin(appDir, options.templatePath)
Console.info(`Using template that is in: ${purple(templatePath)}`)
return templatePath;
}
return files.pathJoin(
__dirnameConverted,
'..',
'static-assets',
`scaffolds-${ extension }`)
}
// create directory
const isOk = files.mkdir_p(scaffoldPath);
if (!isOk) {
Console.error(red('Something went wrong when creating the folder'));
Console.error(yellow('Do you have the correct permissions?'));
throw new main.ExitWithCode(2);
}
files.cp_r(assetsPath(), files.pathResolve(scaffoldPath), {
transformFilename: function (f) {
if (options.replaceFn) return userTransformFilenameFn(f);
return transformName(f);
},
transformContents: function (contents, fileName) {
if (options.replaceFn) return userTransformContentsFn(contents.toString(), fileName);
const cleaned = removeUnusedLines(contents.toString(), fileName);
return transformName(cleaned);
}
})
const checkAndRemoveFiles = () => {
if (!methods)
files.unlink(files.pathJoin(scaffoldPath, `methods.${ extension }`));
if (!publications)
files.unlink(files.pathJoin(scaffoldPath, `publications.${ extension }`));
}
const xor = (a, b) => ( a || b ) && !( a && b );
if (!isWizard && xor(methods, publications)) {
checkAndRemoveFiles()
}
if (isWizard) {
checkAndRemoveFiles()
}
const packageJsonPath = files.pathJoin(appDir, 'package.json');
const packageJsonFile = files.readFile(packageJsonPath, 'utf8');
const packageJson = JSON.parse(packageJsonFile);
const mainJsPath =
packageJson?.meteor?.mainModule?.server
? files.pathJoin(appDir, packageJson.meteor.mainModule.server)
: files.pathJoin(appDir, 'server', 'main.js');
const mainJs = files.readFile(mainJsPath);
const mainJsLines = mainJs.toString().split('\n');
const importLine = path
? `import '${path}';`
: `import '/imports/api/${ scaffoldName }';`
const mainJsFile = [importLine, ...mainJsLines].join('\n');
files.writeFile(mainJsPath, mainJsFile);
Console.info(`Created ${ blue(scaffoldName) } scaffold in ${ yellow(scaffoldPath) }`);
return 0;
});
///////////////////////////////////////////////////////////////////////////////
// admin get-machine
///////////////////////////////////////////////////////////////////////////////

Some files were not shown because too many files have changed in this diff Show More