mirror of
https://github.com/meteor/meteor.git
synced 2026-05-02 03:01:46 -04:00
Merge branch 'release-3.0' into release-3.0-tools
# Conflicts: # packages/babel-compiler/.npm/package/npm-shrinkwrap.json # packages/ddp-client/common/livedata_connection.js # packages/ddp-server/writefence.js # tools/cli/commands-packages.js # tools/meteor-services/service-connection.js # tools/static-assets/server/boot.js
This commit is contained in:
@@ -116,7 +116,8 @@ set_fibers_env: &set_fibers_env
|
||||
matrix_for_fibers: &matrix_for_fibers
|
||||
matrix:
|
||||
parameters:
|
||||
fibers: [true, false]
|
||||
# If we want to run with Fibers and without, just append false here.
|
||||
fibers: [true]
|
||||
|
||||
|
||||
jobs:
|
||||
|
||||
4
.github/workflows/labeler.yml
vendored
4
.github/workflows/labeler.yml
vendored
@@ -9,6 +9,10 @@ name: Labeler
|
||||
on:
|
||||
- pull_request_target
|
||||
|
||||
permissions:
|
||||
contents: read # to determine modified files (actions/labeler)
|
||||
pull-requests: write # to add labels to PRs (actions/labeler)
|
||||
|
||||
jobs:
|
||||
label:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
@@ -6,6 +6,10 @@ on:
|
||||
pull_request:
|
||||
paths:
|
||||
- "npm-packages/eslint-plugin-meteor/**"
|
||||
|
||||
permissions:
|
||||
contents: read # to fetch code (actions/checkout)
|
||||
|
||||
jobs:
|
||||
test:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
4
.github/workflows/npm-meteor-babel.yml
vendored
4
.github/workflows/npm-meteor-babel.yml
vendored
@@ -6,6 +6,10 @@ on:
|
||||
pull_request:
|
||||
paths:
|
||||
- "npm-packages/meteor-babel/**"
|
||||
|
||||
permissions:
|
||||
contents: read # to fetch code (actions/checkout)
|
||||
|
||||
jobs:
|
||||
test:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
4
.github/workflows/npm-meteor-promise.yml
vendored
4
.github/workflows/npm-meteor-promise.yml
vendored
@@ -6,6 +6,10 @@ on:
|
||||
pull_request:
|
||||
paths:
|
||||
- "npm-packages/meteor-promise/**"
|
||||
|
||||
permissions:
|
||||
contents: read # to fetch code (actions/checkout)
|
||||
|
||||
jobs:
|
||||
test:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
@@ -15,7 +15,8 @@ env:
|
||||
- phantom=false
|
||||
- PUPPETEER_DOWNLOAD_PATH=~/.npm/chromium
|
||||
jobs:
|
||||
- DISABLE_FIBERS=1
|
||||
# We don't want to run the tests without fibers anymore.
|
||||
# - DISABLE_FIBERS=1
|
||||
# Use a different flag, since node would use false as a string.
|
||||
- FIBERS_ENABLED=1
|
||||
addons:
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
title: Meteor API Docs
|
||||
subtitle: API Docs
|
||||
versions:
|
||||
- '2.9'
|
||||
- '2.8'
|
||||
- '2.7'
|
||||
- '2.6'
|
||||
@@ -86,6 +87,7 @@ sidebar_categories:
|
||||
Command Line:
|
||||
- commandline
|
||||
- environment-variables
|
||||
- using-core-types
|
||||
Troubleshooting:
|
||||
- expired-certificate
|
||||
- windows
|
||||
|
||||
318
docs/history.md
318
docs/history.md
@@ -1,16 +1,259 @@
|
||||
## 2.8.1, Unreleased
|
||||
## v2.9, 2022-12-12
|
||||
|
||||
#### Highlights
|
||||
### Highlights
|
||||
|
||||
* TypeScript update to v4.6.4 [PR](https://github.com/meteor/meteor/pull/12204) by [@StorytellerCZ](https://github.com/StorytellerCZ).
|
||||
* Create Email.sendAsync method without using Fibers [PR](https://github.com/meteor/meteor/pull/12101)
|
||||
by [edimarlnx](https://github.com/edimarlnx).
|
||||
* Create async method CssTools.minifyCssAsync [PR](https://github.com/meteor/meteor/pull/12105)
|
||||
by [edimarlnx](https://github.com/edimarlnx).
|
||||
* Change Accounts and Oauth to use Async methods [PR](https://github.com/meteor/meteor/pull/12156)
|
||||
by [edimarlnx](https://github.com/edimarlnx).
|
||||
* TinyTest package without Future [PR](https://github.com/meteor/meteor/pull/12222)
|
||||
by [matheusccastroo](https://github.com/matheusccastroo).
|
||||
* Feat: user accounts base async [PR](https://github.com/meteor/meteor/pull/12274)
|
||||
by [Grubba27](https://github.com/Grubba27).
|
||||
* Move somed methods from OAuth of out of accounts-base [PR](https://github.com/meteor/meteor/pull/12202)
|
||||
by [StorytellerCZ](https://github.com/StorytellerCZ).
|
||||
* Feat: not using insecure & autopublish [PR](https://github.com/meteor/meteor/pull/12220)
|
||||
by [Grubba27](https://github.com/Grubba27).
|
||||
* Don't apply babel async-await plugin when not running on Fibers [PR](https://github.com/meteor/meteor/pull/12221).
|
||||
by [matheusccastroo](https://github.com/matheusccastroo).
|
||||
* Implemented Fibers-less MongoDB count methods [PR](https://github.com/meteor/meteor/pull/12295)
|
||||
by [radekmie](https://github.com/radekmie).
|
||||
* Feat: Generate scaffold in cli [PR](https://github.com/meteor/meteor/pull/12298)
|
||||
by [Grubba27](https://github.com/Grubba27).
|
||||
* Update types [PR](https://github.com/meteor/meteor/pull/12306) by [piotrpospiech](https://github.com/piotrpospiech).
|
||||
* Remove underscore from package-version-parser [PR](https://github.com/meteor/meteor/pull/12248)
|
||||
by [harryadel](https://github.com/harryadel).
|
||||
* Update MongoDB driver version [PR](https://github.com/meteor/meteor/pull/12333) by [Grubba27](https://github.com/Grubba27).
|
||||
* New Vue3 Skeleton [PR](https://github.com/meteor/meteor/pull/12302)
|
||||
by [henriquealbert](https://github.com/henriquealbert).
|
||||
|
||||
#### Breaking Changes
|
||||
|
||||
* OAuth related code has been moved from `accounts-base` to `accounts-oauth`, removing the dependency on `service-configuration`
|
||||
more can be seen in this [discussion](https://github.com/meteor/meteor/discussions/12171) and in the [PR](https://github.com/meteor/meteor/pull/12202).
|
||||
|
||||
#### Migration Steps
|
||||
|
||||
You can follow in [here](https://guide.meteor.com/2.9-migration.html).
|
||||
|
||||
#### Meteor Version Release
|
||||
|
||||
* `eslint-plugin-meteor@7.4.0`:
|
||||
- updated Typescript deps and meteor babel.
|
||||
* `eslint-plugin-meteor@7.4.0`:
|
||||
- updated Typescript deps and meteor babel.
|
||||
* `accounts-base@2.2.6`
|
||||
- Moved some functions to accounts-oauth.
|
||||
* `accounts-oauth@1.4.2`
|
||||
- Received functions from accounts-base.
|
||||
* `accounts-password@2.3.2`
|
||||
- Asyncfied functions such as `changePassword`, `forgotPassword`, `resetPassword`, `verifyEmail`, `setPasswordAsync`.
|
||||
* `babel-compiler@7.10.1`
|
||||
- Updated babel to 7.17.1.
|
||||
* `email@2.2.3`
|
||||
- Create Email.sendAsync method without using Fibers.
|
||||
* `facebook-oauth@1.11.2`
|
||||
- Updated facebook-oauth to use async functions.
|
||||
* `github-oauth@1.4.1`
|
||||
- Updated github-oauth to use async functions.
|
||||
* `google-oauth@1.4.3`
|
||||
- Updated google-oauth to use async functions.
|
||||
* `meetup-oauth@1.1.2`
|
||||
- Updated meetup-oauth to use async functions.
|
||||
* `meteor-developer-oauth@1.3.2`
|
||||
- Updated meteor-developer-oauth to use async functions.
|
||||
* `meteor@1.10.3`
|
||||
- Added Async Local Storage helpers.
|
||||
* `minifier-css@1.6.2`
|
||||
- Asyncfied `minifyCss` function.
|
||||
* `minimongo@1.9.1`
|
||||
- Implemented Fibers-less MongoDB count methods.
|
||||
* `mongo@1.16.2`
|
||||
- Implemented Fibers-less MongoDB count methods.
|
||||
* `npm-mongo@4.12.1`
|
||||
- Updated npm-mongo to 4.12.
|
||||
* `oauth@2.1.3`
|
||||
- Asyncfied methods.
|
||||
* `oauth1@1.5.1`
|
||||
- Asyncfied methods.
|
||||
* `oauth2@1.3.2`
|
||||
- Asyncfied methods.
|
||||
* `package-version-parser@3.2.1`
|
||||
- Removed underscore.
|
||||
* `promise@0.12.2`
|
||||
- Added DISABLE_FIBERS flag.
|
||||
* `standard-minifier-css@1.8.3`
|
||||
- Asyncfied minify method.
|
||||
* `test-helpers@1.3.1`
|
||||
- added runAndThrowIfNeeded function.
|
||||
* `test-in-browser@1.3.2`
|
||||
- Adjusted e[type] to e.type
|
||||
* `tinytest@1.2.2`
|
||||
- TinyTest package without Future.
|
||||
* `twitter-oauth@1.3.2`
|
||||
- Asyncfied methods.
|
||||
* `typescript@4.6.4`
|
||||
- updated typescript to 4.6.4.
|
||||
* `weibo-oauth@1.3.2`
|
||||
- Asyncfied methods.
|
||||
|
||||
#### Special thanks to
|
||||
- [@henriquealbert](https://github.com/henriquealbert);
|
||||
- [@edimarlnx](https://github.com/edimarlnx);
|
||||
- [@matheusccastroo](https://github.com/matheusccastroo);
|
||||
- [@Grubba27](https://github.com/Grubba27);
|
||||
- [@StorytellerCZ](https://github.com/StorytellerCZ);
|
||||
- [@radekmie](https://github.com/radekmie);
|
||||
- [@piotrpospiech](https://github.com/piotrpospiech);
|
||||
- [@harryadel](https://github.com/harryadel);
|
||||
|
||||
For making this great framework even better!
|
||||
|
||||
|
||||
## v2.8.2, 2022-11-29
|
||||
|
||||
#### Highlights
|
||||
* `mongo@1.16.2`:
|
||||
- Make count NOT create a cursor. [PR](https://github.com/meteor/meteor/pull/12326).
|
||||
* `meteorjs/babel@7.16.1-beta.0`
|
||||
- Adjusted config to Auto import React on jsx,tsx files [PR](https://github.com/meteor/meteor/pull/12327).
|
||||
- needs to use directly from npm the meteorjs/babel@7.16.1-beta.0.
|
||||
|
||||
#### Breaking Changes
|
||||
N/A
|
||||
|
||||
#### Migration Steps
|
||||
|
||||
#### Meteor Version Release
|
||||
* `mongo@1.16.2`:
|
||||
- Make count NOT create a cursor. [PR](https://github.com/meteor/meteor/pull/12326).
|
||||
|
||||
#### Special thanks to
|
||||
- [@henriquealbert](https://github.com/henriquealbert);
|
||||
- [@znewsham](https://github.com/znewsham);
|
||||
|
||||
For making this great framework even better!
|
||||
|
||||
|
||||
|
||||
## v2.8.1, 2022-11-14
|
||||
|
||||
#### Highlights
|
||||
|
||||
- modernize tools/run-updater.js by [afrokick](https://github.com/afrokick)
|
||||
- feat(error message): Especifing error message when cross-boundary by [Grubba27](https://github.com/Grubba27)
|
||||
- Type definitions for core packages by [piotrpospiech](https://github.com/piotrpospiech)
|
||||
- Add https proxy support to meteor-installer by [heschong](https://github.com/heschong)
|
||||
- Fix case insensitive lookup resource overuse by [ToyboxZach](https://github.com/ToyboxZach)
|
||||
- Update default Facebook API to v15 and fix local changelog by [StorytellerCZ](https://github.com/StorytellerCZ)
|
||||
- Bump to Node v14.21.1 by [StorytellerCZ](https://github.com/StorytellerCZ)
|
||||
- Use true mongo binary types by [znewsham](https://github.com/znewsham)
|
||||
- Add docs for Accounts.registerLoginHandler by [shivam1646](https://github.com/shivam1646)
|
||||
- Updated MongoDB driver to 4.11 by [radekmie](https://github.com/radekmie)
|
||||
- Show port in restart message by [harryadel](https://github.com/harryadel)
|
||||
- In the client, don't wait if the stub doesn't return a promise by [denihs](https://github.com/denihs)
|
||||
- The rest of type definitions for core packages by [piotrpospiech](https://github.com/piotrpospiech)
|
||||
- Removing underscore in packages by [harryadel](https://github.com/harryadel):
|
||||
- [twitter-oauth] Remove underscore
|
||||
- [test-in-browser] Remove underscore
|
||||
- [webapp-hashing] Remove underscore
|
||||
- [browser-policy] Remove underscore
|
||||
- [ecmascript] Remove underscore
|
||||
- [browser-policy-framing] Remove underscore
|
||||
- [diff-sequence] Remove underscore
|
||||
- [facts-ui] Remove underscore
|
||||
- [geojson-utils] Remove underscore
|
||||
|
||||
#### Breaking Changes
|
||||
|
||||
N/A
|
||||
|
||||
#### Migration Steps
|
||||
|
||||
_In case you want types in your app using the core packages types/zodern:types (now you do have the option)_
|
||||
|
||||
1. Remove `@types/meteor` package
|
||||
2. Install [`zodern:types`](https://github.com/zodern/meteor-types) package
|
||||
3. Follow [installation guide for the Meteor Apps](https://github.com/zodern/meteor-types#meteor-apps) to update
|
||||
|
||||
#### Meteor Version Release
|
||||
|
||||
* `accounts-base@2.2.5`
|
||||
- added types for package.
|
||||
* `browser-policy@1.1.1`
|
||||
- adjusted package tests.
|
||||
* `browser-policy-common@1.0.12`
|
||||
- added types for package.
|
||||
* `browser-policy-framing@1.1.1`
|
||||
- removed underscore.
|
||||
* `check@1.3.2`
|
||||
- added types for package.
|
||||
* `ddp@1.4.0`
|
||||
- added types for package.
|
||||
* `ddp-client@2.6.1`
|
||||
- In the client, don't wait if the stub doesn't return a promise.
|
||||
* `ddp-rate-limiter@1.1.1`
|
||||
- added types for package.
|
||||
* `diff-sequence@1.1.2`
|
||||
- removed underscore.
|
||||
* `ecmascript@0.16.3`
|
||||
- removed underscore.
|
||||
* `ejson@1.1.3`
|
||||
- added types for package.
|
||||
* `ejson@2.2.2`
|
||||
- added types for package.
|
||||
* `facebook-oauth@1.12.0`
|
||||
- Updated default version of Facebook GraphAPI to v15
|
||||
|
||||
|
||||
* `facts-ui@1.0.1`
|
||||
- removed underscore.
|
||||
* `fetch@0.1.2`
|
||||
- added types for package.
|
||||
* `geojson-utils@1.0.11`
|
||||
- removed underscore.
|
||||
* `hot-module-replacement@0.5.2`
|
||||
- added types for package.
|
||||
* `meteor@1.10.2`
|
||||
- added types for package.
|
||||
* `modern-browsers@0.1.9`
|
||||
- added types for package.
|
||||
* `modules-runtime@0.13.2`
|
||||
- added accurate error messages.
|
||||
* `modules-runtime-hot@0.14.1`
|
||||
- added accurate error messages.
|
||||
* `mongo@1.16.1`
|
||||
- added types for package.
|
||||
- added true mongo binary
|
||||
* `npm-mongo@4.11.0`
|
||||
- updated npm mongo version to match npm one.
|
||||
* `promise@0.13.0`
|
||||
- added types for package.
|
||||
* `random@1.2.1`
|
||||
- added types for package.
|
||||
* `reactive-dict@1.3.1`
|
||||
- added types for package.
|
||||
* `reactive-dict@1.0.12`
|
||||
- added types for package.
|
||||
* `server-render@0.4.1`
|
||||
- added types for package.
|
||||
* `service-configuration@1.3.1`
|
||||
- added types for package.
|
||||
* `session@1.2.1`
|
||||
- added types for package.
|
||||
* `test-in-browser@1.3.1`
|
||||
- removed underscore.
|
||||
* `tracker@1.2.1`
|
||||
- added types for package.
|
||||
* `twitter-oauth@1.3.1`
|
||||
- removed underscore.
|
||||
* `underscore@1.0.11`
|
||||
- added types for package.
|
||||
* `webapp@1.13.2`
|
||||
- added types for package.
|
||||
* `webapp-hashing@1.1.1`
|
||||
- added types for package.
|
||||
## v2.8, 2022-10-19
|
||||
|
||||
#### Highlights
|
||||
@@ -53,7 +296,16 @@ Read our [Migration Guide](https://guide.meteor.com/2.8-migration.html) for this
|
||||
- Validates required Node.js version. [PR](https://github.com/meteor/meteor/pull/12066).
|
||||
* `npm-mongo@4.9.0`:
|
||||
- Updated MongoDB driver to 4.9. [PR](https://github.com/meteor/meteor/pull/12163).
|
||||
|
||||
* `@meteorjs/babel@7.17.0`
|
||||
- Upgrade TypeScript to `4.6.4`
|
||||
* `babel-compiler@7.10.0`
|
||||
- Upgrade TypeScript to `4.6.4`
|
||||
* `ecmascript@0.16.3`
|
||||
- Upgrade TypeScript to `4.6.4`
|
||||
* `typescript@4.6.4`
|
||||
- Upgrade TypeScript to `4.6.4`
|
||||
* `eslint-plugin-meteor@7.4.0`
|
||||
- Upgrade TypeScript to `4.6.4`
|
||||
|
||||
#### Independent Releases
|
||||
* `accounts-passwordless@2.1.3`:
|
||||
@@ -683,7 +935,7 @@ This version should be ignored. Proceed to 2.5.5 above.
|
||||
* Typescript updated to [v4.3.5](https://github.com/Microsoft/TypeScript/releases/tag/v4.3.5)
|
||||
* Email package now allows setting `Email.customTransport` to override sending method.
|
||||
* Use `createIndex` instead of `_ensureIndex` to align with new MongoDB naming.
|
||||
* Apollo skeleton has been upgraded for [Apollo server v3](https://github.com/apollographql/apollo-server/blob/main/CHANGELOG.md#v300)
|
||||
* Apollo skeleton has been upgraded for [Apollo server v3](https://github.com/apollographql/apollo-server/blob/main/CHANGELOG_historical.md#v300)
|
||||
* `reify` has been updated to v0.22.2 which reduces the overhead of `import` statements and some uses of `export ... from`, especially when a module is imported a large number of times or re-exports a large number of exports from other modules. PRs [1](https://github.com/benjamn/reify/pull/246), [2](https://github.com/benjamn/reify/pull/291)
|
||||
* Meteor NPM installer is [now available for all platforms](https://github.com/meteor/meteor/pull/11590).
|
||||
* DDP server now allows you to set publication strategies for your publications to control mergebox behavior
|
||||
@@ -1053,7 +1305,7 @@ This version should be ignored. Proceed to 2.5.5 above.
|
||||
- The undocumented environment variable `DDP_DEFAULT_CONNECTION_URL` behavior has changed. Setting `DDP_DEFAULT_CONNECTION_URL` when running the server (development: `meteor run` or production: `node main.js`) sets the default DDP server value for meteor. But this did not work for `cordova` apps. Now you can define the `cordova` app default DDP server value by setting `DDP_DEFAULT_CONNECTION_URL` when building (`meteor build`).
|
||||
- Skeletons dependencies updated to latest version
|
||||
- Svelte skeleton now has HMR
|
||||
- New deploy option: `--build-only`. Helpful if you want to build first and after some validations proceeding with the upload and deploy. [Read more](https://cloud-guide.meteor.com/deploy-guide.html#cache-only)
|
||||
- New deploy option: `--build-only`. Helpful if you want to build first and after some validations proceeding with the upload and deploy. [Read more](https://galaxy-guide.meteor.com/deploy-command-line.html#cache-only)
|
||||
- Improved watched system to properly rebuild `client` even when a file is outside of `client` or `imports` folders. See [PR](https://github.com/meteor/meteor/pull/11474) for details.
|
||||
- Fix an issue when `App.appendToConfig` crashed Cordova build.
|
||||
- Reify compiler now uses cache in runtime. [Read more](https://github.com/meteor/meteor/pull/11400)
|
||||
@@ -1541,7 +1793,7 @@ N/A
|
||||
|
||||
* `meteor create --vue` is now available thanks to [@chris-visser](https://github.com/chris-visser). PR [#11086](https://github.com/meteor/meteor/pull/11086)
|
||||
|
||||
* `--cache-build` option is now available on `meteor deploy` command and you can use it safely all the time if you are using a Git repository to run your deploy. This is helpful if your upload is failing then you can retry just the upload and also if you deploy the same bundle to multiple environments. [Read more](https://cloud-guide.meteor.com/deploy-guide.html#cache-build).
|
||||
* `--cache-build` option is now available on `meteor deploy` command and you can use it safely all the time if you are using a Git repository to run your deploy. This is helpful if your upload is failing then you can retry just the upload and also if you deploy the same bundle to multiple environments. [Read more](https://galaxy-guide.meteor.com/deploy-command-line.html#cache-build)
|
||||
|
||||
* Multiple optimizations in build performance, many of them for Windows thanks to [@zodern](https://github.com/zodern). PRs [#10838](https://github.com/meteor/meteor/pull/10838), [#11114](https://github.com/meteor/meteor/pull/11114), [#11115](https://github.com/meteor/meteor/pull/11115), [#11102](https://github.com/meteor/meteor/pull/11102), [#10839](https://github.com/meteor/meteor/pull/10839)
|
||||
|
||||
@@ -3923,9 +4175,9 @@ N/A
|
||||
|
||||
> Note: With this version of Reify, `import` declarations are compiled to
|
||||
`module.watch(require(id), ...)` instead of `module.importSync(id, ...)`
|
||||
or the older `module.import(id, ...)`. The behavior of the compiled code
|
||||
should be the same as before, but the details seemed different enough to
|
||||
warrant a note.
|
||||
> or the older `module.import(id, ...)`. The behavior of the compiled code
|
||||
> should be the same as before, but the details seemed different enough to
|
||||
> warrant a note.
|
||||
|
||||
* The `install` npm package has been upgraded to version 0.10.1.
|
||||
|
||||
@@ -4269,15 +4521,15 @@ https://github.com/meteor/meteor/commit/0cbd25111d1249a61ca7adce23fad5215408c821
|
||||
are once again constrained by the current Meteor release.
|
||||
|
||||
> Before Meteor 1.4, the current release dictated the exact version of
|
||||
every installed core package, which meant newer core packages could not
|
||||
be installed without publishing a new Meteor release. In order to
|
||||
support incremental development of core packages, Meteor 1.4 removed all
|
||||
release-based constraints on core package versions
|
||||
> every installed core package, which meant newer core packages could not
|
||||
> be installed without publishing a new Meteor release. In order to
|
||||
> support incremental development of core packages, Meteor 1.4 removed all
|
||||
> release-based constraints on core package versions
|
||||
([#7084](https://github.com/meteor/meteor/pull/7084)). Now, in Meteor
|
||||
1.4.3, core package versions must remain patch-compatible with the
|
||||
versions they had when the Meteor release was published. This middle
|
||||
ground restores meaning to Meteor releases, yet still permits patch
|
||||
updates to core packages.
|
||||
> 1.4.3, core package versions must remain patch-compatible with the
|
||||
> versions they had when the Meteor release was published. This middle
|
||||
> ground restores meaning to Meteor releases, yet still permits patch
|
||||
> updates to core packages.
|
||||
|
||||
* The `cordova-lib` npm package has been updated to 6.4.0, along with
|
||||
cordova-android (6.1.1) and cordova-ios (4.3.0), and various plugins.
|
||||
@@ -4367,11 +4619,11 @@ updates to core packages.
|
||||
change was deemed too significant for this release.
|
||||
|
||||
> Note: The decision to revert the above change was made late in the
|
||||
Meteor 1.4.2.4 release process, before it was ever recommended but too
|
||||
late in the process to avoid the additional increment of the version number.
|
||||
See [#8311](https://github.com/meteor/meteor/pull/8311) for additional
|
||||
information. This change will still be released in an upcoming version
|
||||
of Meteor with a more seamless upgrade.
|
||||
> Meteor 1.4.2.4 release process, before it was ever recommended but too
|
||||
> late in the process to avoid the additional increment of the version number.
|
||||
> See [#8311](https://github.com/meteor/meteor/pull/8311) for additional
|
||||
> information. This change will still be released in an upcoming version
|
||||
> of Meteor with a more seamless upgrade.
|
||||
|
||||
## v1.4.2.4, 2017-02-02
|
||||
|
||||
@@ -4380,7 +4632,7 @@ of Meteor with a more seamless upgrade.
|
||||
* The `npm` npm package has been upgraded from version 3.10.9 to 4.1.2.
|
||||
|
||||
> Note: This change was later deemed too substantial for a point release
|
||||
and was reverted in Meteor 1.4.2.7.
|
||||
> and was reverted in Meteor 1.4.2.7.
|
||||
|
||||
* Fix for [Issue #8136](https://github.com/meteor/meteor/issues/8136).
|
||||
|
||||
@@ -4407,9 +4659,9 @@ and was reverted in Meteor 1.4.2.7.
|
||||
|
||||
> Note: Meteor 1.4.2.2 was finalized before
|
||||
[#8045](https://github.com/meteor/meteor/pull/8045) was merged, but
|
||||
those changes were [deemed important
|
||||
> those changes were [deemed important
|
||||
enough](https://github.com/meteor/meteor/pull/8044#issuecomment-260913739)
|
||||
to skip recommending 1.4.2.2 and instead immediately release 1.4.2.3.
|
||||
> to skip recommending 1.4.2.2 and instead immediately release 1.4.2.3.
|
||||
|
||||
## v1.4.2.2, 2016-11-15
|
||||
|
||||
@@ -4496,10 +4748,10 @@ to skip recommending 1.4.2.2 and instead immediately release 1.4.2.3.
|
||||
See https://github.com/meteor/meteor/pull/7668 for more details.
|
||||
|
||||
> Note: the `METEOR_PROFILE` environment variable now provides data for
|
||||
server startup time as well as build time, which should make it easier
|
||||
to tell which of your packages are responsible for slow startup times.
|
||||
Please include the output of `METEOR_PROFILE=10 meteor run` with any
|
||||
GitHub issue about rebuild performance.
|
||||
> server startup time as well as build time, which should make it easier
|
||||
> to tell which of your packages are responsible for slow startup times.
|
||||
> Please include the output of `METEOR_PROFILE=10 meteor run` with any
|
||||
> GitHub issue about rebuild performance.
|
||||
|
||||
* `npm` has been upgraded to version 3.10.9.
|
||||
|
||||
@@ -5076,8 +5328,8 @@ GitHub issue about rebuild performance.
|
||||
## v1.3.2.4, 2016-04-20
|
||||
|
||||
> Meteor 1.3.2.4 was published because publishing 1.3.2.3 failed in an
|
||||
unrecoverable way. Meteor 1.3.2.4 contains no additional changes beyond
|
||||
the changes in 1.3.2.3.
|
||||
> unrecoverable way. Meteor 1.3.2.4 contains no additional changes beyond
|
||||
> the changes in 1.3.2.3.
|
||||
|
||||
## v1.3.2.3, 2016-04-20
|
||||
|
||||
|
||||
@@ -129,7 +129,19 @@ Create a basic [Blaze](https://blazejs.org/) app.
|
||||
|
||||
`--vue`
|
||||
|
||||
Create a basic vue-based app. See the [Vue guide](https://guide.meteor.com/vue.html)
|
||||
Create a basic [Vue 3](https://vuejs.org/) app.
|
||||
|
||||
`--react`
|
||||
|
||||
Create a basic react app. See the section on [React tutorial](https://guide.meteor.com/react.html#react-tutorial)
|
||||
for more information. This is the default.
|
||||
|
||||
`--angular`
|
||||
for more information.
|
||||
|
||||
`--vue-2`
|
||||
|
||||
Create a basic vue2-based app. See the [Vue guide](https://vue-tutorial.meteor.com/)
|
||||
for more information.
|
||||
|
||||
`--svelte`
|
||||
@@ -146,43 +158,350 @@ Create a basic [React](https://reactjs.org) + [Chakra-UI](https://chakra-ui.com/
|
||||
|
||||
`--solid`
|
||||
|
||||
Create a basic [solid](https://www.solidjs.com/) app.
|
||||
Create a basic [Solid](https://www.solidjs.com/) app.
|
||||
|
||||
**Packages**
|
||||
|
||||
| | Default (`--react`) | `--bare` | `--full` | `--minimal` | `--blaze` | `--apollo` | `--vue` | `--svelte` | `--tailwind` | `--chakra-ui` | `--solid` |
|
||||
|------------------------------------------------------------------------------------------------------|:-------------------:|:--------:|:--------:|:-----------:|:---------:|:----------:|:-------:|:----------:|:------------:|:-------------:|:---------:|
|
||||
| [autopublish](https://atmospherejs.com/meteor/autopublish) | X | | | | X | | | X | X | X | X |
|
||||
| [akryum:vue-component](https://atmospherejs.com/akryum/vue-component) | | | | | | | X | | | | |
|
||||
| [apollo](https://atmospherejs.com/meteor/apollo) | | | | | | X | | | | | |
|
||||
| [blaze-html-templates](https://atmospherejs.com/meteor/blaze-html-templates) | | | X | | X | | | | | | |
|
||||
| [ecmascript](https://atmospherejs.com/meteor/ecmascript) | X | X | X | X | X | X | X | X | X | X | X |
|
||||
| [es5-shim](https://atmospherejs.com/meteor/es5-shim) | X | X | X | X | X | X | X | X | X | X | X |
|
||||
| [hot-module-replacement](https://atmospherejs.com/meteor/hot-module-replacement) | X | | | | X | X | | | X | X | X |
|
||||
| [insecure](https://atmospherejs.com/meteor/insecure) | X | | | | X | | | X | X | X | X |
|
||||
| [johanbrook:publication-collector](https://atmospherejs.com/meteor/johanbrook/publication-collector) | | | X | | | X | | | | | |
|
||||
| [jquery](https://atmospherejs.com/meteor/jquery) | | | X | | X | | | | | | |
|
||||
| [ostrio:flow-router-extra](https://atmospherejs.com/meteor/ostrio/flow-router-extra) | | | X | | | | | | | | |
|
||||
| [less](https://atmospherejs.com/meteor/less) | | | X | | | | | | | | |
|
||||
| [meteor](https://atmospherejs.com/meteor/meteor) | | | | X | | | | | | | |
|
||||
| [meteor-base](https://atmospherejs.com/meteor/meteor-base) | X | X | X | | X | X | X | X | X | X | X |
|
||||
| [mobile-experience](https://atmospherejs.com/meteor/mobile-experience) | X | X | X | | X | X | X | X | X | X | X |
|
||||
| [mongo](https://atmospherejs.com/meteor/mongo) | X | X | X | | X | X | X | X | X | X | X |
|
||||
| [meteortesting:mocha](https://atmospherejs.com/meteortesting/mocha) | | | X | | | | X | | | | |
|
||||
| [reactive-var](https://atmospherejs.com/meteor/reactive-var) | X | X | X | | X | X | X | X | X | X | X |
|
||||
| [rdb:svelte-meteor-data](https://atmospherejs.com/rdb/svelte-meteor-data) | | | | | | | | X | | | |
|
||||
| [server-render](https://atmospherejs.com/meteor/server-render) | | | | X | | X | X | | | | |
|
||||
| [shell-server](https://atmospherejs.com/meteor/shell-server) | | X | | X | X | X | X | X | X | X | X |
|
||||
| [standard-minifier-css](https://atmospherejs.com/meteor/standard-minifier-css) | X | X | X | X | X | X | X | X | X | X | X |
|
||||
| [standard-minifier-js](https://atmospherejs.com/meteor/standard-minifier-js) | X | X | X | X | X | X | X | X | X | X | X |
|
||||
| [static-html](https://atmospherejs.com/meteor/static-html) | | X | | X | | X | X | X | | | |
|
||||
| [svelte:compiler](https://atmospherejs.com/svelte/compiler) | | | | | | | | X | | | |
|
||||
| [swydo:graphql](https://atmospherejs.com/swydo/graphql) | | | | | | X | | | | | |
|
||||
| [tailwindcss](https://tailwindcss.com) | | X | X | | X | | X | | X | | |
|
||||
| [tracker](https://atmospherejs.com/meteor/tracker) | | X | X | | X | | X | | | | |
|
||||
| [typescript](https://atmospherejs.com/meteor/typescript) | X | X | X | X | X | X | X | X | X | X | X |
|
||||
| [webapp](https://atmospherejs.com/meteor/webapp) | | | | X | | | | | | | |
|
||||
| [react-meteor-data](https://atmospherejs.com/meteor/react-meteor-data) | X | | | | | | | | X | X | |
|
||||
| | Default (`--react`) | `--bare` | `--full` | `--minimal` | `--blaze` | `--apollo` | `--vue-2` | `--svelte` | `--tailwind` | `--chakra-ui` | `--solid` | `--vue` |
|
||||
|------------------------------------------------------------------------------------------------------|:-------------------:|:--------:|:--------:|:-----------:|:---------:|:----------:|:---------:|:----------:|:------------:|:-------------:|:---------:|:-------:|
|
||||
| [autopublish](https://atmospherejs.com/meteor/autopublish) | X | | | | X | | | X | X | X | 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``
|
||||

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

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

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

|
||||
|
||||
> Note that this is not a CLI framework inside meteor but just giving some solutions for really common problems out of the box.
|
||||
> Check out Yargs, Inquirer or Commander for more information about CLI frameworks.
|
||||
|
||||
|
||||
You can use your own templates for scaffolding your specific workloads. To do that, you should pass in a template directory URL so that it can copy it with its changes.
|
||||
|
||||
<h3 id="meteorgenerate-template-rename"> How to rename things?</h3>
|
||||
|
||||
Out of the box is provided a few functions such as replacing ``$$name$$``, ``$$PascalName$$`` and ``$$camelName$$``
|
||||
|
||||
these replacements come from this function:
|
||||
|
||||
_Note that scaffoldName is the name that you have passed as argument_
|
||||
|
||||
```js
|
||||
const transformName = (name) => {
|
||||
return name.replace(/\$\$name\$\$|\$\$PascalName\$\$|\$\$camelName\$\$/g, function (substring, args) {
|
||||
if (substring === '$$name$$') return scaffoldName;
|
||||
if (substring === '$$PascalName$$') return toPascalCase(scaffoldName);
|
||||
if (substring === '$$camelName$$') return toCamelCase(scaffoldName);
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
<h3 id="meteorgenerate-template-faq"> How to bring your own templates? </h3>
|
||||
|
||||
`--replaceFn`
|
||||
|
||||
There is an option called ``--replaceFn`` that when you pass in given a .js file with two functions it will override all templating that we have defaulted to use your given function.
|
||||
_example of a replacer file_
|
||||
```js
|
||||
export function transformFilename(scaffoldName, filename) {
|
||||
console.log(scaffoldName, filename);
|
||||
return filename
|
||||
}
|
||||
|
||||
export function transformContents(scaffoldName, contents, fileName) {
|
||||
console.log(fileName, contents);
|
||||
return contents
|
||||
}
|
||||
|
||||
```
|
||||
If you run your command like this:
|
||||
|
||||
```bash
|
||||
meteor generate feed --replaceFn=/fn/replace.js
|
||||
```
|
||||
It will generate files full of ``$$PascalCase$$``using the meteor provided templates.
|
||||
|
||||
A better example of this feature would be the following js file:
|
||||
```js
|
||||
const toPascalCase = (str) => {
|
||||
if(!str.includes('-')) return str.charAt(0).toUpperCase() + str.slice(1);
|
||||
else return str.split('-').map(toPascalCase).join('');
|
||||
}
|
||||
const toCamelCase = (str) => {
|
||||
if(!str.includes('-')) return str.charAt(0).toLowerCase() + str.slice(1);
|
||||
else return str.split('-').map(toPascalCase).join('');
|
||||
}
|
||||
|
||||
const transformName = (scaffoldName, str) => {
|
||||
return str.replace(/\$\$name\$\$|\$\$PascalName\$\$|\$\$camelName\$\$/g, function (substring, args) {
|
||||
if (substring === '$$name$$') return scaffoldName;
|
||||
if (substring === '$$PascalName$$') return toPascalCase(scaffoldName);
|
||||
if (substring === '$$camelName$$') return toCamelCase(scaffoldName);
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
export function transformFilename(scaffoldName, filename) {
|
||||
return transformName(scaffoldName, filename);
|
||||
}
|
||||
|
||||
export function transformContents(scaffoldName, contents, fileName) {
|
||||
return transformName(scaffoldName, contents);
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
|
||||
|
||||
<h2 id="meteorloginlogout">meteor login / logout</h2>
|
||||
|
||||
@@ -604,8 +923,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)
|
||||
|
||||
39
docs/source/using-core-types.md
Normal file
39
docs/source/using-core-types.md
Normal file
@@ -0,0 +1,39 @@
|
||||
---
|
||||
title: Using Core Types
|
||||
description: Using core types with zodern:types
|
||||
---
|
||||
|
||||
For MeteorJS in its version 2.8.1 we have introduced to our core packages an integration with the [zodern:types](https://github.com/zodern/meteor-types) package.
|
||||
This package allows you to use the TypeScript types for the Meteor core packages in your TypeScript code or JavaScript code.
|
||||
in order to use the types you need to install the package by running the command:
|
||||
|
||||
```bash
|
||||
meteor add zodern:types
|
||||
```
|
||||
|
||||
And add the following line to your `tsconfig.json` file (if you do not have one, create one and add the code bellow):
|
||||
|
||||
```json
|
||||
{
|
||||
"compilerOptions": {
|
||||
"preserveSymlinks": true,
|
||||
"paths": {
|
||||
"meteor/*": [
|
||||
"node_modules/@types/meteor/*",
|
||||
".meteor/local/types/packages.d.ts"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
then run the command:
|
||||
|
||||
```bash
|
||||
meteor lint
|
||||
```
|
||||
|
||||
this will create a file within your .meteor folder that will have your types for the core packages.
|
||||
You can continue to use your code as you did before, but now you can use the types for the core packages even if you are in JavaScript.
|
||||
|
||||
for more information about the package please visit the [zodern:types](https://github.com/zodern/meteor-types).
|
||||
@@ -5,6 +5,7 @@ edit_branch: 'devel'
|
||||
edit_path: 'guide'
|
||||
content_root: 'content'
|
||||
versions:
|
||||
- '2.9'
|
||||
- '2.8'
|
||||
- '2.7'
|
||||
- '2.6'
|
||||
@@ -37,7 +38,7 @@ sidebar_categories:
|
||||
- index
|
||||
- code-style
|
||||
- structure
|
||||
- 2.8-migration
|
||||
- 2.9-migration
|
||||
Data:
|
||||
- collections
|
||||
- data-loading
|
||||
|
||||
@@ -157,7 +157,7 @@ As `callAsync` returns a promise, it'll be solved in the future. So you need to
|
||||
|
||||
It's also important to understand what will happen if you call an async method with `Meteor.call`, and vice versa.
|
||||
|
||||
If you can an async method with `Meteor.call` in the client, and you don't have the package `insecure` on your project, an error like this will be thrown:
|
||||
If you call an async method with `Meteor.call` in the client, and you don't have the package `insecure` on your project, an error like this will be thrown:
|
||||
|
||||
<img src="images/live-data-error.png">
|
||||
|
||||
|
||||
102
guide/source/2.9-migration.md
Normal file
102
guide/source/2.9-migration.md
Normal file
@@ -0,0 +1,102 @@
|
||||
---
|
||||
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
2
meteor
@@ -1,6 +1,6 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
BUNDLE_VERSION=14.20.1.0
|
||||
BUNDLE_VERSION=14.21.1.6
|
||||
|
||||
# OS Check. Put here because here is where we download the precompiled
|
||||
# bundles that are arch specific.
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# eslint-config-meteor
|
||||
# @meteorjs/eslint-config-meteor
|
||||
|
||||
This is an [ESLint](https://eslint.org) configuration for [Meteor](https://www.meteor.com) apps which implements the recommendations from the [Meteor Guide](https://guide.meteor.com/)'s section on [Code style](https://guide.meteor.com/code-style.html#eslint).
|
||||
|
||||
|
||||
@@ -1,21 +1,16 @@
|
||||
module.exports = {
|
||||
parser: 'babel-eslint',
|
||||
parserOptions: {
|
||||
allowImportExportEverywhere: true
|
||||
allowImportExportEverywhere: true,
|
||||
},
|
||||
env: {
|
||||
node: true,
|
||||
browser: true
|
||||
browser: true,
|
||||
},
|
||||
plugins: [
|
||||
'meteor'
|
||||
],
|
||||
extends: [
|
||||
'airbnb',
|
||||
'plugin:meteor/recommended'
|
||||
],
|
||||
plugins: ['meteor'],
|
||||
extends: ['airbnb', 'plugin:meteor/recommended'],
|
||||
settings: {
|
||||
'import/resolver': 'meteor'
|
||||
'import/resolver': 'meteor',
|
||||
},
|
||||
rules: {
|
||||
'react/jsx-filename-extension': 0,
|
||||
@@ -30,24 +25,21 @@ module.exports = {
|
||||
'no-underscore-dangle': [
|
||||
'error',
|
||||
{
|
||||
allow: [
|
||||
'_id',
|
||||
'_ensureIndex'
|
||||
]
|
||||
}
|
||||
allow: ['_id', '_ensureIndex'],
|
||||
},
|
||||
],
|
||||
'object-shorthand': [
|
||||
'error',
|
||||
'always',
|
||||
{
|
||||
avoidQuotes: false
|
||||
}
|
||||
avoidQuotes: false,
|
||||
},
|
||||
],
|
||||
|
||||
'space-before-function-paren': 0,
|
||||
|
||||
|
||||
// for Meteor API's that rely on `this` context, e.g. Template.onCreated and publications
|
||||
'func-names': 0,
|
||||
'prefer-arrow-callback': 0
|
||||
}
|
||||
'prefer-arrow-callback': 0,
|
||||
},
|
||||
};
|
||||
|
||||
@@ -8,14 +8,14 @@
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/meteor/eslint-config-meteor.git"
|
||||
"url": "git+https://github.com/meteor/meteor.git"
|
||||
},
|
||||
"author": "David Burles",
|
||||
"license": "MIT",
|
||||
"bugs": {
|
||||
"url": "https://github.com/meteor/eslint-config-meteor/issues"
|
||||
"url": "https://github.com/meteor/meteor/issues"
|
||||
},
|
||||
"homepage": "https://github.com/meteor/eslint-config-meteor#readme",
|
||||
"homepage": "https://github.com/meteor/meteor/tree/devel/npm-packages/eslint-config-meteor#readme",
|
||||
"peerDependencies": {
|
||||
"babel-eslint": ">= 7",
|
||||
"eslint": ">= 3",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "eslint-plugin-meteor",
|
||||
"version": "7.3.0",
|
||||
"version": "7.4.0",
|
||||
"author": "Dominik Ferber <dominik.ferber+npm@gmail.com>",
|
||||
"description": "Meteor specific linting rules for ESLint",
|
||||
"main": "lib/index.js",
|
||||
|
||||
@@ -14,8 +14,8 @@ var packageJson = {
|
||||
pacote: "https://github.com/meteor/pacote/tarball/a81b0324686e85d22c7688c47629d4009000e8b8",
|
||||
"node-gyp": "8.0.0",
|
||||
"node-pre-gyp": "0.15.0",
|
||||
typescript: "4.5.4",
|
||||
"@meteorjs/babel": "7.16.0-beta.7",
|
||||
typescript: "4.6.4",
|
||||
"@meteorjs/babel": "7.17.1-beta.0",
|
||||
// Keep the versions of these packages consistent with the versions
|
||||
// found in dev-bundle-server-package.js.
|
||||
"meteor-promise": "0.9.0",
|
||||
|
||||
@@ -185,13 +185,14 @@ 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
|
||||
}]);
|
||||
}
|
||||
const isFiberDisabled = process.env.DISABLE_FIBERS || 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: !isFiberDisabled,
|
||||
isFiberDisabled: isFiberDisabled,
|
||||
overwriteFiberExit: process.env.OVERWRITE_FIBERS_EXIT === '1',
|
||||
}]);
|
||||
|
||||
// Enable async generator functions proposal.
|
||||
combined.plugins.push(require("@babel/plugin-proposal-async-generator-functions"));
|
||||
|
||||
3880
npm-packages/meteor-babel/package-lock.json
generated
3880
npm-packages/meteor-babel/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@meteorjs/babel",
|
||||
"author": "Meteor <dev@meteor.com>",
|
||||
"version": "7.16.0-beta.7",
|
||||
"version": "7.18.0-beta.3",
|
||||
"license": "MIT",
|
||||
"description": "Babel wrapper package for use with Meteor",
|
||||
"keywords": [
|
||||
@@ -47,7 +47,7 @@
|
||||
"convert-source-map": "^1.6.0",
|
||||
"lodash": "^4.17.21",
|
||||
"meteor-babel-helpers": "0.0.3",
|
||||
"typescript": "^4.5.4"
|
||||
"typescript": "~4.6.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/plugin-proposal-decorators": "7.14.5",
|
||||
|
||||
@@ -9,14 +9,10 @@ module.exports = function (babel) {
|
||||
Function: {
|
||||
exit: function (path) {
|
||||
const node = path.node;
|
||||
if (! node.async) {
|
||||
if (!node.async) {
|
||||
return;
|
||||
}
|
||||
|
||||
// The original function becomes a non-async function that
|
||||
// returns a Promise.
|
||||
node.async = false;
|
||||
|
||||
// The inner function should inherit lexical environment items
|
||||
// like `this`, `super`, and `arguments` from the outer
|
||||
// function, and arrow functions provide exactly that behavior.
|
||||
@@ -30,6 +26,17 @@ module.exports = function (babel) {
|
||||
!! this.opts.useNativeAsyncAwait
|
||||
);
|
||||
|
||||
if (this.opts.isFiberDisabled && this.opts.overwriteFiberExit) {
|
||||
if (node.type === "ArrowFunctionExpression") {
|
||||
node.body = innerFn;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// The original function becomes a non-async function that
|
||||
// returns a Promise.
|
||||
node.async = false;
|
||||
|
||||
const promiseResultExpression = t.callExpression(
|
||||
t.memberExpression(
|
||||
t.identifier("Promise"),
|
||||
|
||||
@@ -14,6 +14,8 @@ npm install -g meteor
|
||||
|
||||
| NPM Package | Meteor Official Release |
|
||||
|-------------|-------------------------|
|
||||
| 2.8.2 | 2.8.1 |
|
||||
| 2.8.1 | 2.8.1 |
|
||||
| 2.8.0 | 2.8.0 |
|
||||
| 2.7.5 | 2.7.3 |
|
||||
| 2.7.4 | 2.7.3 |
|
||||
@@ -61,4 +63,4 @@ npm install -g meteor --ignore-meteor-setup-exec-path
|
||||
### Proxy configuration
|
||||
|
||||
Setting the `https_proxy` or `HTTPS_PROXY` environment variable to a valid proxy URL will cause the
|
||||
downloader to use the configured proxy to retrieve the Meteor files.
|
||||
downloader to use the configured proxy to retrieve the Meteor files.
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
const path = require('path');
|
||||
const os = require('os');
|
||||
|
||||
const METEOR_LATEST_VERSION = '2.8.0';
|
||||
const METEOR_LATEST_VERSION = '2.9.0';
|
||||
const sudoUser = process.env.SUDO_USER || '';
|
||||
function isRoot() {
|
||||
return process.getuid && process.getuid() === 0;
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
const { DownloaderHelper } = require('node-downloader-helper');
|
||||
const HttpsProxyAgent = require('https-proxy-agent');
|
||||
const cliProgress = require('cli-progress');
|
||||
const Seven = require('node-7z');
|
||||
const path = require('path');
|
||||
@@ -150,6 +149,8 @@ function generateProxyAgent() {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const HttpsProxyAgent = require('https-proxy-agent');
|
||||
|
||||
return new HttpsProxyAgent(proxyUrl);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "meteor",
|
||||
"version": "2.8.0",
|
||||
"version": "2.9.0",
|
||||
"description": "Install Meteor",
|
||||
"main": "install.js",
|
||||
"scripts": {
|
||||
@@ -11,6 +11,7 @@
|
||||
"dependencies": {
|
||||
"7zip-bin": "^5.2.0",
|
||||
"cli-progress": "^3.11.1",
|
||||
"https-proxy-agent": "^5.0.1",
|
||||
"node-7z": "^2.1.2",
|
||||
"node-downloader-helper": "^1.0.19",
|
||||
"rimraf": "^3.0.2",
|
||||
|
||||
326
packages/accounts-base/accounts-base.d.ts
vendored
Normal file
326
packages/accounts-base/accounts-base.d.ts
vendored
Normal file
@@ -0,0 +1,326 @@
|
||||
import { Mongo } from 'meteor/mongo';
|
||||
import { Meteor } from 'meteor/meteor';
|
||||
|
||||
export interface URLS {
|
||||
resetPassword: (token: string) => string;
|
||||
verifyEmail: (token: string) => string;
|
||||
enrollAccount: (token: string) => string;
|
||||
}
|
||||
|
||||
export interface EmailFields {
|
||||
from?: ((user: Meteor.User) => string) | undefined;
|
||||
subject?: ((user: Meteor.User) => string) | undefined;
|
||||
text?: ((user: Meteor.User, url: string) => string) | undefined;
|
||||
html?: ((user: Meteor.User, url: string) => string) | undefined;
|
||||
}
|
||||
|
||||
export namespace Accounts {
|
||||
var urls: URLS;
|
||||
|
||||
function user(options?: {
|
||||
fields?: Mongo.FieldSpecifier | undefined;
|
||||
}): Meteor.User | null;
|
||||
|
||||
function userId(): string | null;
|
||||
|
||||
function createUser(
|
||||
options: {
|
||||
username?: string | undefined;
|
||||
email?: string | undefined;
|
||||
password?: string | undefined;
|
||||
profile?: Object | undefined;
|
||||
},
|
||||
callback?: (error?: Error | Meteor.Error | Meteor.TypedError) => void
|
||||
): string;
|
||||
|
||||
function config(options: {
|
||||
sendVerificationEmail?: boolean | undefined;
|
||||
forbidClientAccountCreation?: boolean | undefined;
|
||||
restrictCreationByEmailDomain?: string | Function | undefined;
|
||||
loginExpirationInDays?: number | undefined;
|
||||
oauthSecretKey?: string | undefined;
|
||||
passwordResetTokenExpirationInDays?: number | undefined;
|
||||
passwordEnrollTokenExpirationInDays?: number | undefined;
|
||||
ambiguousErrorMessages?: boolean | undefined;
|
||||
defaultFieldSelector?: { [key: string]: 0 | 1 } | undefined;
|
||||
}): void;
|
||||
|
||||
function onLogin(
|
||||
func: Function
|
||||
): {
|
||||
stop: () => void;
|
||||
};
|
||||
|
||||
function onLoginFailure(
|
||||
func: Function
|
||||
): {
|
||||
stop: () => void;
|
||||
};
|
||||
|
||||
function loginServicesConfigured(): boolean;
|
||||
|
||||
function onPageLoadLogin(func: Function): void;
|
||||
}
|
||||
|
||||
export namespace Accounts {
|
||||
function changePassword(
|
||||
oldPassword: string,
|
||||
newPassword: string,
|
||||
callback?: (error?: Error | Meteor.Error | Meteor.TypedError) => void
|
||||
): void;
|
||||
|
||||
function forgotPassword(
|
||||
options: { email?: string | undefined },
|
||||
callback?: (error?: Error | Meteor.Error | Meteor.TypedError) => void
|
||||
): void;
|
||||
|
||||
function resetPassword(
|
||||
token: string,
|
||||
newPassword: string,
|
||||
callback?: (error?: Error | Meteor.Error | Meteor.TypedError) => void
|
||||
): void;
|
||||
|
||||
function verifyEmail(
|
||||
token: string,
|
||||
callback?: (error?: Error | Meteor.Error | Meteor.TypedError) => void
|
||||
): void;
|
||||
|
||||
function onEmailVerificationLink(callback: Function): void;
|
||||
|
||||
function onEnrollmentLink(callback: Function): void;
|
||||
|
||||
function onResetPasswordLink(callback: Function): void;
|
||||
|
||||
function loggingIn(): boolean;
|
||||
|
||||
function loggingOut(): boolean;
|
||||
|
||||
function logout(
|
||||
callback?: (error?: Error | Meteor.Error | Meteor.TypedError) => void
|
||||
): void;
|
||||
|
||||
function logoutOtherClients(
|
||||
callback?: (error?: Error | Meteor.Error | Meteor.TypedError) => void
|
||||
): void;
|
||||
|
||||
var ui: {
|
||||
config(options: {
|
||||
requestPermissions?: Object | undefined;
|
||||
requestOfflineToken?: Object | undefined;
|
||||
forceApprovalPrompt?: Object | undefined;
|
||||
passwordSignupFields?: string | undefined;
|
||||
}): void;
|
||||
};
|
||||
}
|
||||
|
||||
export interface Header {
|
||||
[id: string]: string;
|
||||
}
|
||||
|
||||
export interface EmailTemplates {
|
||||
from: string;
|
||||
siteName: string;
|
||||
headers?: Header | undefined;
|
||||
resetPassword: EmailFields;
|
||||
enrollAccount: EmailFields;
|
||||
verifyEmail: EmailFields;
|
||||
}
|
||||
|
||||
export namespace Accounts {
|
||||
var emailTemplates: EmailTemplates;
|
||||
|
||||
function addEmail(userId: string, newEmail: string, verified?: boolean): void;
|
||||
|
||||
function removeEmail(userId: string, email: string): void;
|
||||
|
||||
function onCreateUser(
|
||||
func: (options: { profile?: {} | undefined }, user: Meteor.User) => void
|
||||
): void;
|
||||
|
||||
function findUserByEmail(
|
||||
email: string,
|
||||
options?: { fields?: Mongo.FieldSpecifier | undefined }
|
||||
): Meteor.User | null | undefined;
|
||||
|
||||
function findUserByUsername(
|
||||
username: string,
|
||||
options?: { fields?: Mongo.FieldSpecifier | undefined }
|
||||
): Meteor.User | null | undefined;
|
||||
|
||||
function sendEnrollmentEmail(
|
||||
userId: string,
|
||||
email?: string,
|
||||
extraTokenData?: Record<string, unknown>,
|
||||
extraParams?: Record<string, unknown>
|
||||
): void;
|
||||
|
||||
function sendResetPasswordEmail(
|
||||
userId: string,
|
||||
email?: string,
|
||||
extraTokenData?: Record<string, unknown>,
|
||||
extraParams?: Record<string, unknown>
|
||||
): void;
|
||||
|
||||
function sendVerificationEmail(
|
||||
userId: string,
|
||||
email?: string,
|
||||
extraTokenData?: Record<string, unknown>,
|
||||
extraParams?: Record<string, unknown>
|
||||
): void;
|
||||
|
||||
function setUsername(userId: string, newUsername: string): void;
|
||||
|
||||
function setPassword(
|
||||
userId: string,
|
||||
newPassword: string,
|
||||
options?: { logout?: Object | undefined }
|
||||
): void;
|
||||
|
||||
function validateNewUser(func: Function): boolean;
|
||||
|
||||
function validateLoginAttempt(
|
||||
func: Function
|
||||
): {
|
||||
stop: () => void;
|
||||
};
|
||||
|
||||
function _hashPassword(
|
||||
password: string
|
||||
): { digest: string; algorithm: string };
|
||||
|
||||
interface IValidateLoginAttemptCbOpts {
|
||||
type: string;
|
||||
allowed: boolean;
|
||||
error: Meteor.Error;
|
||||
user: Meteor.User;
|
||||
connection: Meteor.Connection;
|
||||
methodName: string;
|
||||
methodArguments: any[];
|
||||
}
|
||||
}
|
||||
|
||||
export namespace Accounts {
|
||||
function onLogout(func: Function): void;
|
||||
}
|
||||
|
||||
export namespace Accounts {
|
||||
function onLogout(
|
||||
func: (options: {
|
||||
user: Meteor.User;
|
||||
connection: Meteor.Connection;
|
||||
}) => void
|
||||
): void;
|
||||
}
|
||||
|
||||
export namespace Accounts {
|
||||
interface LoginMethodOptions {
|
||||
/**
|
||||
* The method to call (default 'login')
|
||||
*/
|
||||
methodName?: string | undefined;
|
||||
/**
|
||||
* The arguments for the method
|
||||
*/
|
||||
methodArguments?: any[] | undefined;
|
||||
/**
|
||||
* If provided, will be called with the result of the
|
||||
* method. If it throws, the client will not be logged in (and
|
||||
* its error will be passed to the callback).
|
||||
*/
|
||||
validateResult?: Function | undefined;
|
||||
/**
|
||||
* Will be called with no arguments once the user is fully
|
||||
* logged in, or with the error on error.
|
||||
*/
|
||||
userCallback?: ((err?: any) => void) | undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* Call a login method on the server.
|
||||
*
|
||||
* A login method is a method which on success calls `this.setUserId(id)` and
|
||||
* `Accounts._setLoginToken` on the server and returns an object with fields
|
||||
* 'id' (containing the user id), 'token' (containing a resume token), and
|
||||
* optionally `tokenExpires`.
|
||||
*
|
||||
* This function takes care of:
|
||||
* - Updating the Meteor.loggingIn() reactive data source
|
||||
* - Calling the method in 'wait' mode
|
||||
* - On success, saving the resume token to localStorage
|
||||
* - On success, calling Accounts.connection.setUserId()
|
||||
* - Setting up an onReconnect handler which logs in with
|
||||
* the resume token
|
||||
*
|
||||
* Options:
|
||||
* - methodName: The method to call (default 'login')
|
||||
* - methodArguments: The arguments for the method
|
||||
* - validateResult: If provided, will be called with the result of the
|
||||
* method. If it throws, the client will not be logged in (and
|
||||
* its error will be passed to the callback).
|
||||
* - userCallback: Will be called with no arguments once the user is fully
|
||||
* logged in, or with the error on error.
|
||||
*
|
||||
* */
|
||||
function callLoginMethod(options: LoginMethodOptions): void;
|
||||
|
||||
/**
|
||||
*
|
||||
* The main entry point for auth packages to hook in to login.
|
||||
*
|
||||
* A login handler is a login method which can return `undefined` to
|
||||
* indicate that the login request is not handled by this handler.
|
||||
*
|
||||
* @param name {String} Optional. The service name, used by default
|
||||
* if a specific service name isn't returned in the result.
|
||||
*
|
||||
* @param handler {Function} A function that receives an options object
|
||||
* (as passed as an argument to the `login` method) and returns one of:
|
||||
* - `undefined`, meaning don't handle;
|
||||
* - a login method result object
|
||||
**/
|
||||
function registerLoginHandler(
|
||||
name: string,
|
||||
handler: (options: any) => undefined | Object
|
||||
): void;
|
||||
|
||||
type Password =
|
||||
| string
|
||||
| {
|
||||
digest: string;
|
||||
algorithm: 'sha-256';
|
||||
};
|
||||
|
||||
/**
|
||||
*
|
||||
* Check whether the provided password matches the bcrypt'ed password in
|
||||
* the database user record. `password` can be a string (in which case
|
||||
* it will be run through SHA256 before bcrypt) or an object with
|
||||
* properties `digest` and `algorithm` (in which case we bcrypt
|
||||
* `password.digest`).
|
||||
*/
|
||||
function _checkPassword(
|
||||
user: Meteor.User,
|
||||
password: Password
|
||||
): { userId: string; error?: any };
|
||||
}
|
||||
|
||||
export namespace Accounts {
|
||||
type StampedLoginToken = {
|
||||
token: string;
|
||||
when: Date;
|
||||
};
|
||||
type HashedStampedLoginToken = {
|
||||
hashedToken: string;
|
||||
when: Date;
|
||||
};
|
||||
|
||||
function _generateStampedLoginToken(): StampedLoginToken;
|
||||
function _hashStampedToken(token: StampedLoginToken): HashedStampedLoginToken;
|
||||
function _insertHashedLoginToken<T>(
|
||||
userId: string,
|
||||
token: HashedStampedLoginToken,
|
||||
query?: Mongo.Selector<T> | Mongo.ObjectID | string
|
||||
): void;
|
||||
function _hashLoginToken(token: string): string;
|
||||
}
|
||||
@@ -119,18 +119,21 @@ export class AccountsClient extends AccountsCommon {
|
||||
*/
|
||||
logout(callback) {
|
||||
this._loggingOut.set(true);
|
||||
this.connection.apply('logout', [], {
|
||||
|
||||
this.connection.applyAsync('logout', [], {
|
||||
// TODO[FIBERS]: Look this { wait: true } later.
|
||||
wait: true
|
||||
}, (error, result) => {
|
||||
this._loggingOut.set(false);
|
||||
this._loginCallbacksCalled = false;
|
||||
if (error) {
|
||||
callback && callback(error);
|
||||
} else {
|
||||
})
|
||||
.then((result) => {
|
||||
this._loggingOut.set(false);
|
||||
this._loginCallbacksCalled = false;
|
||||
this.makeClientLoggedOut();
|
||||
callback && callback();
|
||||
}
|
||||
});
|
||||
})
|
||||
.catch((e) => {
|
||||
this._loggingOut.set(false);
|
||||
callback && callback(e);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -798,6 +801,11 @@ if (Package.blaze) {
|
||||
*/
|
||||
Template.registerHelper('currentUser', () => Meteor.user());
|
||||
|
||||
// TODO: the code above needs to be changed to Meteor.userAsync() when we have
|
||||
// a way to make it reactive using async.
|
||||
// Template.registerHelper('currentUserAsync',
|
||||
// async () => await Meteor.userAsync());
|
||||
|
||||
/**
|
||||
* @global
|
||||
* @name loggingIn
|
||||
|
||||
@@ -36,9 +36,9 @@ const createUserAndLogout = (test, done, nextTests) => {
|
||||
},
|
||||
},
|
||||
() => {
|
||||
Meteor.logout(() => {
|
||||
Meteor.logout(async () => {
|
||||
// Make sure we're logged out
|
||||
test.isFalse(Meteor.user());
|
||||
test.isFalse(await Meteor.userAsync());
|
||||
// Handle next tests
|
||||
nextTests(test, done);
|
||||
});
|
||||
@@ -94,6 +94,20 @@ Tinytest.addAsync(
|
||||
}
|
||||
);
|
||||
|
||||
Tinytest.addAsync(
|
||||
'accounts async - Meteor.loggingIn() is false after login has completed',
|
||||
(test, done) => {
|
||||
logoutAndCreateUser(test, done, () => {
|
||||
// Login then verify loggingIn is false after login has completed
|
||||
Meteor.loginWithPassword(username, password, async () => {
|
||||
test.isFalse(Meteor.loggingIn());
|
||||
test.isTrue(await Meteor.userAsync());
|
||||
removeTestUser(done);
|
||||
});
|
||||
});
|
||||
}
|
||||
);
|
||||
|
||||
Tinytest.addAsync(
|
||||
'accounts - Meteor.loggingOut() is true right after a logout call',
|
||||
(test, done) => {
|
||||
@@ -150,7 +164,7 @@ Tinytest.addAsync(
|
||||
);
|
||||
|
||||
Tinytest.addAsync(
|
||||
'accounts - Meteor.user obeys explicit and default field selectors',
|
||||
'accounts - Meteor.user() obeys explicit and default field selectors',
|
||||
(test, done) => {
|
||||
logoutAndCreateUser(test, done, () => {
|
||||
Meteor.loginWithPassword(username, password, () => {
|
||||
@@ -178,6 +192,38 @@ Tinytest.addAsync(
|
||||
}
|
||||
);
|
||||
|
||||
Tinytest.addAsync(
|
||||
'accounts async - Meteor.userAsync() obeys explicit and default field selectors',
|
||||
(test, done) => {
|
||||
logoutAndCreateUser(test, done, () => {
|
||||
Meteor.loginWithPassword(username, password, async () => {
|
||||
// by default, all fields should be returned
|
||||
let user;
|
||||
user = await Meteor.userAsync();
|
||||
test.equal(user.profile[excludeField], excludeValue);
|
||||
|
||||
// this time we want to exclude the default fields
|
||||
const options = Accounts._options;
|
||||
Accounts._options = {};
|
||||
Accounts.config({ defaultFieldSelector: { ['profile.' + defaultExcludeField]: 0 } });
|
||||
|
||||
user = await Meteor.userAsync();
|
||||
test.isUndefined(user.profile[defaultExcludeField]);
|
||||
test.equal(user.profile[excludeField], excludeValue);
|
||||
test.equal(user.profile.name, username);
|
||||
|
||||
// this time we only want certain fields...
|
||||
|
||||
user = await Meteor.userAsync({ fields: { 'profile.name': 1 } });
|
||||
test.isUndefined(user.profile[excludeField]);
|
||||
test.isUndefined(user.profile[defaultExcludeField]);
|
||||
test.equal(user.profile.name, username);
|
||||
Accounts._options = options;
|
||||
removeTestUser(done);
|
||||
});
|
||||
});
|
||||
}
|
||||
);
|
||||
|
||||
Tinytest.addAsync(
|
||||
'accounts-2fa - Meteor.loginWithPasswordAnd2faCode() fails when token is not provided',
|
||||
@@ -199,13 +245,13 @@ Tinytest.addAsync(
|
||||
);
|
||||
|
||||
|
||||
Tinytest.addAsync(
|
||||
Tinytest.addAsync(
|
||||
'accounts-2fa - Meteor.loginWithPasswordAnd2faCode() fails with invalid code',
|
||||
(test, done) => {
|
||||
createUserAndLogout(test, done, () => {
|
||||
forceEnableUser2fa(() => {
|
||||
Meteor.loginWithPasswordAnd2faCode(username, password, 'ABC', e => {
|
||||
test.isFalse(Meteor.user());
|
||||
Meteor.loginWithPasswordAnd2faCode(username, password, 'ABC', async e => {
|
||||
test.isFalse(await Meteor.user());
|
||||
test.equal(e.reason, 'Invalid 2FA code');
|
||||
removeTestUser(done);
|
||||
});
|
||||
|
||||
@@ -79,40 +79,6 @@ export class AccountsCommon {
|
||||
// should come up with a more generic way to do this (eg, with some sort of
|
||||
// symbolic error code rather than a number).
|
||||
this.LoginCancelledError.numericError = 0x8acdc2f;
|
||||
|
||||
// loginServiceConfiguration and ConfigError are maintained for backwards compatibility
|
||||
Meteor.startup(() => {
|
||||
const { ServiceConfiguration } = Package['service-configuration'];
|
||||
this.loginServiceConfiguration = ServiceConfiguration.configurations;
|
||||
this.ConfigError = ServiceConfiguration.ConfigError;
|
||||
|
||||
const settings = Meteor.settings?.packages?.['accounts-base'];
|
||||
if (settings) {
|
||||
if (settings.oauthSecretKey) {
|
||||
if (!Package['oauth-encryption']) {
|
||||
throw new Error(
|
||||
'The oauth-encryption package must be loaded to set oauthSecretKey'
|
||||
);
|
||||
}
|
||||
Package['oauth-encryption'].OAuthEncryption.loadKey(
|
||||
settings.oauthSecretKey
|
||||
);
|
||||
delete settings.oauthSecretKey;
|
||||
}
|
||||
// Validate config options keys
|
||||
Object.keys(settings).forEach(key => {
|
||||
if (!VALID_CONFIG_KEYS.includes(key)) {
|
||||
// TODO Consider just logging a debug message instead to allow for additional keys in the settings here?
|
||||
throw new Meteor.Error(
|
||||
`Accounts configuration: Invalid key: ${key}`
|
||||
);
|
||||
} else {
|
||||
// set values in Accounts._options
|
||||
this._options[key] = settings[key];
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -170,6 +136,18 @@ export class AccountsCommon {
|
||||
: null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @summary Get the current user record, or `null` if no user is logged in.
|
||||
* @locus Anywhere
|
||||
* @param {Object} [options]
|
||||
* @param {MongoFieldSpecifier} options.fields Dictionary of fields to return or exclude.
|
||||
*/
|
||||
async userAsync(options) {
|
||||
const userId = this.userId();
|
||||
return userId
|
||||
? this.users.findOneAsync(userId, this._addDefaultFieldSelector(options))
|
||||
: null;
|
||||
}
|
||||
// Set up config for the accounts system. Call this on both the client
|
||||
// and the server.
|
||||
//
|
||||
@@ -264,6 +242,7 @@ export class AccountsCommon {
|
||||
// Validate config options keys
|
||||
Object.keys(options).forEach(key => {
|
||||
if (!VALID_CONFIG_KEYS.includes(key)) {
|
||||
// TODO Consider just logging a debug message instead to allow for additional keys in the settings here?
|
||||
throw new Meteor.Error(`Accounts.config: Invalid key: ${key}`);
|
||||
}
|
||||
});
|
||||
@@ -418,6 +397,15 @@ Meteor.userId = () => Accounts.userId();
|
||||
*/
|
||||
Meteor.user = options => Accounts.user(options);
|
||||
|
||||
/**
|
||||
* @summary Get the current user record, or `null` if no user is logged in. A reactive data source.
|
||||
* @locus Anywhere but publish functions
|
||||
* @importFromPackage meteor
|
||||
* @param {Object} [options]
|
||||
* @param {MongoFieldSpecifier} options.fields Dictionary of fields to return or exclude.
|
||||
*/
|
||||
Meteor.userAsync = options => Accounts.userAsync(options);
|
||||
|
||||
// how long (in days) until a login token expires
|
||||
const DEFAULT_LOGIN_EXPIRATION_DAYS = 90;
|
||||
// how long (in days) until reset password token expires
|
||||
@@ -430,9 +418,6 @@ const DEFAULT_PASSWORD_ENROLL_TOKEN_EXPIRATION_DAYS = 30;
|
||||
const MIN_TOKEN_LIFETIME_CAP_SECS = 3600; // one hour
|
||||
// how often (in milliseconds) we check for expired tokens
|
||||
export const EXPIRE_TOKENS_INTERVAL_MS = 600 * 1000; // 10 minutes
|
||||
// how long we wait before logging out clients when Meteor.logoutOtherClients is
|
||||
// called
|
||||
export const CONNECTION_CLOSE_DELAY_MS = 10 * 1000;
|
||||
// A large number of expiration days (approximately 100 years worth) that is
|
||||
// used when creating unexpiring tokens.
|
||||
const LOGIN_UNEXPIRING_TOKEN_DAYS = 365 * 100;
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import crypto from 'crypto';
|
||||
import { Meteor } from 'meteor/meteor'
|
||||
import {
|
||||
AccountsCommon,
|
||||
EXPIRE_TOKENS_INTERVAL_MS,
|
||||
@@ -13,6 +14,7 @@ const NonEmptyString = Match.Where(x => {
|
||||
return x.length > 0;
|
||||
});
|
||||
|
||||
|
||||
/**
|
||||
* @summary Constructor for the `Accounts` namespace on the server.
|
||||
* @locus Server
|
||||
@@ -70,8 +72,6 @@ export class AccountsServer extends AccountsCommon {
|
||||
|
||||
// list of all registered handlers.
|
||||
this._loginHandlers = [];
|
||||
|
||||
setupUsersCollection(this.users);
|
||||
setupDefaultLoginHandlers(this);
|
||||
setExpireTokensInterval(this);
|
||||
|
||||
@@ -125,6 +125,10 @@ export class AccountsServer extends AccountsCommon {
|
||||
return currentInvocation.userId;
|
||||
}
|
||||
|
||||
async init() {
|
||||
await setupUsersCollection(this.users);
|
||||
}
|
||||
|
||||
///
|
||||
/// LOGIN HOOKS
|
||||
///
|
||||
@@ -258,11 +262,11 @@ export class AccountsServer extends AccountsCommon {
|
||||
});
|
||||
};
|
||||
|
||||
_successfulLogout(connection, userId) {
|
||||
async _successfulLogout(connection, userId) {
|
||||
// don't fetch the user object unless there are some callbacks registered
|
||||
let user;
|
||||
this._onLogoutHook.each(callback => {
|
||||
if (!user && userId) user = this.users.findOne(userId, {fields: this._options.defaultFieldSelector});
|
||||
await this._onLogoutHook.forEachAsync(async callback => {
|
||||
if (!user && userId) user = await this.users.findOne(userId, { fields: this._options.defaultFieldSelector });
|
||||
callback({ user, connection });
|
||||
return true;
|
||||
});
|
||||
@@ -398,10 +402,10 @@ export class AccountsServer extends AccountsCommon {
|
||||
// indicates that the login token has already been inserted into the
|
||||
// database and doesn't need to be inserted again. (It's used by the
|
||||
// "resume" login handler).
|
||||
_loginUser(methodInvocation, userId, stampedLoginToken) {
|
||||
async _loginUser(methodInvocation, userId, stampedLoginToken) {
|
||||
if (! stampedLoginToken) {
|
||||
stampedLoginToken = this._generateStampedLoginToken();
|
||||
this._insertLoginToken(userId, stampedLoginToken);
|
||||
await this._insertLoginToken(userId, stampedLoginToken);
|
||||
}
|
||||
|
||||
// This order (and the avoidance of yields) is important to make
|
||||
@@ -472,12 +476,13 @@ export class AccountsServer extends AccountsCommon {
|
||||
this._validateLogin(methodInvocation.connection, attempt);
|
||||
|
||||
if (attempt.allowed) {
|
||||
const o = await this._loginUser(
|
||||
methodInvocation,
|
||||
result.userId,
|
||||
result.stampedLoginToken
|
||||
)
|
||||
const ret = {
|
||||
...this._loginUser(
|
||||
methodInvocation,
|
||||
result.userId,
|
||||
result.stampedLoginToken
|
||||
),
|
||||
...o,
|
||||
...result.options
|
||||
};
|
||||
ret.type = attempt.type;
|
||||
@@ -614,8 +619,8 @@ export class AccountsServer extends AccountsCommon {
|
||||
// Any connections associated with old-style unhashed tokens will be
|
||||
// in the process of becoming associated with hashed tokens and then
|
||||
// they'll get closed.
|
||||
destroyToken(userId, loginToken) {
|
||||
this.users.update(userId, {
|
||||
async destroyToken(userId, loginToken) {
|
||||
await this.users.update(userId, {
|
||||
$pull: {
|
||||
"services.resume.loginTokens": {
|
||||
$or: [
|
||||
@@ -652,13 +657,13 @@ export class AccountsServer extends AccountsCommon {
|
||||
return await accounts._attemptLogin(this, "login", arguments, result);
|
||||
};
|
||||
|
||||
methods.logout = function () {
|
||||
methods.logout = async function () {
|
||||
const token = accounts._getLoginToken(this.connection.id);
|
||||
accounts._setLoginToken(this.userId, this.connection, null);
|
||||
if (token && this.userId) {
|
||||
accounts.destroyToken(this.userId, token);
|
||||
await accounts.destroyToken(this.userId, token);
|
||||
}
|
||||
accounts._successfulLogout(this.connection, this.userId);
|
||||
await accounts._successfulLogout(this.connection, this.userId);
|
||||
this.setUserId(null);
|
||||
};
|
||||
|
||||
@@ -670,8 +675,8 @@ export class AccountsServer extends AccountsCommon {
|
||||
// @returns Object
|
||||
// If successful, returns { token: <new token>, id: <user id>,
|
||||
// tokenExpires: <expiration date> }.
|
||||
methods.getNewToken = function () {
|
||||
const user = accounts.users.findOne(this.userId, {
|
||||
methods.getNewToken = async function () {
|
||||
const user = await accounts.users.findOne(this.userId, {
|
||||
fields: { "services.resume.loginTokens": 1 }
|
||||
});
|
||||
if (! this.userId || ! user) {
|
||||
@@ -690,8 +695,8 @@ export class AccountsServer extends AccountsCommon {
|
||||
}
|
||||
const newStampedToken = accounts._generateStampedLoginToken();
|
||||
newStampedToken.when = currentStampedToken.when;
|
||||
accounts._insertLoginToken(this.userId, newStampedToken);
|
||||
return accounts._loginUser(this, this.userId, newStampedToken);
|
||||
await accounts._insertLoginToken(this.userId, newStampedToken);
|
||||
return await accounts._loginUser(this, this.userId, newStampedToken);
|
||||
};
|
||||
|
||||
// Removes all tokens except the token associated with the current
|
||||
@@ -724,14 +729,19 @@ export class AccountsServer extends AccountsCommon {
|
||||
throw new Meteor.Error(403, "Service unknown");
|
||||
}
|
||||
|
||||
const { ServiceConfiguration } = Package['service-configuration'];
|
||||
if (ServiceConfiguration.configurations.findOne({service: options.service}))
|
||||
throw new Meteor.Error(403, `Service ${options.service} already configured`);
|
||||
if (Package['service-configuration']) {
|
||||
const { ServiceConfiguration } = Package['service-configuration'];
|
||||
if (ServiceConfiguration.configurations.findOne({service: options.service}))
|
||||
throw new Meteor.Error(403, `Service ${options.service} already configured`);
|
||||
|
||||
if (hasOwn.call(options, 'secret') && usingOAuthEncryption())
|
||||
options.secret = OAuthEncryption.seal(options.secret);
|
||||
if (Package["oauth-encryption"]) {
|
||||
const { OAuthEncryption } = Package["oauth-encryption"]
|
||||
if (hasOwn.call(options, 'secret') && OAuthEncryption.keyIsLoaded())
|
||||
options.secret = OAuthEncryption.seal(options.secret);
|
||||
}
|
||||
|
||||
ServiceConfiguration.configurations.insert(options);
|
||||
ServiceConfiguration.configurations.insert(options);
|
||||
}
|
||||
};
|
||||
|
||||
accounts._server.methods(methods);
|
||||
@@ -756,8 +766,10 @@ export class AccountsServer extends AccountsCommon {
|
||||
|
||||
// Publish all login service configuration fields other than secret.
|
||||
this._server.publish("meteor.loginServiceConfiguration", () => {
|
||||
const { ServiceConfiguration } = Package['service-configuration'];
|
||||
return ServiceConfiguration.configurations.find({}, {fields: {secret: 0}});
|
||||
if (Package['service-configuration']) {
|
||||
const { ServiceConfiguration } = Package['service-configuration'];
|
||||
return ServiceConfiguration.configurations.find({}, {fields: {secret: 0}});
|
||||
}
|
||||
}, {is_auto: true}); // not technically autopublish, but stops the warning.
|
||||
|
||||
// Use Meteor.startup to give other packages a chance to call
|
||||
@@ -888,10 +900,10 @@ export class AccountsServer extends AccountsCommon {
|
||||
// Using $addToSet avoids getting an index error if another client
|
||||
// logging in simultaneously has already inserted the new hashed
|
||||
// token.
|
||||
_insertHashedLoginToken(userId, hashedToken, query) {
|
||||
async _insertHashedLoginToken(userId, hashedToken, query) {
|
||||
query = query ? { ...query } : {};
|
||||
query._id = userId;
|
||||
this.users.update(query, {
|
||||
await this.users.update(query, {
|
||||
$addToSet: {
|
||||
"services.resume.loginTokens": hashedToken
|
||||
}
|
||||
@@ -899,14 +911,20 @@ export class AccountsServer extends AccountsCommon {
|
||||
};
|
||||
|
||||
// Exported for tests.
|
||||
_insertLoginToken(userId, stampedToken, query) {
|
||||
this._insertHashedLoginToken(
|
||||
async _insertLoginToken(userId, stampedToken, query) {
|
||||
await this._insertHashedLoginToken(
|
||||
userId,
|
||||
this._hashStampedToken(stampedToken),
|
||||
query
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
*
|
||||
* @param userId
|
||||
* @private
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
_clearAllLoginTokens(userId) {
|
||||
this.users.update(userId, {
|
||||
$set: {
|
||||
@@ -964,7 +982,7 @@ export class AccountsServer extends AccountsCommon {
|
||||
// already -- in this case we just clean up the observe that we started).
|
||||
const myObserveNumber = ++this._nextUserObserveNumber;
|
||||
this._userObservesForConnections[connection.id] = myObserveNumber;
|
||||
Meteor.defer(() => {
|
||||
Meteor.defer(async () => {
|
||||
// If something else happened on this connection in the meantime (it got
|
||||
// closed, or another call to _setLoginToken happened), just do
|
||||
// nothing. We don't need to start an observe for an old connection or old
|
||||
@@ -977,7 +995,7 @@ export class AccountsServer extends AccountsCommon {
|
||||
// Because we upgrade unhashed login tokens to hashed tokens at
|
||||
// login time, sessions will only be logged in with a hashed
|
||||
// token. Thus we only need to observe hashed tokens here.
|
||||
const observe = this.users.find({
|
||||
const observe = await this.users.find({
|
||||
_id: userId,
|
||||
'services.resume.loginTokens.hashedToken': newToken
|
||||
}, { fields: { _id: 1 } }).observeChanges({
|
||||
@@ -1088,7 +1106,14 @@ export class AccountsServer extends AccountsCommon {
|
||||
// tests. oldestValidDate is simulate expiring tokens without waiting
|
||||
// for them to actually expire. userId is used by tests to only expire
|
||||
// tokens for the test user.
|
||||
_expireTokens(oldestValidDate, userId) {
|
||||
/**
|
||||
*
|
||||
* @param oldestValidDate
|
||||
* @param userId
|
||||
* @private
|
||||
* @return {Promise<void>}
|
||||
*/
|
||||
async _expireTokens(oldestValidDate, userId) {
|
||||
const tokenLifetimeMs = this._getTokenLifetimeMs();
|
||||
|
||||
// when calling from a test with extra arguments, you must specify both!
|
||||
@@ -1103,7 +1128,7 @@ export class AccountsServer extends AccountsCommon {
|
||||
|
||||
// Backwards compatible with older versions of meteor that stored login token
|
||||
// timestamps as numbers.
|
||||
this.users.update({ ...userFilter,
|
||||
await this.users.update({ ...userFilter,
|
||||
$or: [
|
||||
{ "services.resume.loginTokens.when": { $lt: oldestValidDate } },
|
||||
{ "services.resume.loginTokens.when": { $lt: +oldestValidDate } }
|
||||
@@ -1140,7 +1165,7 @@ export class AccountsServer extends AccountsCommon {
|
||||
};
|
||||
|
||||
// Called by accounts-password
|
||||
insertUserDoc(options, user) {
|
||||
async insertUserDoc(options, user) {
|
||||
// - clone user document, to protect from modification
|
||||
// - add createdAt timestamp
|
||||
// - prepare an _id, so that you can modify other collections (eg
|
||||
@@ -1185,7 +1210,7 @@ export class AccountsServer extends AccountsCommon {
|
||||
|
||||
let userId;
|
||||
try {
|
||||
userId = this.users.insert(fullUser);
|
||||
userId = await this.users.insert(fullUser);
|
||||
} catch (e) {
|
||||
// XXX string parsing sucks, maybe
|
||||
// https://jira.mongodb.org/browse/SERVER-3069 will get fixed one day
|
||||
@@ -1215,9 +1240,9 @@ export class AccountsServer extends AccountsCommon {
|
||||
/// CLEAN UP FOR `logoutOtherClients`
|
||||
///
|
||||
|
||||
_deleteSavedTokensForUser(userId, tokensToDelete) {
|
||||
async _deleteSavedTokensForUser(userId, tokensToDelete) {
|
||||
if (tokensToDelete) {
|
||||
this.users.update(userId, {
|
||||
await this.users.update(userId, {
|
||||
$unset: {
|
||||
"services.resume.haveLoginTokensToDelete": 1,
|
||||
"services.resume.loginTokensToDelete": 1
|
||||
@@ -1236,16 +1261,23 @@ export class AccountsServer extends AccountsCommon {
|
||||
// shouldn't happen very often. We shouldn't put a delay here because
|
||||
// that would give a lot of power to an attacker with a stolen login
|
||||
// token and the ability to crash the server.
|
||||
Meteor.startup(() => {
|
||||
this.users.find({
|
||||
Meteor.startup(async () => {
|
||||
await this.users.find({
|
||||
"services.resume.haveLoginTokensToDelete": true
|
||||
}, {fields: {
|
||||
}, {
|
||||
fields: {
|
||||
"services.resume.loginTokensToDelete": 1
|
||||
}}).forEach(user => {
|
||||
}
|
||||
}).forEach(user => {
|
||||
this._deleteSavedTokensForUser(
|
||||
user._id,
|
||||
user.services.resume.loginTokensToDelete
|
||||
);
|
||||
)
|
||||
// We don't need to wait for this to complete.
|
||||
.then(_ => _)
|
||||
.catch(err => {
|
||||
console.log(err);
|
||||
});
|
||||
});
|
||||
});
|
||||
};
|
||||
@@ -1265,7 +1297,7 @@ export class AccountsServer extends AccountsCommon {
|
||||
// @returns {Object} Object with token and id keys, like the result
|
||||
// of the "login" method.
|
||||
//
|
||||
updateOrCreateUserFromExternalService(
|
||||
async updateOrCreateUserFromExternalService(
|
||||
serviceName,
|
||||
serviceData,
|
||||
options
|
||||
@@ -1300,13 +1332,11 @@ export class AccountsServer extends AccountsCommon {
|
||||
} else {
|
||||
selector[serviceIdKey] = serviceData.id;
|
||||
}
|
||||
|
||||
let user = this.users.findOne(selector, {fields: this._options.defaultFieldSelector});
|
||||
|
||||
let user = await this.users.findOne(selector, {fields: this._options.defaultFieldSelector});
|
||||
// Check to see if the developer has a custom way to find the user outside
|
||||
// of the general selectors above.
|
||||
if (!user && this._additionalFindUserOnExternalLogin) {
|
||||
user = this._additionalFindUserOnExternalLogin({serviceName, serviceData, options})
|
||||
user = await this._additionalFindUserOnExternalLogin({serviceName, serviceData, options})
|
||||
}
|
||||
|
||||
// Before continuing, run user hook to see if we should continue
|
||||
@@ -1326,7 +1356,7 @@ export class AccountsServer extends AccountsCommon {
|
||||
}
|
||||
|
||||
if (user) {
|
||||
pinEncryptedFieldsToUser(serviceData, user._id);
|
||||
await pinEncryptedFieldsToUser(serviceData, user._id);
|
||||
|
||||
let setAttrs = {};
|
||||
Object.keys(serviceData).forEach(key =>
|
||||
@@ -1336,7 +1366,7 @@ export class AccountsServer extends AccountsCommon {
|
||||
// XXX Maybe we should re-use the selector above and notice if the update
|
||||
// touches nothing?
|
||||
setAttrs = { ...setAttrs, ...opts };
|
||||
this.users.update(user._id, {
|
||||
await this.users.update(user._id, {
|
||||
$set: setAttrs
|
||||
});
|
||||
|
||||
@@ -1348,9 +1378,10 @@ export class AccountsServer extends AccountsCommon {
|
||||
// Create a new user with the service data.
|
||||
user = {services: {}};
|
||||
user.services[serviceName] = serviceData;
|
||||
const userId = await this.insertUserDoc(opts, user);
|
||||
return {
|
||||
type: serviceName,
|
||||
userId: this.insertUserDoc(opts, user)
|
||||
userId
|
||||
};
|
||||
}
|
||||
};
|
||||
@@ -1532,7 +1563,7 @@ const setupDefaultLoginHandlers = accounts => {
|
||||
};
|
||||
|
||||
// Login handler for resume tokens.
|
||||
const defaultResumeLoginHandler = (accounts, options) => {
|
||||
const defaultResumeLoginHandler = async (accounts, options) => {
|
||||
if (!options.resume)
|
||||
return undefined;
|
||||
|
||||
@@ -1543,7 +1574,7 @@ const defaultResumeLoginHandler = (accounts, options) => {
|
||||
// First look for just the new-style hashed login token, to avoid
|
||||
// sending the unhashed token to the database in a query if we don't
|
||||
// need to.
|
||||
let user = accounts.users.findOne(
|
||||
let user = await accounts.users.findOne(
|
||||
{"services.resume.loginTokens.hashedToken": hashedToken},
|
||||
{fields: {"services.resume.loginTokens.$": 1}});
|
||||
|
||||
@@ -1553,7 +1584,7 @@ const defaultResumeLoginHandler = (accounts, options) => {
|
||||
// the old-style token OR the new-style token, because another
|
||||
// client connection logging in simultaneously might have already
|
||||
// converted the token.
|
||||
user = accounts.users.findOne({
|
||||
user = await accounts.users.findOne({
|
||||
$or: [
|
||||
{"services.resume.loginTokens.hashedToken": hashedToken},
|
||||
{"services.resume.loginTokens.token": options.resume}
|
||||
@@ -1572,13 +1603,13 @@ const defaultResumeLoginHandler = (accounts, options) => {
|
||||
// {hashedToken, when} for a hashed token or {token, when} for an
|
||||
// unhashed token.
|
||||
let oldUnhashedStyleToken;
|
||||
let token = user.services.resume.loginTokens.find(token =>
|
||||
let token = await user.services.resume.loginTokens.find(token =>
|
||||
token.hashedToken === hashedToken
|
||||
);
|
||||
if (token) {
|
||||
oldUnhashedStyleToken = false;
|
||||
} else {
|
||||
token = user.services.resume.loginTokens.find(token =>
|
||||
token = await user.services.resume.loginTokens.find(token =>
|
||||
token.token === options.resume
|
||||
);
|
||||
oldUnhashedStyleToken = true;
|
||||
@@ -1598,7 +1629,7 @@ const defaultResumeLoginHandler = (accounts, options) => {
|
||||
// after we read it). Using $addToSet avoids getting an index
|
||||
// error if another client logging in simultaneously has already
|
||||
// inserted the new hashed token.
|
||||
accounts.users.update(
|
||||
await accounts.users.update(
|
||||
{
|
||||
_id: user._id,
|
||||
"services.resume.loginTokens.token": options.resume
|
||||
@@ -1614,7 +1645,7 @@ const defaultResumeLoginHandler = (accounts, options) => {
|
||||
// Remove the old token *after* adding the new, since otherwise
|
||||
// another client trying to login between our removing the old and
|
||||
// adding the new wouldn't find a token to login with.
|
||||
accounts.users.update(user._id, {
|
||||
await accounts.users.update(user._id, {
|
||||
$pull: {
|
||||
"services.resume.loginTokens": { "token": options.resume }
|
||||
}
|
||||
@@ -1630,49 +1661,50 @@ const defaultResumeLoginHandler = (accounts, options) => {
|
||||
};
|
||||
};
|
||||
|
||||
const expirePasswordToken = (
|
||||
accounts,
|
||||
oldestValidDate,
|
||||
tokenFilter,
|
||||
userId
|
||||
) => {
|
||||
// boolean value used to determine if this method was called from enroll account workflow
|
||||
let isEnroll = false;
|
||||
const userFilter = userId ? {_id: userId} : {};
|
||||
// check if this method was called from enroll account workflow
|
||||
if(tokenFilter['services.password.enroll.reason']) {
|
||||
isEnroll = true;
|
||||
}
|
||||
let resetRangeOr = {
|
||||
$or: [
|
||||
{ "services.password.reset.when": { $lt: oldestValidDate } },
|
||||
{ "services.password.reset.when": { $lt: +oldestValidDate } }
|
||||
]
|
||||
};
|
||||
if(isEnroll) {
|
||||
resetRangeOr = {
|
||||
const expirePasswordToken =
|
||||
async (
|
||||
accounts,
|
||||
oldestValidDate,
|
||||
tokenFilter,
|
||||
userId
|
||||
) => {
|
||||
// boolean value used to determine if this method was called from enroll account workflow
|
||||
let isEnroll = false;
|
||||
const userFilter = userId ? { _id: userId } : {};
|
||||
// check if this method was called from enroll account workflow
|
||||
if (tokenFilter['services.password.enroll.reason']) {
|
||||
isEnroll = true;
|
||||
}
|
||||
let resetRangeOr = {
|
||||
$or: [
|
||||
{ "services.password.enroll.when": { $lt: oldestValidDate } },
|
||||
{ "services.password.enroll.when": { $lt: +oldestValidDate } }
|
||||
{ "services.password.reset.when": { $lt: oldestValidDate } },
|
||||
{ "services.password.reset.when": { $lt: +oldestValidDate } }
|
||||
]
|
||||
};
|
||||
}
|
||||
const expireFilter = { $and: [tokenFilter, resetRangeOr] };
|
||||
if(isEnroll) {
|
||||
accounts.users.update({...userFilter, ...expireFilter}, {
|
||||
$unset: {
|
||||
"services.password.enroll": ""
|
||||
}
|
||||
}, { multi: true });
|
||||
} else {
|
||||
accounts.users.update({...userFilter, ...expireFilter}, {
|
||||
$unset: {
|
||||
"services.password.reset": ""
|
||||
}
|
||||
}, { multi: true });
|
||||
}
|
||||
if (isEnroll) {
|
||||
resetRangeOr = {
|
||||
$or: [
|
||||
{ "services.password.enroll.when": { $lt: oldestValidDate } },
|
||||
{ "services.password.enroll.when": { $lt: +oldestValidDate } }
|
||||
]
|
||||
};
|
||||
}
|
||||
const expireFilter = { $and: [tokenFilter, resetRangeOr] };
|
||||
if (isEnroll) {
|
||||
await accounts.users.update({ ...userFilter, ...expireFilter }, {
|
||||
$unset: {
|
||||
"services.password.enroll": ""
|
||||
}
|
||||
}, { multi: true });
|
||||
} else {
|
||||
await accounts.users.update({ ...userFilter, ...expireFilter }, {
|
||||
$unset: {
|
||||
"services.password.reset": ""
|
||||
}
|
||||
}, { multi: true });
|
||||
}
|
||||
|
||||
};
|
||||
};
|
||||
|
||||
const setExpireTokensInterval = accounts => {
|
||||
accounts.expireTokenInterval = Meteor.setInterval(() => {
|
||||
@@ -1682,17 +1714,7 @@ const setExpireTokensInterval = accounts => {
|
||||
}, EXPIRE_TOKENS_INTERVAL_MS);
|
||||
};
|
||||
|
||||
///
|
||||
/// OAuth Encryption Support
|
||||
///
|
||||
|
||||
const OAuthEncryption =
|
||||
Package["oauth-encryption"] &&
|
||||
Package["oauth-encryption"].OAuthEncryption;
|
||||
|
||||
const usingOAuthEncryption = () => {
|
||||
return OAuthEncryption && OAuthEncryption.keyIsLoaded();
|
||||
};
|
||||
const OAuthEncryption = Package["oauth-encryption"]?.OAuthEncryption;
|
||||
|
||||
// OAuth service data is temporarily stored in the pending credentials
|
||||
// collection during the oauth authentication process. Sensitive data
|
||||
@@ -1704,44 +1726,12 @@ const usingOAuthEncryption = () => {
|
||||
const pinEncryptedFieldsToUser = (serviceData, userId) => {
|
||||
Object.keys(serviceData).forEach(key => {
|
||||
let value = serviceData[key];
|
||||
if (OAuthEncryption && OAuthEncryption.isSealed(value))
|
||||
if (OAuthEncryption?.isSealed(value))
|
||||
value = OAuthEncryption.seal(OAuthEncryption.open(value), userId);
|
||||
serviceData[key] = value;
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
// Encrypt unencrypted login service secrets when oauth-encryption is
|
||||
// added.
|
||||
//
|
||||
// XXX For the oauthSecretKey to be available here at startup, the
|
||||
// developer must call Accounts.config({oauthSecretKey: ...}) at load
|
||||
// time, instead of in a Meteor.startup block, because the startup
|
||||
// block in the app code will run after this accounts-base startup
|
||||
// block. Perhaps we need a post-startup callback?
|
||||
|
||||
Meteor.startup(() => {
|
||||
if (! usingOAuthEncryption()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const { ServiceConfiguration } = Package['service-configuration'];
|
||||
|
||||
ServiceConfiguration.configurations.find({
|
||||
$and: [{
|
||||
secret: { $exists: true }
|
||||
}, {
|
||||
"secret.algorithm": { $exists: false }
|
||||
}]
|
||||
}).forEach(config => {
|
||||
ServiceConfiguration.configurations.update(config._id, {
|
||||
$set: {
|
||||
secret: OAuthEncryption.seal(config.secret)
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
// XXX see comment on Accounts.createUser in passwords_server about adding a
|
||||
// second "server options" argument.
|
||||
const defaultCreateUserHook = (options, user) => {
|
||||
@@ -1781,7 +1771,7 @@ function defaultValidateNewUserHook(user) {
|
||||
}
|
||||
}
|
||||
|
||||
const setupUsersCollection = users => {
|
||||
const setupUsersCollection = async users => {
|
||||
///
|
||||
/// RESTRICTING WRITES TO USER OBJECTS
|
||||
///
|
||||
@@ -1807,21 +1797,21 @@ const setupUsersCollection = users => {
|
||||
});
|
||||
|
||||
/// DEFAULT INDEXES ON USERS
|
||||
users.createIndex('username', { unique: true, sparse: true });
|
||||
users.createIndex('emails.address', { unique: true, sparse: true });
|
||||
users.createIndex('services.resume.loginTokens.hashedToken',
|
||||
await users.createIndex('username', { unique: true, sparse: true });
|
||||
await users.createIndex('emails.address', { unique: true, sparse: true });
|
||||
await users.createIndex('services.resume.loginTokens.hashedToken',
|
||||
{ unique: true, sparse: true });
|
||||
users.createIndex('services.resume.loginTokens.token',
|
||||
await users.createIndex('services.resume.loginTokens.token',
|
||||
{ unique: true, sparse: true });
|
||||
// For taking care of logoutOtherClients calls that crashed before the
|
||||
// tokens were deleted.
|
||||
users.createIndex('services.resume.haveLoginTokensToDelete',
|
||||
await users.createIndex('services.resume.haveLoginTokensToDelete',
|
||||
{ sparse: true });
|
||||
// For expiring login tokens
|
||||
users.createIndex("services.resume.loginTokens.when", { sparse: true });
|
||||
await users.createIndex("services.resume.loginTokens.when", { sparse: true });
|
||||
// For expiring password tokens
|
||||
users.createIndex('services.password.reset.when', { sparse: true });
|
||||
users.createIndex('services.password.enroll.when', { sparse: true });
|
||||
await users.createIndex('services.password.reset.when', { sparse: true });
|
||||
await users.createIndex('services.password.enroll.when', { sparse: true });
|
||||
};
|
||||
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,25 +1,25 @@
|
||||
const getTokenFromSecret = ({ selector, secret: secretParam }) => {
|
||||
const getTokenFromSecret = async ({ selector, secret: secretParam }) => {
|
||||
let secret = secretParam;
|
||||
|
||||
if (!secret) {
|
||||
const { services: { twoFactorAuthentication } = {} } =
|
||||
Meteor.users.findOne(selector) || {};
|
||||
await Meteor.users.findOne(selector) || {};
|
||||
if (!twoFactorAuthentication) {
|
||||
throw new Meteor.Error(500, 'twoFactorAuthentication not set.');
|
||||
}
|
||||
secret = twoFactorAuthentication.secret;
|
||||
}
|
||||
const { token } = Accounts._generate2faToken(secret);
|
||||
const { token } = await Accounts._generate2faToken(secret);
|
||||
|
||||
return token;
|
||||
};
|
||||
|
||||
Meteor.methods({
|
||||
removeAccountsTestUser(username) {
|
||||
Meteor.users.remove({ username });
|
||||
async removeAccountsTestUser(username) {
|
||||
await Meteor.users.remove({ username });
|
||||
},
|
||||
forceEnableUser2fa(selector, secret) {
|
||||
Meteor.users.update(
|
||||
async forceEnableUser2fa(selector, secret) {
|
||||
await Meteor.users.update(
|
||||
selector,
|
||||
{
|
||||
$set: {
|
||||
@@ -30,7 +30,7 @@ Meteor.methods({
|
||||
},
|
||||
}
|
||||
);
|
||||
return getTokenFromSecret({ selector, secret });
|
||||
return await getTokenFromSecret({ selector, secret });
|
||||
},
|
||||
getTokenFromSecret,
|
||||
});
|
||||
|
||||
3
packages/accounts-base/package-types.json
Normal file
3
packages/accounts-base/package-types.json
Normal file
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"typesEntry": "accounts-base.d.ts"
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
Package.describe({
|
||||
summary: 'A user account system',
|
||||
version: '2.2.4',
|
||||
version: '2.2.6',
|
||||
});
|
||||
|
||||
Package.onUse(api => {
|
||||
@@ -15,10 +15,6 @@ Package.onUse(api => {
|
||||
api.use('reactive-var', 'client');
|
||||
api.use('url', ['client', 'server']);
|
||||
|
||||
// use unordered to work around a circular dependency
|
||||
// (service-configuration needs Accounts.connection)
|
||||
api.use('service-configuration', ['client', 'server'], { unordered: true });
|
||||
|
||||
// needed for getting the currently logged-in user and handling reconnects
|
||||
api.use('ddp', ['client', 'server']);
|
||||
|
||||
@@ -48,6 +44,8 @@ Package.onUse(api => {
|
||||
// modules that import the accounts-base package.
|
||||
api.mainModule('server_main.js', 'server');
|
||||
api.mainModule('client_main.js', 'client');
|
||||
|
||||
api.addAssets('accounts-base.d.ts', 'server');
|
||||
});
|
||||
|
||||
Package.onTest(api => {
|
||||
|
||||
@@ -5,7 +5,8 @@ import { AccountsServer } from "./accounts_server.js";
|
||||
* @summary The namespace for all server-side accounts-related methods.
|
||||
*/
|
||||
Accounts = new AccountsServer(Meteor.server);
|
||||
|
||||
// TODO[FIBERS]: I need TLA
|
||||
Accounts.init().then()
|
||||
// Users table. Don't use the normal autopublish, since we want to hide
|
||||
// some fields. Code to autopublish this is in accounts_server.js.
|
||||
// XXX Allow users to configure this collection name.
|
||||
@@ -15,7 +16,7 @@ Accounts = new AccountsServer(Meteor.server);
|
||||
* @locus Anywhere
|
||||
* @type {Mongo.Collection}
|
||||
* @importFromPackage meteor
|
||||
*/
|
||||
*/
|
||||
Meteor.users = Accounts.users;
|
||||
|
||||
export {
|
||||
|
||||
@@ -1,3 +1,24 @@
|
||||
import { Meteor } from 'meteor/meteor';
|
||||
|
||||
// TODO get from account-base
|
||||
// config option keys
|
||||
const VALID_CONFIG_KEYS = [
|
||||
'sendVerificationEmail',
|
||||
'forbidClientAccountCreation',
|
||||
'passwordEnrollTokenExpiration',
|
||||
'passwordEnrollTokenExpirationInDays',
|
||||
'restrictCreationByEmailDomain',
|
||||
'loginExpirationInDays',
|
||||
'loginExpiration',
|
||||
'passwordResetTokenExpirationInDays',
|
||||
'passwordResetTokenExpiration',
|
||||
'ambiguousErrorMessages',
|
||||
'bcryptRounds',
|
||||
'defaultFieldSelector',
|
||||
'loginTokenExpirationHours',
|
||||
'tokenSequenceLength',
|
||||
];
|
||||
|
||||
Accounts.oauth = {};
|
||||
|
||||
const services = {};
|
||||
@@ -31,3 +52,37 @@ Accounts.oauth.unregisterService = name => {
|
||||
};
|
||||
|
||||
Accounts.oauth.serviceNames = () => Object.keys(services);
|
||||
|
||||
// loginServiceConfiguration and ConfigError are maintained for backwards compatibility
|
||||
Meteor.startup(() => {
|
||||
const { ServiceConfiguration } = Package['service-configuration'];
|
||||
Accounts.loginServiceConfiguration = ServiceConfiguration.configurations;
|
||||
Accounts.ConfigError = ServiceConfiguration.ConfigError;
|
||||
|
||||
const settings = Meteor.settings?.packages?.['accounts-base'];
|
||||
if (settings) {
|
||||
if (settings.oauthSecretKey) {
|
||||
if (!Package['oauth-encryption']) {
|
||||
throw new Error(
|
||||
'The oauth-encryption package must be loaded to set oauthSecretKey'
|
||||
);
|
||||
}
|
||||
Package['oauth-encryption'].OAuthEncryption.loadKey(
|
||||
settings.oauthSecretKey
|
||||
);
|
||||
delete settings.oauthSecretKey;
|
||||
}
|
||||
// Validate config options keys
|
||||
Object.keys(settings).forEach(key => {
|
||||
if (!VALID_CONFIG_KEYS.includes(key)) {
|
||||
// TODO Consider just logging a debug message instead to allow for additional keys in the settings here?
|
||||
throw new Meteor.Error(
|
||||
`Accounts configuration: Invalid key: ${key}`
|
||||
);
|
||||
} else {
|
||||
// set values in Accounts._options
|
||||
Accounts._options[key] = settings[key];
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import { Meteor } from 'meteor/meteor';
|
||||
|
||||
// Listen to calls to `login` with an oauth option set. This is where
|
||||
// users actually get logged in to meteor via oauth.
|
||||
Accounts.registerLoginHandler(options => {
|
||||
@@ -55,3 +57,44 @@ Accounts.registerLoginHandler(options => {
|
||||
return Accounts.updateOrCreateUserFromExternalService(result.serviceName, result.serviceData, result.options);
|
||||
}
|
||||
});
|
||||
|
||||
///
|
||||
/// OAuth Encryption Support
|
||||
///
|
||||
|
||||
const OAuthEncryption = Package["oauth-encryption"]?.OAuthEncryption;
|
||||
|
||||
const usingOAuthEncryption = () => {
|
||||
return OAuthEncryption?.keyIsLoaded();
|
||||
};
|
||||
|
||||
// Encrypt unencrypted login service secrets when oauth-encryption is
|
||||
// added.
|
||||
//
|
||||
// XXX For the oauthSecretKey to be available here at startup, the
|
||||
// developer must call Accounts.config({oauthSecretKey: ...}) at load
|
||||
// time, instead of in a Meteor.startup block, because the startup
|
||||
// block in the app code will run after this accounts-base startup
|
||||
// block. Perhaps we need a post-startup callback?
|
||||
|
||||
Meteor.startup(() => {
|
||||
if (! usingOAuthEncryption()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const { ServiceConfiguration } = Package['service-configuration'];
|
||||
|
||||
ServiceConfiguration.configurations.find({
|
||||
$and: [{
|
||||
secret: { $exists: true }
|
||||
}, {
|
||||
"secret.algorithm": { $exists: false }
|
||||
}]
|
||||
}).forEach(config => {
|
||||
ServiceConfiguration.configurations.update(config._id, {
|
||||
$set: {
|
||||
secret: OAuthEncryption.seal(config.secret)
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
Package.describe({
|
||||
summary: "Common code for OAuth-based login services",
|
||||
version: "1.4.1",
|
||||
version: "1.4.2",
|
||||
});
|
||||
|
||||
Package.onUse(api => {
|
||||
@@ -9,6 +9,11 @@ Package.onUse(api => {
|
||||
api.use(['accounts-base', 'ecmascript'], ['client', 'server']);
|
||||
// Export Accounts (etc) to packages using this one.
|
||||
api.imply('accounts-base', ['client', 'server']);
|
||||
|
||||
// use unordered to work around a circular dependency
|
||||
// (service-configuration needs Accounts.connection)
|
||||
api.use('service-configuration', ['client', 'server'], { unordered: true });
|
||||
|
||||
api.use('oauth');
|
||||
|
||||
api.addFiles('oauth_common.js');
|
||||
|
||||
@@ -5,7 +5,7 @@ Package.describe({
|
||||
// 2.2.x in the future. The version was also bumped to 2.0.0 temporarily
|
||||
// during the Meteor 1.5.1 release process, so versions 2.0.0-beta.2
|
||||
// through -beta.5 and -rc.0 have already been published.
|
||||
version: '2.3.1',
|
||||
version: '2.3.2',
|
||||
});
|
||||
|
||||
Npm.depends({
|
||||
|
||||
@@ -7,12 +7,10 @@ const reportError = (error, callback) => {
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
const internalLoginWithPassword = ({ selector, password, code, callback }) => {
|
||||
if (typeof selector === 'string')
|
||||
if (!selector.includes('@')) selector = { username: selector };
|
||||
else selector = { email: selector };
|
||||
|
||||
Accounts.callLoginMethod({
|
||||
methodArguments: [
|
||||
{
|
||||
|
||||
@@ -98,7 +98,7 @@ const checkPasswordAsync = async (user, password) => {
|
||||
return result;
|
||||
};
|
||||
|
||||
const checkPassword = async (user, password) => {
|
||||
const checkPassword = (user, password) => {
|
||||
return Promise.await(checkPasswordAsync(user, password));
|
||||
};
|
||||
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
Package.describe({
|
||||
name: "babel-compiler",
|
||||
summary: "Parser/transpiler for ECMAScript 2015+ syntax",
|
||||
version: '7.9.0'
|
||||
version: '7.10.1'
|
||||
});
|
||||
|
||||
Npm.depends({
|
||||
'@meteorjs/babel': '7.16.0-beta.7',
|
||||
'@meteorjs/babel': '7.17.2-beta.0',
|
||||
'json5': '2.1.1'
|
||||
});
|
||||
|
||||
|
||||
38
packages/browser-policy-common/browser-policy-common.d.ts
vendored
Normal file
38
packages/browser-policy-common/browser-policy-common.d.ts
vendored
Normal file
@@ -0,0 +1,38 @@
|
||||
export namespace BrowserPolicy {
|
||||
var framing: {
|
||||
disallow(): void;
|
||||
restrictToOrigin(origin: string): void;
|
||||
allowAll(): void;
|
||||
};
|
||||
|
||||
var content: {
|
||||
allowEval(): void;
|
||||
allowInlineStyles(): void;
|
||||
allowInlineScripts(): void;
|
||||
allowSameOriginForAll(): void;
|
||||
allowDataUrlForAll(): void;
|
||||
allowOriginForAll(origin: string): void;
|
||||
allowImageOrigin(origin: string): void;
|
||||
allowMediaOrigin(origin: string): void;
|
||||
allowFontOrigin(origin: string): void;
|
||||
allowStyleOrigin(origin: string): void;
|
||||
allowScriptOrigin(origin: string): void;
|
||||
allowFrameOrigin(origin: string): void;
|
||||
allowFrameAncestorsOrigin(origin: string): void;
|
||||
allowContentTypeSniffing(): void;
|
||||
allowAllContentOrigin(): void;
|
||||
allowAllContentDataUrl(): void;
|
||||
allowAllContentSameOrigin(): void;
|
||||
allowConnectOrigin(origin: string): void;
|
||||
allowObjectOrigin(origin: string): void;
|
||||
|
||||
disallowAll(): void;
|
||||
disallowInlineStyles(): void;
|
||||
disallowEval(): void;
|
||||
disallowInlineScripts(): void;
|
||||
disallowFont(): void;
|
||||
disallowObject(): void;
|
||||
disallowAllContent(): void;
|
||||
disallowConnect(): void;
|
||||
};
|
||||
}
|
||||
3
packages/browser-policy-common/package-types.json
Normal file
3
packages/browser-policy-common/package-types.json
Normal file
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"typesEntry": "browser-policy-common.d.ts"
|
||||
}
|
||||
@@ -1,10 +1,11 @@
|
||||
Package.describe({
|
||||
summary: "Common code for browser-policy packages",
|
||||
version: "1.0.11"
|
||||
version: "1.0.12"
|
||||
});
|
||||
|
||||
Package.onUse(function (api) {
|
||||
api.use('webapp', 'server');
|
||||
api.addFiles('browser-policy-common.js', 'server');
|
||||
api.export('BrowserPolicy', 'server');
|
||||
api.addAssets('browser-policy-common.d.ts', 'server');
|
||||
});
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
Package.describe({
|
||||
summary: "Restrict which websites can frame your app",
|
||||
version: "1.1.0"
|
||||
version: '1.1.1'
|
||||
});
|
||||
|
||||
Package.onUse(function (api) {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
Package.describe({
|
||||
summary: "Configure security policies enforced by the browser",
|
||||
version: "1.1.0"
|
||||
version: '1.1.1'
|
||||
});
|
||||
|
||||
Package.onUse(function (api) {
|
||||
|
||||
92
packages/check/check.d.ts
vendored
Normal file
92
packages/check/check.d.ts
vendored
Normal file
@@ -0,0 +1,92 @@
|
||||
/**
|
||||
* The namespace for all Match types and methods.
|
||||
*/
|
||||
export namespace Match {
|
||||
interface Matcher<T> {
|
||||
_meteorCheckMatcherBrand: void;
|
||||
}
|
||||
// prettier-ignore
|
||||
export type Pattern =
|
||||
typeof String |
|
||||
typeof Number |
|
||||
typeof Boolean |
|
||||
typeof Object |
|
||||
typeof Function |
|
||||
(new (...args: any[]) => any) |
|
||||
undefined | null | string | number | boolean |
|
||||
[Pattern] |
|
||||
{[key: string]: Pattern} |
|
||||
Matcher<any>;
|
||||
// prettier-ignore
|
||||
export type PatternMatch<T extends Pattern> =
|
||||
T extends Matcher<infer U> ? U :
|
||||
T extends typeof String ? string :
|
||||
T extends typeof Number ? number :
|
||||
T extends typeof Boolean ? boolean :
|
||||
T extends typeof Object ? object :
|
||||
T extends typeof Function ? Function :
|
||||
T extends undefined | null | string | number | boolean ? T :
|
||||
T extends new (...args: any[]) => infer U ? U :
|
||||
T extends [Pattern] ? PatternMatch<T[0]>[] :
|
||||
T extends {[key: string]: Pattern} ? {[K in keyof T]: PatternMatch<T[K]>} :
|
||||
unknown;
|
||||
|
||||
/** Matches any value. */
|
||||
var Any: Matcher<any>;
|
||||
/** Matches a signed 32-bit integer. Doesn’t match `Infinity`, `-Infinity`, or `NaN`. */
|
||||
var Integer: Matcher<number>;
|
||||
|
||||
/**
|
||||
* Matches either `undefined`, `null`, or pattern. If used in an object, matches only if the key is not set as opposed to the value being set to `undefined` or `null`. This set of conditions
|
||||
* was chosen because `undefined` arguments to Meteor Methods are converted to `null` when sent over the wire.
|
||||
*/
|
||||
function Maybe<T extends Pattern>(
|
||||
pattern: T
|
||||
): Matcher<PatternMatch<T> | undefined | null>;
|
||||
|
||||
/** Behaves like `Match.Maybe` except it doesn’t accept `null`. If used in an object, the behavior is identical to `Match.Maybe`. */
|
||||
function Optional<T extends Pattern>(
|
||||
pattern: T
|
||||
): Matcher<PatternMatch<T> | undefined>;
|
||||
|
||||
/** Matches an Object with the given keys; the value may also have other keys with arbitrary values. */
|
||||
function ObjectIncluding<T extends { [key: string]: Pattern }>(
|
||||
dico: T
|
||||
): Matcher<PatternMatch<T>>;
|
||||
|
||||
/** Matches any value that matches at least one of the provided patterns. */
|
||||
function OneOf<T extends Pattern[]>(
|
||||
...patterns: T
|
||||
): Matcher<PatternMatch<T[number]>>;
|
||||
|
||||
/**
|
||||
* Calls the function condition with the value as the argument. If condition returns true, this matches. If condition throws a `Match.Error` or returns false, this fails. If condition throws
|
||||
* any other error, that error is thrown from the call to `check` or `Match.test`.
|
||||
*/
|
||||
function Where<T>(condition: (val: any) => val is T): Matcher<T>;
|
||||
function Where(condition: (val: any) => boolean): Matcher<any>;
|
||||
|
||||
/**
|
||||
* Returns true if the value matches the pattern.
|
||||
* @param value The value to check
|
||||
* @param pattern The pattern to match `value` against
|
||||
*/
|
||||
function test<T extends Pattern>(
|
||||
value: any,
|
||||
pattern: T
|
||||
): value is PatternMatch<T>;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check that a value matches a pattern.
|
||||
* If the value does not match the pattern, throw a `Match.Error`.
|
||||
*
|
||||
* Particularly useful to assert that arguments to a function have the right
|
||||
* types and structure.
|
||||
* @param value The value to check
|
||||
* @param pattern The pattern to match `value` against
|
||||
*/
|
||||
export declare function check<T extends Match.Pattern>(
|
||||
value: any,
|
||||
pattern: T
|
||||
): asserts value is Match.PatternMatch<T>;
|
||||
3
packages/check/package-types.json
Normal file
3
packages/check/package-types.json
Normal file
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"typesEntry": "check.d.ts"
|
||||
}
|
||||
@@ -1,12 +1,14 @@
|
||||
Package.describe({
|
||||
summary: 'Check whether a value matches a pattern',
|
||||
version: '1.3.1',
|
||||
version: '1.3.2',
|
||||
});
|
||||
|
||||
Package.onUse(api => {
|
||||
api.use('ecmascript');
|
||||
api.use('ejson');
|
||||
|
||||
api.addAssets('check.d.ts', 'server');
|
||||
|
||||
api.mainModule('match.js');
|
||||
|
||||
api.export('check');
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
node_modules
|
||||
@@ -1,7 +0,0 @@
|
||||
This directory and the files immediately inside it are automatically generated
|
||||
when you change this package's NPM dependencies. Commit the files in this
|
||||
directory (npm-shrinkwrap.json, .gitignore, and this README) to source control
|
||||
so that others run the same versions of sub-dependencies.
|
||||
|
||||
You should NOT check in the node_modules directory that Meteor automatically
|
||||
creates; if you are using git, the .gitignore file tells git to ignore it.
|
||||
@@ -1,25 +0,0 @@
|
||||
{
|
||||
"lockfileVersion": 1,
|
||||
"dependencies": {
|
||||
"@sinonjs/commons": {
|
||||
"version": "1.8.3",
|
||||
"resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-1.8.3.tgz",
|
||||
"integrity": "sha512-xkNcLAn/wZaX14RPlwizcKicDk9G3F8m2nU3L7Ukm5zBgTwiT0wsoFAHx9Jq56fJA1z/7uKGtCRu16sOUCLIHQ=="
|
||||
},
|
||||
"@sinonjs/fake-timers": {
|
||||
"version": "7.0.5",
|
||||
"resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-7.0.5.tgz",
|
||||
"integrity": "sha512-fUt6b15bjV/VW93UP5opNXJxdwZSbK1EdiwnhN7XrQrcpaOhMJpZ/CjwFpM3THpxwA+YviBUJKSuEqKlCK5alw=="
|
||||
},
|
||||
"es6-promise": {
|
||||
"version": "4.2.8",
|
||||
"resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-4.2.8.tgz",
|
||||
"integrity": "sha512-HJDGx5daxeIvxdBxvG2cb9g4tEvwIk3i8+nhX0yGrYmZUzbkdg8QbDevheDB8gd0//uPj4c1EQua8Q+MViT0/w=="
|
||||
},
|
||||
"type-detect": {
|
||||
"version": "4.0.8",
|
||||
"resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz",
|
||||
"integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g=="
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -696,7 +696,14 @@ export class Connection {
|
||||
invocation
|
||||
);
|
||||
try {
|
||||
stubOptions.stubReturnValue = await stubInvocation();
|
||||
const resultOrThenable = stubInvocation();
|
||||
const isThenable =
|
||||
resultOrThenable && typeof resultOrThenable.then === 'function';
|
||||
if (isThenable) {
|
||||
stubOptions.stubReturnValue = await resultOrThenable;
|
||||
} else {
|
||||
stubOptions.stubReturnValue = resultOrThenable;
|
||||
}
|
||||
} finally {
|
||||
DDP._CurrentMethodInvocation._set(currentContext);
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
Package.describe({
|
||||
summary: "Meteor's latency-compensated distributed data client",
|
||||
version: '2.6.0',
|
||||
version: '2.6.1',
|
||||
documentation: null
|
||||
});
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -59,28 +59,29 @@ if (Meteor.isServer) {
|
||||
// other.
|
||||
const waiters = Object.create(null);
|
||||
|
||||
const Future = Npm.require('fibers/future');
|
||||
|
||||
const returnThroughFuture = function(token, returnValue) {
|
||||
// Make sure that when we call return, the fields are already cleared.
|
||||
const record = waiters[token];
|
||||
if (!record) return;
|
||||
delete waiters[token];
|
||||
record.future['return'](returnValue);
|
||||
record.future(returnValue);
|
||||
};
|
||||
|
||||
Meteor.methods({
|
||||
delayedTrue: function(token) {
|
||||
check(token, String);
|
||||
const record = (waiters[token] = {
|
||||
future: new Future(),
|
||||
|
||||
let resolver;
|
||||
const promise = new Promise(res => resolver = res);
|
||||
waiters[token] = {
|
||||
future: resolver,
|
||||
timer: Meteor.setTimeout(function() {
|
||||
returnThroughFuture(token, true);
|
||||
}, 1000)
|
||||
});
|
||||
};
|
||||
|
||||
this.unblock();
|
||||
return record.future.wait();
|
||||
return promise;
|
||||
},
|
||||
makeDelayedTrueImmediatelyReturnFalse: function(token) {
|
||||
check(token, String);
|
||||
@@ -108,8 +109,8 @@ Ledger.allow({
|
||||
fetch: []
|
||||
});
|
||||
|
||||
Meteor.startup(function() {
|
||||
if (Meteor.isServer) Ledger.remove({}); // XXX can this please be Ledger.remove()?
|
||||
Meteor.startup(async function() {
|
||||
if (Meteor.isServer) await Ledger.removeAsync({});
|
||||
});
|
||||
|
||||
if (Meteor.isServer)
|
||||
@@ -119,14 +120,14 @@ if (Meteor.isServer)
|
||||
});
|
||||
|
||||
Meteor.methods({
|
||||
'ledger/transfer': function(world, from_name, to_name, amount, cheat) {
|
||||
'ledger/transfer': async function(world, from_name, to_name, amount, cheat) {
|
||||
check(world, String);
|
||||
check(from_name, String);
|
||||
check(to_name, String);
|
||||
check(amount, Number);
|
||||
check(cheat, Match.Optional(Boolean));
|
||||
const from = Ledger.findOne({ name: from_name, world: world });
|
||||
const to = Ledger.findOne({ name: to_name, world: world });
|
||||
const from = await Ledger.findOneAsync({ name: from_name, world: world });
|
||||
const to = await Ledger.findOneAsync({ name: to_name, world: world });
|
||||
|
||||
if (Meteor.isServer) cheat = false;
|
||||
|
||||
@@ -145,8 +146,8 @@ Meteor.methods({
|
||||
if (from.balance < amount && !cheat)
|
||||
throw new Meteor.Error(409, 'Insufficient funds');
|
||||
|
||||
Ledger.update(from._id, { $inc: { balance: -amount } });
|
||||
Ledger.update(to._id, { $inc: { balance: amount } });
|
||||
await Ledger.updateAsync({_id: from._id}, { $inc: { balance: -amount } });
|
||||
await Ledger.updateAsync({_id: to._id, }, { $inc: { balance: amount } });
|
||||
}
|
||||
});
|
||||
|
||||
@@ -156,56 +157,57 @@ Meteor.methods({
|
||||
|
||||
objectsWithUsers = new Mongo.Collection('objectsWithUsers');
|
||||
|
||||
if (Meteor.isServer) {
|
||||
objectsWithUsers.remove({});
|
||||
objectsWithUsers.insert({ name: 'owned by none', ownerUserIds: [null] });
|
||||
objectsWithUsers.insert({ name: 'owned by one - a', ownerUserIds: ['1'] });
|
||||
objectsWithUsers.insert({
|
||||
name: 'owned by one/two - a',
|
||||
ownerUserIds: ['1', '2']
|
||||
});
|
||||
objectsWithUsers.insert({
|
||||
name: 'owned by one/two - b',
|
||||
ownerUserIds: ['1', '2']
|
||||
});
|
||||
objectsWithUsers.insert({ name: 'owned by two - a', ownerUserIds: ['2'] });
|
||||
objectsWithUsers.insert({ name: 'owned by two - b', ownerUserIds: ['2'] });
|
||||
Meteor.startup(async function() {
|
||||
if (Meteor.isServer) {
|
||||
await objectsWithUsers.removeAsync({});
|
||||
await objectsWithUsers.insertAsync({name: 'owned by none', ownerUserIds: [null]});
|
||||
await objectsWithUsers.insertAsync({name: 'owned by one - a', ownerUserIds: ['1']});
|
||||
await objectsWithUsers.insertAsync({
|
||||
name: 'owned by one/two - a',
|
||||
ownerUserIds: ['1', '2']
|
||||
});
|
||||
await objectsWithUsers.insertAsync({
|
||||
name: 'owned by one/two - b',
|
||||
ownerUserIds: ['1', '2']
|
||||
});
|
||||
await objectsWithUsers.insertAsync({name: 'owned by two - a', ownerUserIds: ['2']});
|
||||
await objectsWithUsers.insertAsync({name: 'owned by two - b', ownerUserIds: ['2']});
|
||||
|
||||
Meteor.publish('objectsWithUsers', function() {
|
||||
return objectsWithUsers.find(
|
||||
{ ownerUserIds: this.userId },
|
||||
{ fields: { ownerUserIds: 0 } }
|
||||
);
|
||||
});
|
||||
|
||||
(function() {
|
||||
const userIdWhenStopped = Object.create(null);
|
||||
Meteor.publish('recordUserIdOnStop', function(key) {
|
||||
check(key, String);
|
||||
const self = this;
|
||||
self.onStop(function() {
|
||||
userIdWhenStopped[key] = self.userId;
|
||||
});
|
||||
Meteor.publish('objectsWithUsers', function () {
|
||||
return objectsWithUsers.find(
|
||||
{ownerUserIds: this.userId},
|
||||
{fields: {ownerUserIds: 0}}
|
||||
);
|
||||
});
|
||||
|
||||
Meteor.methods({
|
||||
userIdWhenStopped: function(key) {
|
||||
(function () {
|
||||
const userIdWhenStopped = Object.create(null);
|
||||
Meteor.publish('recordUserIdOnStop', function (key) {
|
||||
check(key, String);
|
||||
return userIdWhenStopped[key];
|
||||
}
|
||||
});
|
||||
})();
|
||||
}
|
||||
const self = this;
|
||||
self.onStop(function () {
|
||||
userIdWhenStopped[key] = self.userId;
|
||||
});
|
||||
});
|
||||
|
||||
Meteor.methods({
|
||||
userIdWhenStopped: function (key) {
|
||||
check(key, String);
|
||||
return userIdWhenStopped[key];
|
||||
}
|
||||
});
|
||||
})();
|
||||
}
|
||||
});
|
||||
/*****/
|
||||
|
||||
/// Helper for "livedata - setUserId fails when called on server"
|
||||
|
||||
if (Meteor.isServer) {
|
||||
Meteor.startup(function() {
|
||||
Meteor.startup(async function() {
|
||||
errorThrownWhenCallingSetUserIdDirectlyOnServer = null;
|
||||
try {
|
||||
Meteor.call('setUserId', '1000');
|
||||
await Meteor.callAsync('setUserId', '1000');
|
||||
} catch (e) {
|
||||
errorThrownWhenCallingSetUserIdDirectlyOnServer = e;
|
||||
}
|
||||
@@ -331,36 +333,38 @@ if (Meteor.isServer) {
|
||||
One = new Mongo.Collection('collectionOne');
|
||||
Two = new Mongo.Collection('collectionTwo');
|
||||
|
||||
if (Meteor.isServer) {
|
||||
One.remove({});
|
||||
One.insert({ name: 'value1' });
|
||||
One.insert({ name: 'value2' });
|
||||
Meteor.startup(async () => {
|
||||
if (Meteor.isServer) {
|
||||
await One.removeAsync({});
|
||||
await One.insertAsync({ name: 'value1' });
|
||||
await One.insertAsync({ name: 'value2' });
|
||||
|
||||
Two.remove({});
|
||||
Two.insert({ name: 'value3' });
|
||||
Two.insert({ name: 'value4' });
|
||||
Two.insert({ name: 'value5' });
|
||||
await Two.removeAsync({});
|
||||
await Two.insertAsync({ name: 'value3' });
|
||||
await Two.insertAsync({ name: 'value4' });
|
||||
await Two.insertAsync({ name: 'value5' });
|
||||
|
||||
Meteor.publish('multiPublish', function(options) {
|
||||
// See below to see what options are accepted.
|
||||
check(options, Object);
|
||||
if (options.normal) {
|
||||
return [One.find(), Two.find()];
|
||||
} else if (options.dup) {
|
||||
// Suppress the log of the expected internal error.
|
||||
Meteor._suppress_log(1);
|
||||
return [
|
||||
One.find(),
|
||||
One.find({ name: 'value2' }), // multiple cursors for one collection - error
|
||||
Two.find()
|
||||
];
|
||||
} else if (options.notCursor) {
|
||||
// Suppress the log of the expected internal error.
|
||||
Meteor._suppress_log(1);
|
||||
return [One.find(), 'not a cursor', Two.find()];
|
||||
} else throw 'unexpected options';
|
||||
});
|
||||
}
|
||||
Meteor.publish('multiPublish', function(options) {
|
||||
// See below to see what options are accepted.
|
||||
check(options, Object);
|
||||
if (options.normal) {
|
||||
return [One.find(), Two.find()];
|
||||
} else if (options.dup) {
|
||||
// Suppress the log of the expected internal error.
|
||||
Meteor._suppress_log(1);
|
||||
return [
|
||||
One.find(),
|
||||
One.find({ name: 'value2' }), // multiple cursors for one collection - error
|
||||
Two.find(),
|
||||
];
|
||||
} else if (options.notCursor) {
|
||||
// Suppress the log of the expected internal error.
|
||||
Meteor._suppress_log(1);
|
||||
return [One.find(), 'not a cursor', Two.find()];
|
||||
} else throw 'unexpected options';
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
/// Helper for "livedata - result by value"
|
||||
const resultByValueArrays = Object.create(null);
|
||||
|
||||
@@ -1,6 +1,24 @@
|
||||
import { DDP } from '../common/namespace.js';
|
||||
import { Connection } from '../common/livedata_connection.js';
|
||||
|
||||
const _sleep = ms => new Promise(resolve => setTimeout(resolve, ms));
|
||||
|
||||
const callWhenSubReady = async (subName, handle, cb = () => {}) => {
|
||||
let control = 0;
|
||||
|
||||
while (!handle.ready()) {
|
||||
if (!handle.ready()) {
|
||||
// Just in case something happens with the subscription, we have this control
|
||||
if (control++ === 1000) {
|
||||
throw new Error(`Subscribe to ${subName} is taking too long!`);
|
||||
}
|
||||
await _sleep(0);
|
||||
return;
|
||||
}
|
||||
await cb();
|
||||
}
|
||||
};
|
||||
|
||||
// XXX should check error codes
|
||||
const failure = function(test, code, reason) {
|
||||
return function(error, result) {
|
||||
@@ -79,10 +97,10 @@ Tinytest.add('livedata - non-function method', function(test) {
|
||||
});
|
||||
|
||||
const echoTest = function(item) {
|
||||
return function(test, expect) {
|
||||
return async function(test, expect) {
|
||||
if (Meteor.isServer) {
|
||||
test.equal(Meteor.call('echo', item), [item]);
|
||||
test.equal(Meteor.call('echoOne', item), item);
|
||||
test.equal(await Meteor.callAsync('echo', item), [item]);
|
||||
test.equal(await Meteor.callAsync('echoOne', item), item);
|
||||
}
|
||||
if (Meteor.isClient) test.equal(Meteor.call('echo', item), undefined);
|
||||
|
||||
@@ -96,13 +114,13 @@ const echoTest = function(item) {
|
||||
|
||||
testAsyncMulti('livedata - basic method invocation', [
|
||||
// Unknown methods
|
||||
function(test, expect) {
|
||||
async function(test, expect) {
|
||||
if (Meteor.isServer) {
|
||||
// On server, with no callback, throws exception
|
||||
let ret;
|
||||
let threw;
|
||||
try {
|
||||
ret = Meteor.call('unknown method');
|
||||
ret = await Meteor.callAsync('unknown method');
|
||||
} catch (e) {
|
||||
test.equal(e.error, 404);
|
||||
threw = true;
|
||||
@@ -125,18 +143,19 @@ testAsyncMulti('livedata - basic method invocation', [
|
||||
test.equal(ret, undefined);
|
||||
},
|
||||
|
||||
function(test, expect) {
|
||||
async function(test, expect) {
|
||||
// make sure 'undefined' is preserved as such, instead of turning
|
||||
// into null (JSON does not have 'undefined' so there is special
|
||||
// code for this)
|
||||
if (Meteor.isServer) test.equal(Meteor.call('nothing'), undefined);
|
||||
if (Meteor.isServer)
|
||||
test.equal(await Meteor.callAsync('nothing'), undefined);
|
||||
if (Meteor.isClient) test.equal(Meteor.call('nothing'), undefined);
|
||||
|
||||
test.equal(Meteor.call('nothing', expect(undefined, undefined)), undefined);
|
||||
},
|
||||
|
||||
function(test, expect) {
|
||||
if (Meteor.isServer) test.equal(Meteor.call('echo'), []);
|
||||
async function(test, expect) {
|
||||
if (Meteor.isServer) test.equal(await Meteor.callAsync('echo'), []);
|
||||
if (Meteor.isClient) test.equal(Meteor.call('echo'), undefined);
|
||||
|
||||
test.equal(Meteor.call('echo', expect(undefined, [])), undefined);
|
||||
@@ -153,9 +172,12 @@ testAsyncMulti('livedata - basic method invocation', [
|
||||
echoTest(Infinity),
|
||||
echoTest(-Infinity),
|
||||
|
||||
function(test, expect) {
|
||||
async function(test, expect) {
|
||||
if (Meteor.isServer)
|
||||
test.equal(Meteor.call('echo', 12, { x: 13 }), [12, { x: 13 }]);
|
||||
test.equal(await Meteor.callAsync('echo', 12, { x: 13 }), [
|
||||
12,
|
||||
{ x: 13 },
|
||||
]);
|
||||
if (Meteor.isClient)
|
||||
test.equal(Meteor.call('echo', 12, { x: 13 }), undefined);
|
||||
|
||||
@@ -198,7 +220,7 @@ testAsyncMulti('livedata - basic method invocation', [
|
||||
}
|
||||
},
|
||||
|
||||
function(test, expect) {
|
||||
async function(test, expect) {
|
||||
// No callback
|
||||
|
||||
if (Meteor.isServer) {
|
||||
@@ -209,7 +231,7 @@ testAsyncMulti('livedata - basic method invocation', [
|
||||
Meteor.call('exception', 'server');
|
||||
});
|
||||
// No exception, because no code will run on the client
|
||||
test.equal(Meteor.call('exception', 'client'), undefined);
|
||||
test.equal(await Meteor.callAsync('exception', 'client'), undefined);
|
||||
}
|
||||
|
||||
if (Meteor.isClient) {
|
||||
@@ -273,15 +295,15 @@ testAsyncMulti('livedata - basic method invocation', [
|
||||
),
|
||||
undefined
|
||||
);
|
||||
test.equal(Meteor.call('exception', 'client'), undefined);
|
||||
test.equal(await Meteor.callAsync('exception', 'client'), undefined);
|
||||
}
|
||||
},
|
||||
|
||||
function(test, expect) {
|
||||
async function(test, expect) {
|
||||
if (Meteor.isServer) {
|
||||
let threw = false;
|
||||
try {
|
||||
Meteor.call('exception', 'both', { intended: true });
|
||||
await Meteor.callAsync('exception', 'both', { intended: true });
|
||||
} catch (e) {
|
||||
threw = true;
|
||||
test.equal(e.error, 999);
|
||||
@@ -290,9 +312,9 @@ testAsyncMulti('livedata - basic method invocation', [
|
||||
test.isTrue(threw);
|
||||
threw = false;
|
||||
try {
|
||||
Meteor.call('exception', 'both', {
|
||||
await Meteor.callAsync('exception', 'both', {
|
||||
intended: true,
|
||||
throwThroughFuture: true
|
||||
throwThroughFuture: true,
|
||||
});
|
||||
} catch (e) {
|
||||
threw = true;
|
||||
@@ -327,74 +349,81 @@ testAsyncMulti('livedata - basic method invocation', [
|
||||
'server',
|
||||
{
|
||||
intended: true,
|
||||
throwThroughFuture: true
|
||||
throwThroughFuture: true,
|
||||
},
|
||||
expect(failure(test, 999, 'Client-visible test exception'))
|
||||
),
|
||||
undefined
|
||||
);
|
||||
}
|
||||
}
|
||||
},
|
||||
]);
|
||||
|
||||
const checkBalances = function(test, a, b) {
|
||||
const alice = Ledger.findOne({ name: 'alice', world: test.runId() });
|
||||
const bob = Ledger.findOne({ name: 'bob', world: test.runId() });
|
||||
const checkBalances = async function(test, a, b) {
|
||||
const alice = await Ledger.findOneAsync({
|
||||
name: 'alice',
|
||||
world: test.runId(),
|
||||
});
|
||||
const bob = await Ledger.findOneAsync({ name: 'bob', world: test.runId() });
|
||||
|
||||
test.equal(alice.balance, a);
|
||||
test.equal(bob.balance, b);
|
||||
};
|
||||
|
||||
const subscribeBeforeRun = async (subName, testId, cb) => {
|
||||
if (Meteor.isClient) {
|
||||
const handle = Meteor.subscribe(subName, testId);
|
||||
await callWhenSubReady(subName, handle);
|
||||
handle.stop();
|
||||
}
|
||||
await cb();
|
||||
};
|
||||
|
||||
// would be nice to have a database-aware test harness of some kind --
|
||||
// this is a big hack (and XXX pollutes the global test namespace)
|
||||
testAsyncMulti('livedata - compound methods', [
|
||||
function(test, expect) {
|
||||
if (Meteor.isClient) Meteor.subscribe('ledger', test.runId(), expect());
|
||||
|
||||
Ledger.insert(
|
||||
{ name: 'alice', balance: 100, world: test.runId() },
|
||||
expect(function() {})
|
||||
);
|
||||
Ledger.insert(
|
||||
{ name: 'bob', balance: 50, world: test.runId() },
|
||||
expect(function() {})
|
||||
);
|
||||
async function(test) {
|
||||
await Ledger.insertAsync({
|
||||
name: 'alice',
|
||||
balance: 100,
|
||||
world: test.runId(),
|
||||
});
|
||||
await Ledger.insertAsync({ name: 'bob', balance: 50, world: test.runId() });
|
||||
},
|
||||
function(test, expect) {
|
||||
Meteor.call(
|
||||
'ledger/transfer',
|
||||
test.runId(),
|
||||
'alice',
|
||||
'bob',
|
||||
10,
|
||||
expect(function(err, result) {
|
||||
test.equal(err, undefined);
|
||||
test.equal(result, undefined);
|
||||
checkBalances(test, 90, 60);
|
||||
})
|
||||
);
|
||||
checkBalances(test, 90, 60);
|
||||
async function(test) {
|
||||
await subscribeBeforeRun('ledger', test.runId(), async () => {
|
||||
await Meteor.callAsync(
|
||||
'ledger/transfer',
|
||||
test.runId(),
|
||||
'alice',
|
||||
'bob',
|
||||
10
|
||||
);
|
||||
await checkBalances(test, 90, 60);
|
||||
});
|
||||
},
|
||||
function(test, expect) {
|
||||
Meteor.call(
|
||||
'ledger/transfer',
|
||||
test.runId(),
|
||||
'alice',
|
||||
'bob',
|
||||
100,
|
||||
true,
|
||||
expect(function(err, result) {
|
||||
failure(test, 409)(err, result);
|
||||
// Balances are reverted back to pre-stub values.
|
||||
checkBalances(test, 90, 60);
|
||||
})
|
||||
);
|
||||
async function(test) {
|
||||
await subscribeBeforeRun('ledger', test.runId(), async () => {
|
||||
try {
|
||||
await Meteor.callAsync(
|
||||
'ledger/transfer',
|
||||
test.runId(),
|
||||
'alice',
|
||||
'bob',
|
||||
100,
|
||||
true
|
||||
);
|
||||
} catch (e) {}
|
||||
|
||||
if (Meteor.isClient)
|
||||
// client can fool itself by cheating, but only until the sync
|
||||
// finishes
|
||||
checkBalances(test, -10, 160);
|
||||
else checkBalances(test, 90, 60);
|
||||
}
|
||||
if (Meteor.isClient) {
|
||||
// client can fool itself by cheating, but only until the sync
|
||||
// finishes
|
||||
await checkBalances(test, -10, 160);
|
||||
} else {
|
||||
await checkBalances(test, 90, 60);
|
||||
}
|
||||
});
|
||||
},
|
||||
]);
|
||||
|
||||
// Replaces the Connection's `_livedata_data` method to push incoming
|
||||
@@ -435,7 +464,7 @@ if (Meteor.isClient) {
|
||||
testAsyncMulti(
|
||||
'livedata - changing userid reruns subscriptions without flapping data on the wire',
|
||||
[
|
||||
function(test, expect) {
|
||||
async function(test, expect) {
|
||||
const messages = [];
|
||||
const undoEavesdrop = eavesdropOnCollection(
|
||||
Meteor.connection,
|
||||
@@ -484,53 +513,53 @@ if (Meteor.isClient) {
|
||||
let afterSecondSetUserId;
|
||||
let afterThirdSetUserId;
|
||||
|
||||
Meteor.subscribe(
|
||||
'objectsWithUsers',
|
||||
expect(function() {
|
||||
expectMessages(1, 0, ['owned by none']);
|
||||
const handle = Meteor.subscribe('objectsWithUsers');
|
||||
|
||||
// Just make sure the subscription is ready before running the tests
|
||||
// As everything now runs async, the tests were running before the data fully came in
|
||||
await callWhenSubReady('objectsWithUsers', handle, () => {
|
||||
expectMessages(1, 0, ['owned by none']);
|
||||
Meteor.apply('setUserId', ['1'], { wait: true }, afterFirstSetUserId);
|
||||
afterFirstSetUserId = expect(function() {
|
||||
expectMessages(3, 1, [
|
||||
'owned by one - a',
|
||||
'owned by one/two - a',
|
||||
'owned by one/two - b',
|
||||
]);
|
||||
Meteor.apply(
|
||||
'setUserId',
|
||||
['1'],
|
||||
['2'],
|
||||
{ wait: true },
|
||||
afterFirstSetUserId
|
||||
afterSecondSetUserId
|
||||
);
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
afterFirstSetUserId = expect(function() {
|
||||
expectMessages(3, 1, [
|
||||
'owned by one - a',
|
||||
'owned by one/two - a',
|
||||
'owned by one/two - b'
|
||||
]);
|
||||
Meteor.apply(
|
||||
'setUserId',
|
||||
['2'],
|
||||
{ wait: true },
|
||||
afterSecondSetUserId
|
||||
);
|
||||
});
|
||||
afterSecondSetUserId = expect(function() {
|
||||
expectMessages(2, 1, [
|
||||
'owned by one/two - a',
|
||||
'owned by one/two - b',
|
||||
'owned by two - a',
|
||||
'owned by two - b',
|
||||
]);
|
||||
Meteor.apply(
|
||||
'setUserId',
|
||||
['2'],
|
||||
{ wait: true },
|
||||
afterThirdSetUserId
|
||||
);
|
||||
});
|
||||
|
||||
afterSecondSetUserId = expect(function() {
|
||||
expectMessages(2, 1, [
|
||||
'owned by one/two - a',
|
||||
'owned by one/two - b',
|
||||
'owned by two - a',
|
||||
'owned by two - b'
|
||||
]);
|
||||
Meteor.apply('setUserId', ['2'], { wait: true }, afterThirdSetUserId);
|
||||
});
|
||||
|
||||
afterThirdSetUserId = expect(function() {
|
||||
// Nothing should have been sent since the results of the
|
||||
// query are the same ("don't flap data on the wire")
|
||||
expectMessages(0, 0, [
|
||||
'owned by one/two - a',
|
||||
'owned by one/two - b',
|
||||
'owned by two - a',
|
||||
'owned by two - b'
|
||||
]);
|
||||
undoEavesdrop();
|
||||
afterThirdSetUserId = expect(function() {
|
||||
// Nothing should have been sent since the results of the
|
||||
// query are the same ("don't flap data on the wire")
|
||||
expectMessages(0, 0, [
|
||||
'owned by one/two - a',
|
||||
'owned by one/two - b',
|
||||
'owned by two - a',
|
||||
'owned by two - b',
|
||||
]);
|
||||
undoEavesdrop();
|
||||
});
|
||||
});
|
||||
},
|
||||
function(test, expect) {
|
||||
@@ -563,7 +592,7 @@ if (Meteor.isClient) {
|
||||
{ wait: true },
|
||||
expect(function() {})
|
||||
);
|
||||
}
|
||||
},
|
||||
]
|
||||
);
|
||||
}
|
||||
@@ -614,7 +643,7 @@ Meteor.methods({
|
||||
return 2;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
if (Meteor.isClient) {
|
||||
@@ -623,12 +652,22 @@ if (Meteor.isClient) {
|
||||
const id = Random.id();
|
||||
testAsyncMulti('livedata - added from two different subs', [
|
||||
function(test, expect) {
|
||||
Meteor.call('livedata/setup', id, expect(function() {}));
|
||||
Meteor.call(
|
||||
'livedata/setup',
|
||||
id,
|
||||
expect(function() {})
|
||||
);
|
||||
},
|
||||
function(test, expect) {
|
||||
MultiPub = new Mongo.Collection('MultiPubCollection' + id);
|
||||
const sub1 = Meteor.subscribe('pub1' + id, expect(function() {}));
|
||||
const sub2 = Meteor.subscribe('pub2' + id, expect(function() {}));
|
||||
const sub1 = Meteor.subscribe(
|
||||
'pub1' + id,
|
||||
expect(function() {})
|
||||
);
|
||||
const sub2 = Meteor.subscribe(
|
||||
'pub2' + id,
|
||||
expect(function() {})
|
||||
);
|
||||
},
|
||||
function(test, expect) {
|
||||
Meteor.call(
|
||||
@@ -653,7 +692,7 @@ if (Meteor.isClient) {
|
||||
},
|
||||
function(test, expect) {
|
||||
test.equal(MultiPub.findOne('foo'), { _id: 'foo', a: 'aa', b: 'bb' });
|
||||
}
|
||||
},
|
||||
]);
|
||||
})();
|
||||
}
|
||||
@@ -672,7 +711,7 @@ if (Meteor.isClient) {
|
||||
test.isTrue(coll.findOne(token));
|
||||
})
|
||||
);
|
||||
}
|
||||
},
|
||||
]);
|
||||
|
||||
testAsyncMulti('livedata - runtime universal sub creation', [
|
||||
@@ -688,7 +727,7 @@ if (Meteor.isClient) {
|
||||
test.isTrue(coll.findOne(token));
|
||||
})
|
||||
);
|
||||
}
|
||||
},
|
||||
]);
|
||||
|
||||
testAsyncMulti('livedata - no setUserId after unblock', [
|
||||
@@ -700,7 +739,7 @@ if (Meteor.isClient) {
|
||||
test.isTrue(result);
|
||||
})
|
||||
);
|
||||
}
|
||||
},
|
||||
]);
|
||||
|
||||
testAsyncMulti(
|
||||
@@ -714,7 +753,7 @@ if (Meteor.isClient) {
|
||||
// Use a separate connection so that we can safely check to see if
|
||||
// conn._subscriptions is empty.
|
||||
conn = new Connection('/', {
|
||||
reloadWithOutstanding: true
|
||||
reloadWithOutstanding: true,
|
||||
});
|
||||
collName = Random.id();
|
||||
coll = new Mongo.Collection(collName, { connection: conn });
|
||||
@@ -730,7 +769,7 @@ if (Meteor.isClient) {
|
||||
? 'Internal server error'
|
||||
: 'Explicit error'
|
||||
)
|
||||
)
|
||||
),
|
||||
});
|
||||
};
|
||||
testSubError({ throwInHandler: true });
|
||||
@@ -752,7 +791,7 @@ if (Meteor.isClient) {
|
||||
onReady: expect(),
|
||||
onError: function(error) {
|
||||
errorFromRerun = error;
|
||||
}
|
||||
},
|
||||
}
|
||||
);
|
||||
},
|
||||
@@ -761,7 +800,11 @@ if (Meteor.isClient) {
|
||||
test.equal(coll.find().count(), 1);
|
||||
test.isFalse(errorFromRerun);
|
||||
test.equal(_.size(conn._subscriptions), 1); // white-box test
|
||||
conn.call('setUserId', 'bla', expect(function() {}));
|
||||
conn.call(
|
||||
'setUserId',
|
||||
'bla',
|
||||
expect(function() {})
|
||||
);
|
||||
},
|
||||
function(test, expect) {
|
||||
// Now that we've re-run, we should have stopped the subscription,
|
||||
@@ -780,13 +823,16 @@ if (Meteor.isClient) {
|
||||
{
|
||||
onError: function() {
|
||||
gotErrorFromStopper = true;
|
||||
}
|
||||
},
|
||||
}
|
||||
);
|
||||
// Call a method. This method won't be processed until the publisher's
|
||||
// function returns, so blocking on it being done ensures that we've
|
||||
// gotten the removed/nosub/etc.
|
||||
conn.call('nothing', expect(function() {}));
|
||||
conn.call(
|
||||
'nothing',
|
||||
expect(function() {})
|
||||
);
|
||||
},
|
||||
function(test, expect) {
|
||||
test.equal(coll.find().count(), 0);
|
||||
@@ -794,7 +840,7 @@ if (Meteor.isClient) {
|
||||
test.isFalse(gotErrorFromStopper);
|
||||
test.equal(_.size(conn._subscriptions), 0); // white-box test
|
||||
conn._stream.disconnect({ _permanent: true });
|
||||
}
|
||||
},
|
||||
];
|
||||
})()
|
||||
);
|
||||
@@ -810,7 +856,7 @@ if (Meteor.isClient) {
|
||||
// Use a separate connection so that we can safely check to see if
|
||||
// conn._subscriptions is empty.
|
||||
conn = new Connection('/', {
|
||||
reloadWithOutstanding: true
|
||||
reloadWithOutstanding: true,
|
||||
});
|
||||
collName = Random.id();
|
||||
coll = new Mongo.Collection(collName, { connection: conn });
|
||||
@@ -826,7 +872,7 @@ if (Meteor.isClient) {
|
||||
? 'Internal server error'
|
||||
: 'Explicit error'
|
||||
)
|
||||
)
|
||||
),
|
||||
});
|
||||
};
|
||||
testSubError({ throwInHandler: true });
|
||||
@@ -848,7 +894,7 @@ if (Meteor.isClient) {
|
||||
onReady: expect(),
|
||||
onStop: function(error) {
|
||||
errorFromRerun = error;
|
||||
}
|
||||
},
|
||||
}
|
||||
);
|
||||
},
|
||||
@@ -857,7 +903,11 @@ if (Meteor.isClient) {
|
||||
test.equal(coll.find().count(), 1);
|
||||
test.isFalse(errorFromRerun);
|
||||
test.equal(_.size(conn._subscriptions), 1); // white-box test
|
||||
conn.call('setUserId', 'bla', expect(function() {}));
|
||||
conn.call(
|
||||
'setUserId',
|
||||
'bla',
|
||||
expect(function() {})
|
||||
);
|
||||
},
|
||||
function(test, expect) {
|
||||
// Now that we've re-run, we should have stopped the subscription,
|
||||
@@ -878,13 +928,16 @@ if (Meteor.isClient) {
|
||||
if (error) {
|
||||
gotErrorFromStopper = true;
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
);
|
||||
// Call a method. This method won't be processed until the publisher's
|
||||
// function returns, so blocking on it being done ensures that we've
|
||||
// gotten the removed/nosub/etc.
|
||||
conn.call('nothing', expect(function() {}));
|
||||
conn.call(
|
||||
'nothing',
|
||||
expect(function() {})
|
||||
);
|
||||
},
|
||||
function(test, expect) {
|
||||
test.equal(coll.find().count(), 0);
|
||||
@@ -892,7 +945,7 @@ if (Meteor.isClient) {
|
||||
test.isFalse(gotErrorFromStopper);
|
||||
test.equal(_.size(conn._subscriptions), 0); // white-box test
|
||||
conn._stream.disconnect({ _permanent: true });
|
||||
}
|
||||
},
|
||||
];
|
||||
})()
|
||||
);
|
||||
@@ -908,7 +961,7 @@ if (Meteor.isClient) {
|
||||
test.equal(One.find().count(), 2);
|
||||
test.equal(Two.find().count(), 3);
|
||||
}),
|
||||
onError: failure()
|
||||
onError: failure(),
|
||||
}
|
||||
);
|
||||
},
|
||||
@@ -918,7 +971,7 @@ if (Meteor.isClient) {
|
||||
{ dup: 1 },
|
||||
{
|
||||
onReady: failure(),
|
||||
onError: expect(failure(test, 500, 'Internal server error'))
|
||||
onError: expect(failure(test, 500, 'Internal server error')),
|
||||
}
|
||||
);
|
||||
},
|
||||
@@ -928,10 +981,10 @@ if (Meteor.isClient) {
|
||||
{ notCursor: 1 },
|
||||
{
|
||||
onReady: failure(),
|
||||
onError: expect(failure(test, 500, 'Internal server error'))
|
||||
onError: expect(failure(test, 500, 'Internal server error')),
|
||||
}
|
||||
);
|
||||
}
|
||||
},
|
||||
]);
|
||||
}
|
||||
|
||||
@@ -944,7 +997,7 @@ if (Meteor.isServer) {
|
||||
s2s: function(arg) {
|
||||
check(arg, String);
|
||||
return 's2s ' + arg;
|
||||
}
|
||||
},
|
||||
});
|
||||
}
|
||||
(function() {
|
||||
@@ -973,7 +1026,7 @@ if (Meteor.isServer) {
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
},
|
||||
]);
|
||||
})();
|
||||
|
||||
@@ -992,12 +1045,12 @@ if (Meteor.isServer) {
|
||||
);
|
||||
},
|
||||
|
||||
function(test, expect) {
|
||||
async function(test, expect) {
|
||||
const self = this;
|
||||
if (self.conn.status().connected) {
|
||||
test.equal(self.conn.call('s2s', 'foo'), 's2s foo');
|
||||
test.equal(await self.conn.callAsync('s2s', 'foo'), 's2s foo');
|
||||
}
|
||||
}
|
||||
},
|
||||
]);
|
||||
})();
|
||||
}
|
||||
@@ -1014,7 +1067,7 @@ if (Meteor.isServer) {
|
||||
}),
|
||||
500
|
||||
);
|
||||
}
|
||||
},
|
||||
]);
|
||||
})();
|
||||
|
||||
@@ -1038,13 +1091,13 @@ if (Meteor.isServer) {
|
||||
onReady: expect(function() {
|
||||
test.equal(PublisherCloningCollection.findOne(), {
|
||||
_id: 'a',
|
||||
x: { y: 43 }
|
||||
x: { y: 43 },
|
||||
});
|
||||
}),
|
||||
onError: failure()
|
||||
onError: failure(),
|
||||
}
|
||||
);
|
||||
}
|
||||
},
|
||||
]);
|
||||
}
|
||||
|
||||
@@ -1083,7 +1136,7 @@ testAsyncMulti('livedata - result by value', [
|
||||
test.equal(self.firstResult.length + 1, secondResult.length);
|
||||
})
|
||||
);
|
||||
}
|
||||
},
|
||||
]);
|
||||
|
||||
// XXX some things to test in greater detail:
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
Tinytest.add('livedata - DDP.randomStream', function(test) {
|
||||
Tinytest.addAsync('livedata - DDP.randomStream', async function(test) {
|
||||
const randomSeed = Random.id();
|
||||
const context = { randomSeed: randomSeed };
|
||||
|
||||
let sequence = DDP._CurrentMethodInvocation.withValue(context, function() {
|
||||
let sequence = await DDP._CurrentMethodInvocation.withValue(context, function() {
|
||||
return DDP.randomStream('1');
|
||||
});
|
||||
|
||||
@@ -21,7 +21,7 @@ Tinytest.add('livedata - DDP.randomStream', function(test) {
|
||||
test.equal(id1, id1Cloned);
|
||||
|
||||
// We should get the same sequence when we use the same key
|
||||
sequence = DDP._CurrentMethodInvocation.withValue(context, function() {
|
||||
sequence = await DDP._CurrentMethodInvocation.withValue(context, function() {
|
||||
return DDP.randomStream('1');
|
||||
});
|
||||
seeds = sequence.alea.args;
|
||||
|
||||
@@ -32,23 +32,23 @@ _.extend(StubStream.prototype, {
|
||||
},
|
||||
|
||||
// Methods for tests
|
||||
receive: function(data) {
|
||||
receive: async function(data) {
|
||||
const self = this;
|
||||
|
||||
if (typeof data === 'object') {
|
||||
data = EJSON.stringify(data);
|
||||
}
|
||||
|
||||
_.each(self.callbacks['message'], function(cb) {
|
||||
cb(data);
|
||||
});
|
||||
for (const cb of self.callbacks['message']) {
|
||||
await cb(data);
|
||||
}
|
||||
},
|
||||
|
||||
reset: function() {
|
||||
reset: async function() {
|
||||
const self = this;
|
||||
_.each(self.callbacks['reset'], function(cb) {
|
||||
cb();
|
||||
});
|
||||
for (const cb of self.callbacks['reset']) {
|
||||
await cb();
|
||||
}
|
||||
},
|
||||
|
||||
// Provide a tag to detect stub streams.
|
||||
|
||||
17
packages/ddp-rate-limiter/ddp-rate-limiter.d.ts
vendored
Normal file
17
packages/ddp-rate-limiter/ddp-rate-limiter.d.ts
vendored
Normal file
@@ -0,0 +1,17 @@
|
||||
export namespace DDPRateLimiter {
|
||||
interface Matcher {
|
||||
type?: string | ((type: string) => boolean) | undefined;
|
||||
name?: string | ((name: string) => boolean) | undefined;
|
||||
userId?: string | ((userId: string) => boolean) | undefined;
|
||||
connectionId?: string | ((connectionId: string) => boolean) | undefined;
|
||||
clientAddress?: string | ((clientAddress: string) => boolean) | undefined;
|
||||
}
|
||||
|
||||
function addRule(
|
||||
matcher: Matcher,
|
||||
numRequests: number,
|
||||
timeInterval: number
|
||||
): string;
|
||||
|
||||
function removeRule(ruleId: string): boolean;
|
||||
}
|
||||
3
packages/ddp-rate-limiter/package-types.json
Normal file
3
packages/ddp-rate-limiter/package-types.json
Normal file
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"typesEntry": "ddp-rate-limiter.d.ts"
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
Package.describe({
|
||||
name: 'ddp-rate-limiter',
|
||||
version: '1.1.0',
|
||||
version: '1.1.1',
|
||||
// Brief, one-line summary of the package.
|
||||
summary: 'The DDPRateLimiter allows users to add rate limits to DDP' +
|
||||
' methods and subscriptions.',
|
||||
@@ -14,6 +14,7 @@ Package.describe({
|
||||
Package.onUse(function(api) {
|
||||
api.use('rate-limit', 'server');
|
||||
api.use('ecmascript');
|
||||
api.addAssets('ddp-rate-limiter.d.ts', 'server');
|
||||
api.export('DDPRateLimiter', 'server');
|
||||
api.mainModule('ddp-rate-limiter.js', 'server');
|
||||
});
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
node_modules
|
||||
@@ -1,7 +0,0 @@
|
||||
This directory and the files immediately inside it are automatically generated
|
||||
when you change this package's NPM dependencies. Commit the files in this
|
||||
directory (npm-shrinkwrap.json, .gitignore, and this README) to source control
|
||||
so that others run the same versions of sub-dependencies.
|
||||
|
||||
You should NOT check in the node_modules directory that Meteor automatically
|
||||
creates; if you are using git, the .gitignore file tells git to ignore it.
|
||||
@@ -1,45 +0,0 @@
|
||||
{
|
||||
"lockfileVersion": 1,
|
||||
"dependencies": {
|
||||
"faye-websocket": {
|
||||
"version": "0.11.3",
|
||||
"resolved": "https://registry.npmjs.org/faye-websocket/-/faye-websocket-0.11.3.tgz",
|
||||
"integrity": "sha512-D2y4bovYpzziGgbHYtGCMjlJM36vAl/y+xUyn1C+FVx8szd1E+86KwVw6XvYSzOP8iMpm1X0I4xJD+QtUb36OA=="
|
||||
},
|
||||
"http-parser-js": {
|
||||
"version": "0.5.3",
|
||||
"resolved": "https://registry.npmjs.org/http-parser-js/-/http-parser-js-0.5.3.tgz",
|
||||
"integrity": "sha512-t7hjvef/5HEK7RWTdUzVUhl8zkEu+LlaE0IYzdMuvbSDipxBRpOn4Uhw8ZyECEa808iVT8XCjzo6xmYt4CiLZg=="
|
||||
},
|
||||
"permessage-deflate": {
|
||||
"version": "0.1.7",
|
||||
"resolved": "https://registry.npmjs.org/permessage-deflate/-/permessage-deflate-0.1.7.tgz",
|
||||
"integrity": "sha512-EUNi/RIsyJ1P1u9QHFwMOUWMYetqlE22ZgGbad7YP856WF4BFF0B7DuNy6vEGsgNNud6c/SkdWzkne71hH8MjA=="
|
||||
},
|
||||
"safe-buffer": {
|
||||
"version": "5.2.1",
|
||||
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
|
||||
"integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ=="
|
||||
},
|
||||
"sockjs": {
|
||||
"version": "0.3.21",
|
||||
"resolved": "https://registry.npmjs.org/sockjs/-/sockjs-0.3.21.tgz",
|
||||
"integrity": "sha512-DhbPFGpxjc6Z3I+uX07Id5ZO2XwYsWOrYjaSeieES78cq+JaJvVe5q/m1uvjIQhXinhIeCFRH6JgXe+mvVMyXw=="
|
||||
},
|
||||
"uuid": {
|
||||
"version": "3.4.0",
|
||||
"resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz",
|
||||
"integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A=="
|
||||
},
|
||||
"websocket-driver": {
|
||||
"version": "0.7.4",
|
||||
"resolved": "https://registry.npmjs.org/websocket-driver/-/websocket-driver-0.7.4.tgz",
|
||||
"integrity": "sha512-b17KeDIQVjvb0ssuSDF2cYXSg2iztliJ4B9WdsuB6J952qCPKmnVq4DyW5motImXHDC1cBT/1UezrJVsKw5zjg=="
|
||||
},
|
||||
"websocket-extensions": {
|
||||
"version": "0.1.4",
|
||||
"resolved": "https://registry.npmjs.org/websocket-extensions/-/websocket-extensions-0.1.4.tgz",
|
||||
"integrity": "sha512-OqedPIGOfsDlo31UNwYbCFMSaO9m9G/0faIHj5/dZFDMFqPTcx6UwqyOy3COEaEOg/9VsGIpdqn62W5KhoKSpg=="
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,125 +0,0 @@
|
||||
// A write fence collects a group of writes, and provides a callback
|
||||
// when all of the writes are fully committed and propagated (all
|
||||
// observers have been notified of the write and acknowledged it.)
|
||||
//
|
||||
DDPServer._WriteFence = class {
|
||||
constructor() {
|
||||
this.armed = false;
|
||||
this.fired = false;
|
||||
this.retired = false;
|
||||
this.outstanding_writes = 0;
|
||||
this.before_fire_callbacks = [];
|
||||
this.completion_callbacks = [];
|
||||
}
|
||||
|
||||
// Start tracking a write, and return an object to represent it. The
|
||||
// object has a single method, committed(). This method should be
|
||||
// called when the write is fully committed and propagated. You can
|
||||
// continue to add writes to the WriteFence up until it is triggered
|
||||
// (calls its callbacks because all writes have committed.)
|
||||
beginWrite() {
|
||||
if (this.retired)
|
||||
return { committed: function () {} };
|
||||
|
||||
if (this.fired)
|
||||
throw new Error("fence has already activated -- too late to add writes");
|
||||
|
||||
this.outstanding_writes++;
|
||||
let committed = false;
|
||||
const _committedFn = async () => {
|
||||
if (committed)
|
||||
throw new Error("committed called twice on the same write");
|
||||
committed = true;
|
||||
this.outstanding_writes--;
|
||||
await this._maybeFire();
|
||||
};
|
||||
|
||||
const self = this;
|
||||
return {
|
||||
committed: Meteor._isFibersEnabled ? () => Promise.await(_committedFn.apply(self)) : _committedFn,
|
||||
};
|
||||
}
|
||||
|
||||
// Arm the fence. Once the fence is armed, and there are no more
|
||||
// uncommitted writes, it will activate.
|
||||
arm() {
|
||||
if (this === DDPServer._CurrentWriteFence.get())
|
||||
throw Error("Can't arm the current fence");
|
||||
this.armed = true;
|
||||
return Meteor._isFibersEnabled ? Promise.await(this._maybeFire()) : this._maybeFire();
|
||||
}
|
||||
|
||||
// Register a function to be called once before firing the fence.
|
||||
// Callback function can add new writes to the fence, in which case
|
||||
// it won't fire until those writes are done as well.
|
||||
onBeforeFire(func) {
|
||||
if (this.fired)
|
||||
throw new Error("fence has already activated -- too late to " +
|
||||
"add a callback");
|
||||
this.before_fire_callbacks.push(func);
|
||||
}
|
||||
|
||||
// Register a function to be called when the fence fires.
|
||||
onAllCommitted(func) {
|
||||
if (this.fired)
|
||||
throw new Error("fence has already activated -- too late to " +
|
||||
"add a callback");
|
||||
this.completion_callbacks.push(func);
|
||||
}
|
||||
|
||||
_armAndWait() {
|
||||
let resolver;
|
||||
const returnValue = new Promise(r => resolver = r);
|
||||
this.onAllCommitted(resolver);
|
||||
this.arm();
|
||||
|
||||
return returnValue;
|
||||
}
|
||||
// Convenience function. Arms the fence, then blocks until it fires.
|
||||
armAndWait() {
|
||||
return Meteor._isFibersEnabled ? Promise.await(this._armAndWait()) : this._armAndWait();
|
||||
}
|
||||
|
||||
async _maybeFire() {
|
||||
if (this.fired)
|
||||
throw new Error("write fence already activated?");
|
||||
if (this.armed && !this.outstanding_writes) {
|
||||
const invokeCallback = async (func) => {
|
||||
try {
|
||||
await func(this);
|
||||
} catch (err) {
|
||||
Meteor._debug("exception in write fence callback:", err);
|
||||
}
|
||||
};
|
||||
|
||||
this.outstanding_writes++;
|
||||
while (this.before_fire_callbacks.length > 0) {
|
||||
const cb = this.before_fire_callbacks.shift();
|
||||
await invokeCallback(cb);
|
||||
}
|
||||
this.outstanding_writes--;
|
||||
|
||||
if (!this.outstanding_writes) {
|
||||
this.fired = true;
|
||||
while (this.completion_callbacks.length > 0) {
|
||||
const cb = this.completion_callbacks.shift();
|
||||
await invokeCallback(cb);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Deactivate this fence so that adding more writes has no effect.
|
||||
// The fence must have already fired.
|
||||
retire() {
|
||||
if (!this.fired)
|
||||
throw new Error("Can't retire a fence that hasn't fired.");
|
||||
this.retired = true;
|
||||
}
|
||||
};
|
||||
|
||||
// The current write fence. When there is a current write fence, code
|
||||
// that writes to databases should register their writes with it using
|
||||
// beginWrite().
|
||||
//
|
||||
DDPServer._CurrentWriteFence = new Meteor.EnvironmentVariable;
|
||||
@@ -1,5 +1,7 @@
|
||||
DDPServer = {};
|
||||
|
||||
var Fiber = Npm.require('fibers');
|
||||
|
||||
// Publication strategies define how we handle data from published cursors at the collection level
|
||||
// This allows someone to:
|
||||
// - Choose a trade-off between client-server bandwidth and server memory usage
|
||||
@@ -328,9 +330,9 @@ var Session = function (server, version, socket, options) {
|
||||
self.send({ msg: 'connected', session: self.id });
|
||||
|
||||
// On initial connect, spin up all the universal publishers.
|
||||
Meteor._runAsync(function() {
|
||||
Fiber(function () {
|
||||
self.startUniversalSubs();
|
||||
});
|
||||
}).run();
|
||||
|
||||
if (version !== 'pre1' && options.heartbeatInterval !== 0) {
|
||||
// We no longer need the low level timeout because we have heartbeats.
|
||||
@@ -555,10 +557,10 @@ Object.assign(Session.prototype, {
|
||||
// Any message counts as receiving a pong, as it demonstrates that
|
||||
// the client is still alive.
|
||||
if (self.heartbeat) {
|
||||
Meteor._runAsync(function() {
|
||||
Fiber(function () {
|
||||
self.heartbeat.messageReceived();
|
||||
});
|
||||
};
|
||||
}).run();
|
||||
}
|
||||
|
||||
if (self.version !== 'pre1' && msg_in.msg === 'ping') {
|
||||
if (self._respondToPings)
|
||||
@@ -582,7 +584,7 @@ Object.assign(Session.prototype, {
|
||||
return;
|
||||
}
|
||||
|
||||
function runHandlers() {
|
||||
Fiber(function () {
|
||||
var blocked = true;
|
||||
|
||||
var unblock = function () {
|
||||
@@ -602,9 +604,7 @@ Object.assign(Session.prototype, {
|
||||
else
|
||||
self.sendError('Bad request', msg);
|
||||
unblock(); // in case the handler didn't already do it
|
||||
}
|
||||
|
||||
Meteor._runAsync(runHandlers);
|
||||
}).run();
|
||||
};
|
||||
|
||||
processNext();
|
||||
@@ -778,7 +778,7 @@ Object.assign(Session.prototype, {
|
||||
const isThenable =
|
||||
resultOrThenable && typeof resultOrThenable.then === 'function';
|
||||
if (isThenable) {
|
||||
result = Meteor._isFibersEnabled ? Promise.await(resultOrThenable) : resultOrThenable;
|
||||
result = Promise.await(resultOrThenable);
|
||||
} else {
|
||||
result = resultOrThenable;
|
||||
}
|
||||
@@ -1177,21 +1177,15 @@ Object.assign(Subscription.prototype, {
|
||||
return c && c._publishCursor;
|
||||
};
|
||||
if (isCursor(res)) {
|
||||
if (Meteor._isFibersEnabled) {
|
||||
try {
|
||||
res._publishCursor(self);
|
||||
} catch (e) {
|
||||
self.error(e);
|
||||
return;
|
||||
}
|
||||
// _publishCursor only returns after the initial added callbacks have run.
|
||||
// mark subscription as ready.
|
||||
self.ready();
|
||||
} else {
|
||||
res._publishCursor(self).then(() => {
|
||||
self.ready();
|
||||
}).catch((e) => self.error(e));
|
||||
try {
|
||||
res._publishCursor(self);
|
||||
} catch (e) {
|
||||
self.error(e);
|
||||
return;
|
||||
}
|
||||
// _publishCursor only returns after the initial added callbacks have run.
|
||||
// mark subscription as ready.
|
||||
self.ready();
|
||||
} else if (_.isArray(res)) {
|
||||
// Check all the elements are cursors
|
||||
if (! _.all(res, isCursor)) {
|
||||
@@ -1213,21 +1207,15 @@ Object.assign(Subscription.prototype, {
|
||||
collectionNames[collectionName] = true;
|
||||
};
|
||||
|
||||
if (Meteor._isFibersEnabled) {
|
||||
try {
|
||||
_.each(res, function (cur) {
|
||||
cur._publishCursor(self);
|
||||
});
|
||||
} catch (e) {
|
||||
self.error(e);
|
||||
return;
|
||||
}
|
||||
self.ready();
|
||||
} else {
|
||||
Promise.all(res.map((c) => c._publishCursor(self))).then(() => {
|
||||
self.ready();
|
||||
}).catch((e) => self.error(e));
|
||||
try {
|
||||
_.each(res, function (cur) {
|
||||
cur._publishCursor(self);
|
||||
});
|
||||
} catch (e) {
|
||||
self.error(e);
|
||||
return;
|
||||
}
|
||||
self.ready();
|
||||
} else if (res) {
|
||||
// Truthy values other than cursors or arrays are probably a
|
||||
// user mistake (possible returning a Mongo document via, say,
|
||||
@@ -1504,11 +1492,9 @@ Server = function (options = {}) {
|
||||
sendError("Already connected", msg);
|
||||
return;
|
||||
}
|
||||
|
||||
Meteor._runAsync(function() {
|
||||
Fiber(function () {
|
||||
self._handleConnect(socket, msg);
|
||||
})
|
||||
|
||||
}).run();
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1525,9 +1511,9 @@ Server = function (options = {}) {
|
||||
|
||||
socket.on('close', function () {
|
||||
if (socket._meteorSession) {
|
||||
Meteor._runAsync(function() {
|
||||
Fiber(function () {
|
||||
socket._meteorSession.close();
|
||||
});
|
||||
}).run();
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -1705,9 +1691,9 @@ Object.assign(Server.prototype, {
|
||||
// self.sessions to change while we're running this loop.
|
||||
self.sessions.forEach(function (session) {
|
||||
if (!session._dontStartNewUniversalSubs) {
|
||||
Meteor._runAsync(function() {
|
||||
Fiber(function() {
|
||||
session._startSubscription(handler);
|
||||
});
|
||||
}).run();
|
||||
}
|
||||
});
|
||||
}
|
||||
131
packages/ddp-server-legacy/writefence.js
Normal file
131
packages/ddp-server-legacy/writefence.js
Normal file
@@ -0,0 +1,131 @@
|
||||
var Future = Npm.require('fibers/future');
|
||||
|
||||
// A write fence collects a group of writes, and provides a callback
|
||||
// when all of the writes are fully committed and propagated (all
|
||||
// observers have been notified of the write and acknowledged it.)
|
||||
//
|
||||
DDPServer._WriteFence = function () {
|
||||
var self = this;
|
||||
|
||||
self.armed = false;
|
||||
self.fired = false;
|
||||
self.retired = false;
|
||||
self.outstanding_writes = 0;
|
||||
self.before_fire_callbacks = [];
|
||||
self.completion_callbacks = [];
|
||||
};
|
||||
|
||||
// The current write fence. When there is a current write fence, code
|
||||
// that writes to databases should register their writes with it using
|
||||
// beginWrite().
|
||||
//
|
||||
DDPServer._CurrentWriteFence = new Meteor.EnvironmentVariable;
|
||||
|
||||
_.extend(DDPServer._WriteFence.prototype, {
|
||||
// Start tracking a write, and return an object to represent it. The
|
||||
// object has a single method, committed(). This method should be
|
||||
// called when the write is fully committed and propagated. You can
|
||||
// continue to add writes to the WriteFence up until it is triggered
|
||||
// (calls its callbacks because all writes have committed.)
|
||||
beginWrite: function () {
|
||||
var self = this;
|
||||
|
||||
if (self.retired)
|
||||
return { committed: function () {} };
|
||||
|
||||
if (self.fired)
|
||||
throw new Error("fence has already activated -- too late to add writes");
|
||||
|
||||
self.outstanding_writes++;
|
||||
var committed = false;
|
||||
return {
|
||||
committed: function () {
|
||||
if (committed)
|
||||
throw new Error("committed called twice on the same write");
|
||||
committed = true;
|
||||
self.outstanding_writes--;
|
||||
self._maybeFire();
|
||||
}
|
||||
};
|
||||
},
|
||||
|
||||
// Arm the fence. Once the fence is armed, and there are no more
|
||||
// uncommitted writes, it will activate.
|
||||
arm: function () {
|
||||
var self = this;
|
||||
if (self === DDPServer._CurrentWriteFence.get())
|
||||
throw Error("Can't arm the current fence");
|
||||
self.armed = true;
|
||||
self._maybeFire();
|
||||
},
|
||||
|
||||
// Register a function to be called once before firing the fence.
|
||||
// Callback function can add new writes to the fence, in which case
|
||||
// it won't fire until those writes are done as well.
|
||||
onBeforeFire: function (func) {
|
||||
var self = this;
|
||||
if (self.fired)
|
||||
throw new Error("fence has already activated -- too late to " +
|
||||
"add a callback");
|
||||
self.before_fire_callbacks.push(func);
|
||||
},
|
||||
|
||||
// Register a function to be called when the fence fires.
|
||||
onAllCommitted: function (func) {
|
||||
var self = this;
|
||||
if (self.fired)
|
||||
throw new Error("fence has already activated -- too late to " +
|
||||
"add a callback");
|
||||
self.completion_callbacks.push(func);
|
||||
},
|
||||
|
||||
// Convenience function. Arms the fence, then blocks until it fires.
|
||||
armAndWait: function () {
|
||||
var self = this;
|
||||
var future = new Future;
|
||||
self.onAllCommitted(function () {
|
||||
future['return']();
|
||||
});
|
||||
self.arm();
|
||||
future.wait();
|
||||
},
|
||||
|
||||
_maybeFire: function () {
|
||||
var self = this;
|
||||
if (self.fired)
|
||||
throw new Error("write fence already activated?");
|
||||
if (self.armed && !self.outstanding_writes) {
|
||||
function invokeCallback (func) {
|
||||
try {
|
||||
func(self);
|
||||
} catch (err) {
|
||||
Meteor._debug("exception in write fence callback", err);
|
||||
}
|
||||
}
|
||||
|
||||
self.outstanding_writes++;
|
||||
while (self.before_fire_callbacks.length > 0) {
|
||||
var callbacks = self.before_fire_callbacks;
|
||||
self.before_fire_callbacks = [];
|
||||
_.each(callbacks, invokeCallback);
|
||||
}
|
||||
self.outstanding_writes--;
|
||||
|
||||
if (!self.outstanding_writes) {
|
||||
self.fired = true;
|
||||
var callbacks = self.completion_callbacks;
|
||||
self.completion_callbacks = [];
|
||||
_.each(callbacks, invokeCallback);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
// Deactivate this fence so that adding more writes has no effect.
|
||||
// The fence must have already fired.
|
||||
retire: function () {
|
||||
var self = this;
|
||||
if (! self.fired)
|
||||
throw new Error("Can't retire a fence that hasn't fired.");
|
||||
self.retired = true;
|
||||
}
|
||||
});
|
||||
@@ -1,7 +1,5 @@
|
||||
DDPServer = {};
|
||||
|
||||
var Fiber = Npm.require('fibers');
|
||||
|
||||
// Publication strategies define how we handle data from published cursors at the collection level
|
||||
// This allows someone to:
|
||||
// - Choose a trade-off between client-server bandwidth and server memory usage
|
||||
@@ -330,9 +328,9 @@ var Session = function (server, version, socket, options) {
|
||||
self.send({ msg: 'connected', session: self.id });
|
||||
|
||||
// On initial connect, spin up all the universal publishers.
|
||||
Fiber(function () {
|
||||
Meteor._runAsync(function() {
|
||||
self.startUniversalSubs();
|
||||
}).run();
|
||||
});
|
||||
|
||||
if (version !== 'pre1' && options.heartbeatInterval !== 0) {
|
||||
// We no longer need the low level timeout because we have heartbeats.
|
||||
@@ -557,10 +555,10 @@ Object.assign(Session.prototype, {
|
||||
// Any message counts as receiving a pong, as it demonstrates that
|
||||
// the client is still alive.
|
||||
if (self.heartbeat) {
|
||||
Fiber(function () {
|
||||
Meteor._runAsync(function() {
|
||||
self.heartbeat.messageReceived();
|
||||
}).run();
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
if (self.version !== 'pre1' && msg_in.msg === 'ping') {
|
||||
if (self._respondToPings)
|
||||
@@ -584,7 +582,7 @@ Object.assign(Session.prototype, {
|
||||
return;
|
||||
}
|
||||
|
||||
Fiber(function () {
|
||||
function runHandlers() {
|
||||
var blocked = true;
|
||||
|
||||
var unblock = function () {
|
||||
@@ -604,7 +602,9 @@ Object.assign(Session.prototype, {
|
||||
else
|
||||
self.sendError('Bad request', msg);
|
||||
unblock(); // in case the handler didn't already do it
|
||||
}).run();
|
||||
}
|
||||
|
||||
Meteor._runAsync(runHandlers);
|
||||
};
|
||||
|
||||
processNext();
|
||||
@@ -762,33 +762,32 @@ Object.assign(Session.prototype, {
|
||||
}
|
||||
}
|
||||
|
||||
const getCurrentMethodInvocationResult = () => {
|
||||
const currentContext = DDP._CurrentMethodInvocation._setNewContextAndGetCurrent(
|
||||
invocation
|
||||
const getCurrentMethodInvocationResult = () =>
|
||||
DDP._CurrentPublicationInvocation.withValue(
|
||||
invocation,
|
||||
() =>
|
||||
maybeAuditArgumentChecks(
|
||||
handler,
|
||||
invocation,
|
||||
msg.params,
|
||||
"call to '" + msg.method + "'"
|
||||
),
|
||||
{
|
||||
name: 'getCurrentMethodInvocationResult',
|
||||
keyName: 'getCurrentMethodInvocationResult',
|
||||
}
|
||||
);
|
||||
|
||||
try {
|
||||
let result;
|
||||
const resultOrThenable = maybeAuditArgumentChecks(
|
||||
handler,
|
||||
invocation,
|
||||
msg.params,
|
||||
"call to '" + msg.method + "'"
|
||||
);
|
||||
const isThenable =
|
||||
resultOrThenable && typeof resultOrThenable.then === 'function';
|
||||
if (isThenable) {
|
||||
result = Promise.await(resultOrThenable);
|
||||
} else {
|
||||
result = resultOrThenable;
|
||||
resolve(
|
||||
DDPServer._CurrentWriteFence.withValue(
|
||||
fence,
|
||||
getCurrentMethodInvocationResult,
|
||||
{
|
||||
name: 'DDPServer._CurrentWriteFence',
|
||||
keyName: '_CurrentWriteFence',
|
||||
}
|
||||
return result;
|
||||
} finally {
|
||||
DDP._CurrentMethodInvocation._set(currentContext);
|
||||
}
|
||||
};
|
||||
|
||||
resolve(DDPServer._CurrentWriteFence.withValue(fence, getCurrentMethodInvocationResult));
|
||||
)
|
||||
);
|
||||
});
|
||||
|
||||
function finish() {
|
||||
@@ -899,7 +898,7 @@ Object.assign(Session.prototype, {
|
||||
// subs.
|
||||
self._dontStartNewUniversalSubs = false;
|
||||
self.startUniversalSubs();
|
||||
});
|
||||
}, { name: '_setUserId' });
|
||||
|
||||
// Start sending messages again, beginning with the diff from the previous
|
||||
// state of the world to the current state. No yields are allowed during
|
||||
@@ -1120,16 +1119,19 @@ Object.assign(Subscription.prototype, {
|
||||
const self = this;
|
||||
let resultOrThenable = null;
|
||||
try {
|
||||
resultOrThenable = DDP._CurrentPublicationInvocation.withValue(self, () =>
|
||||
maybeAuditArgumentChecks(
|
||||
self._handler,
|
||||
self,
|
||||
EJSON.clone(self._params),
|
||||
// It's OK that this would look weird for universal subscriptions,
|
||||
// because they have no arguments so there can never be an
|
||||
// audit-argument-checks failure.
|
||||
"publisher '" + self._name + "'"
|
||||
)
|
||||
resultOrThenable = DDP._CurrentPublicationInvocation.withValue(
|
||||
self,
|
||||
() =>
|
||||
maybeAuditArgumentChecks(
|
||||
self._handler,
|
||||
self,
|
||||
EJSON.clone(self._params),
|
||||
// It's OK that this would look weird for universal subscriptions,
|
||||
// because they have no arguments so there can never be an
|
||||
// audit-argument-checks failure.
|
||||
"publisher '" + self._name + "'"
|
||||
),
|
||||
{ name: self._name }
|
||||
);
|
||||
} catch (e) {
|
||||
self.error(e);
|
||||
@@ -1152,6 +1154,7 @@ Object.assign(Subscription.prototype, {
|
||||
} else {
|
||||
self._publishHandlerResult(resultOrThenable);
|
||||
}
|
||||
|
||||
},
|
||||
|
||||
_publishHandlerResult: function (res) {
|
||||
@@ -1177,15 +1180,21 @@ Object.assign(Subscription.prototype, {
|
||||
return c && c._publishCursor;
|
||||
};
|
||||
if (isCursor(res)) {
|
||||
try {
|
||||
res._publishCursor(self);
|
||||
} catch (e) {
|
||||
self.error(e);
|
||||
return;
|
||||
if (Meteor._isFibersEnabled) {
|
||||
try {
|
||||
res._publishCursor(self);
|
||||
} catch (e) {
|
||||
self.error(e);
|
||||
return;
|
||||
}
|
||||
// _publishCursor only returns after the initial added callbacks have run.
|
||||
// mark subscription as ready.
|
||||
self.ready();
|
||||
} else {
|
||||
res._publishCursor(self).then(() => {
|
||||
self.ready();
|
||||
}).catch((e) => self.error(e));
|
||||
}
|
||||
// _publishCursor only returns after the initial added callbacks have run.
|
||||
// mark subscription as ready.
|
||||
self.ready();
|
||||
} else if (_.isArray(res)) {
|
||||
// Check all the elements are cursors
|
||||
if (! _.all(res, isCursor)) {
|
||||
@@ -1207,15 +1216,21 @@ Object.assign(Subscription.prototype, {
|
||||
collectionNames[collectionName] = true;
|
||||
};
|
||||
|
||||
try {
|
||||
_.each(res, function (cur) {
|
||||
cur._publishCursor(self);
|
||||
});
|
||||
} catch (e) {
|
||||
self.error(e);
|
||||
return;
|
||||
if (Meteor._isFibersEnabled) {
|
||||
try {
|
||||
_.each(res, function (cur) {
|
||||
cur._publishCursor(self);
|
||||
});
|
||||
} catch (e) {
|
||||
self.error(e);
|
||||
return;
|
||||
}
|
||||
self.ready();
|
||||
} else {
|
||||
Promise.all(res.map((c) => c._publishCursor(self))).then(() => {
|
||||
self.ready();
|
||||
}).catch((e) => self.error(e));
|
||||
}
|
||||
self.ready();
|
||||
} else if (res) {
|
||||
// Truthy values other than cursors or arrays are probably a
|
||||
// user mistake (possible returning a Mongo document via, say,
|
||||
@@ -1492,9 +1507,11 @@ Server = function (options = {}) {
|
||||
sendError("Already connected", msg);
|
||||
return;
|
||||
}
|
||||
Fiber(function () {
|
||||
|
||||
Meteor._runAsync(function() {
|
||||
self._handleConnect(socket, msg);
|
||||
}).run();
|
||||
})
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1511,9 +1528,9 @@ Server = function (options = {}) {
|
||||
|
||||
socket.on('close', function () {
|
||||
if (socket._meteorSession) {
|
||||
Fiber(function () {
|
||||
Meteor._runAsync(function() {
|
||||
socket._meteorSession.close();
|
||||
}).run();
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -1691,9 +1708,9 @@ Object.assign(Server.prototype, {
|
||||
// self.sessions to change while we're running this loop.
|
||||
self.sessions.forEach(function (session) {
|
||||
if (!session._dontStartNewUniversalSubs) {
|
||||
Fiber(function() {
|
||||
Meteor._runAsync(function() {
|
||||
session._startSubscription(handler);
|
||||
}).run();
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
var Fiber = Npm.require('fibers');
|
||||
|
||||
function sleep(ms) {
|
||||
return new Promise(resolve => setTimeout(resolve, ms));
|
||||
@@ -23,6 +22,8 @@ Meteor.publish('livedata_server_test_sub_context_async', async function(
|
||||
var methodInvocation = DDP._CurrentMethodInvocation.get();
|
||||
var publicationInvocation = DDP._CurrentPublicationInvocation.get();
|
||||
|
||||
// console.log('methodInvocation', methodInvocation);
|
||||
// console.log('publicationInvocation', !!publicationInvocation);
|
||||
// Check the publish function's environment variables and context.
|
||||
if (callback) {
|
||||
callback.call(this, methodInvocation, publicationInvocation);
|
||||
@@ -33,6 +34,7 @@ Meteor.publish('livedata_server_test_sub_context_async', async function(
|
||||
this.onStop(function() {
|
||||
var onStopMethodInvocation = DDP._CurrentMethodInvocation.get();
|
||||
var onStopPublicationInvocation = DDP._CurrentPublicationInvocation.get();
|
||||
|
||||
callback.call(
|
||||
this,
|
||||
onStopMethodInvocation,
|
||||
@@ -45,7 +47,7 @@ Meteor.publish('livedata_server_test_sub_context_async', async function(
|
||||
this.stop();
|
||||
} else {
|
||||
this.ready();
|
||||
Meteor.call('livedata_server_test_setuserid', userId);
|
||||
await Meteor.callAsync('livedata_server_test_setuserid', userId);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -164,8 +166,8 @@ Tinytest.addAsync('livedata server - async publish cursor', function(
|
||||
const remoteCollection = new Mongo.Collection('names', {
|
||||
connection: clientConn,
|
||||
});
|
||||
clientConn.subscribe('asyncPublishCursor', () => {
|
||||
const actual = remoteCollection.find().fetch();
|
||||
clientConn.subscribe('asyncPublishCursor', async () => {
|
||||
const actual = await remoteCollection.find().fetch();
|
||||
test.equal(actual[0].name, 'async');
|
||||
onComplete();
|
||||
});
|
||||
|
||||
@@ -1,6 +1,3 @@
|
||||
var Fiber = Npm.require('fibers');
|
||||
|
||||
|
||||
Tinytest.addAsync(
|
||||
"livedata server - connectionHandle.onClose()",
|
||||
function (test, onComplete) {
|
||||
@@ -87,8 +84,8 @@ Meteor.methods({
|
||||
return this.connection && this.connection.id;
|
||||
},
|
||||
|
||||
livedata_server_test_outer: function () {
|
||||
return Meteor.call('livedata_server_test_inner');
|
||||
livedata_server_test_outer: async function () {
|
||||
return await Meteor.callAsync('livedata_server_test_inner');
|
||||
},
|
||||
|
||||
livedata_server_test_setuserid: function (userId) {
|
||||
@@ -108,12 +105,17 @@ Tinytest.addAsync(
|
||||
});
|
||||
|
||||
makeTestConnection(
|
||||
test,
|
||||
function (clientConn, serverConn) {
|
||||
clientConn.call('livedata_server_test_inner');
|
||||
clientConn.disconnect();
|
||||
},
|
||||
onComplete
|
||||
test,
|
||||
function(clientConn, serverConn) {
|
||||
clientConn
|
||||
.callAsync('livedata_server_test_inner')
|
||||
.then(() => clientConn.disconnect())
|
||||
.catch(e => {
|
||||
onComplete();
|
||||
throw new Meteor.Error(e);
|
||||
});
|
||||
},
|
||||
onComplete
|
||||
);
|
||||
}
|
||||
);
|
||||
@@ -125,10 +127,11 @@ Tinytest.addAsync(
|
||||
makeTestConnection(
|
||||
test,
|
||||
function (clientConn, serverConn) {
|
||||
var res = clientConn.call('livedata_server_test_inner');
|
||||
test.equal(res, serverConn.id);
|
||||
clientConn.disconnect();
|
||||
onComplete();
|
||||
clientConn.callAsync('livedata_server_test_inner').then(res => {
|
||||
test.equal(res, serverConn.id);
|
||||
clientConn.disconnect();
|
||||
onComplete();
|
||||
});
|
||||
},
|
||||
onComplete
|
||||
);
|
||||
@@ -142,10 +145,11 @@ Tinytest.addAsync(
|
||||
makeTestConnection(
|
||||
test,
|
||||
function (clientConn, serverConn) {
|
||||
var res = clientConn.call('livedata_server_test_outer');
|
||||
test.equal(res, serverConn.id);
|
||||
clientConn.disconnect();
|
||||
onComplete();
|
||||
clientConn.callAsync('livedata_server_test_outer').then(res => {
|
||||
test.equal(res, serverConn.id);
|
||||
clientConn.disconnect();
|
||||
onComplete();
|
||||
});
|
||||
},
|
||||
onComplete
|
||||
);
|
||||
@@ -163,16 +167,16 @@ Meteor.publish("livedata_server_test_sub", function (connectionId) {
|
||||
this.stop();
|
||||
});
|
||||
|
||||
Meteor.publish("livedata_server_test_sub_method", function (connectionId) {
|
||||
Meteor.publish("livedata_server_test_sub_method", async function (connectionId) {
|
||||
var callback = onSubscription[connectionId];
|
||||
if (callback) {
|
||||
var id = Meteor.call('livedata_server_test_inner');
|
||||
var id = await Meteor.callAsync('livedata_server_test_inner');
|
||||
callback(id);
|
||||
}
|
||||
this.stop();
|
||||
});
|
||||
|
||||
Meteor.publish("livedata_server_test_sub_context", function (connectionId, userId) {
|
||||
Meteor.publish("livedata_server_test_sub_context", async function (connectionId, userId) {
|
||||
var callback = onSubscription[connectionId];
|
||||
var methodInvocation = DDP._CurrentMethodInvocation.get();
|
||||
var publicationInvocation = DDP._CurrentPublicationInvocation.get();
|
||||
@@ -194,7 +198,7 @@ Meteor.publish("livedata_server_test_sub_context", function (connectionId, userI
|
||||
this.stop();
|
||||
} else {
|
||||
this.ready();
|
||||
Meteor.call('livedata_server_test_setuserid', userId);
|
||||
await Meteor.callAsync('livedata_server_test_setuserid', userId);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -311,13 +315,13 @@ Tinytest.addAsync(
|
||||
);
|
||||
|
||||
Meteor.methods({
|
||||
testResolvedPromise(arg) {
|
||||
const invocation1 = DDP._CurrentMethodInvocation.get();
|
||||
async testResolvedPromise(arg) {
|
||||
const invocationRunningFromCallAsync1 = DDP._CurrentMethodInvocation._isCallAsyncMethodRunning();
|
||||
return Promise.resolve(arg).then(result => {
|
||||
const invocation2 = DDP._CurrentMethodInvocation.get();
|
||||
// This equality holds because Promise callbacks are bound to the
|
||||
// dynamic environment where .then was called.
|
||||
if (invocation1 !== invocation2) {
|
||||
const invocationRunningFromCallAsync2 = DDP._CurrentMethodInvocation._isCallAsyncMethodRunning();
|
||||
// What matters here is that both invocations are coming from the same call,
|
||||
// so both of them can be considered a simulation.
|
||||
if (invocationRunningFromCallAsync1 !== invocationRunningFromCallAsync2) {
|
||||
throw new Meteor.Error("invocation mismatch");
|
||||
}
|
||||
return result + " after waiting";
|
||||
@@ -344,9 +348,10 @@ Meteor.methods({
|
||||
|
||||
Tinytest.addAsync(
|
||||
"livedata server - waiting for Promise",
|
||||
(test, onComplete) => makeTestConnection(test, (clientConn, serverConn) => {
|
||||
(test, onComplete) => makeTestConnection(test, async (clientConn, serverConn) => {
|
||||
const testResolvedPromiseResult = await clientConn.callAsync("testResolvedPromise", "clientConn.call");
|
||||
test.equal(
|
||||
clientConn.call("testResolvedPromise", "clientConn.call"),
|
||||
testResolvedPromiseResult,
|
||||
"clientConn.call after waiting"
|
||||
);
|
||||
|
||||
|
||||
@@ -10,11 +10,6 @@ Npm.depends({
|
||||
});
|
||||
|
||||
Package.onUse(function (api) {
|
||||
if (process.env.DISABLE_FIBERS) {
|
||||
api.use('ddp-server-async', 'server');
|
||||
api.export('DDPServer', 'server');
|
||||
return;
|
||||
}
|
||||
api.use(['check', 'random', 'ejson', 'underscore',
|
||||
'retry', 'mongo-id', 'diff-sequence', 'ecmascript'],
|
||||
'server');
|
||||
|
||||
65
packages/ddp/ddp.d.ts
vendored
Normal file
65
packages/ddp/ddp.d.ts
vendored
Normal file
@@ -0,0 +1,65 @@
|
||||
import { Meteor } from 'meteor/meteor';
|
||||
|
||||
export namespace DDP {
|
||||
interface DDPStatic {
|
||||
subscribe(name: string, ...rest: any[]): Meteor.SubscriptionHandle;
|
||||
call(method: string, ...parameters: any[]): any;
|
||||
apply(method: string, ...parameters: any[]): any;
|
||||
methods(IMeteorMethodsDictionary: any): any;
|
||||
status(): DDPStatus;
|
||||
reconnect(): void;
|
||||
disconnect(): void;
|
||||
onReconnect(): void;
|
||||
}
|
||||
|
||||
function _allSubscriptionsReady(): boolean;
|
||||
|
||||
type Status = 'connected' | 'connecting' | 'failed' | 'waiting' | 'offline';
|
||||
|
||||
interface DDPStatus {
|
||||
connected: boolean;
|
||||
status: Status;
|
||||
retryCount: number;
|
||||
retryTime?: number | undefined;
|
||||
reason?: string | undefined;
|
||||
}
|
||||
|
||||
function connect(url: string): DDPStatic;
|
||||
}
|
||||
|
||||
export namespace DDPCommon {
|
||||
interface MethodInvocationOptions {
|
||||
userId: string | null;
|
||||
setUserId?: ((newUserId: string) => void) | undefined;
|
||||
isSimulation: boolean;
|
||||
connection: Meteor.Connection;
|
||||
randomSeed: string;
|
||||
}
|
||||
|
||||
/** The state for a single invocation of a method, referenced by this inside a method definition. */
|
||||
interface MethodInvocation {
|
||||
new (options: MethodInvocationOptions): MethodInvocation;
|
||||
/**
|
||||
* Call inside a method invocation. Allow subsequent method from this client to begin running in a new fiber.
|
||||
*/
|
||||
unblock(): void;
|
||||
/**
|
||||
* Set the logged in user.
|
||||
* @param userId The value that should be returned by `userId` on this connection.
|
||||
*/
|
||||
setUserId(userId: string | null): void;
|
||||
/**
|
||||
* The id of the user that made this method call, or `null` if no user was logged in.
|
||||
*/
|
||||
userId: string | null;
|
||||
/**
|
||||
* Access inside a method invocation. Boolean value, true if this invocation is a stub.
|
||||
*/
|
||||
isSimulation: boolean;
|
||||
/**
|
||||
* Access inside a method invocation. The [connection](#meteor_onconnection) that this method was received on. `null` if the method is not associated with a connection, eg. a server
|
||||
* initiated method call. Calls to methods made from a server method which was in turn initiated from the client share the same `connection`.
|
||||
*/
|
||||
connection: Meteor.Connection;
|
||||
}
|
||||
}
|
||||
3
packages/ddp/package-types.json
Normal file
3
packages/ddp/package-types.json
Normal file
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"typesEntry": "ddp.d.ts"
|
||||
}
|
||||
@@ -1,12 +1,14 @@
|
||||
Package.describe({
|
||||
summary: "Meteor's latency-compensated distributed data framework",
|
||||
version: '1.4.0'
|
||||
version: '1.4.1'
|
||||
});
|
||||
|
||||
Package.onUse(function (api) {
|
||||
api.use(['ddp-client'], ['client', 'server']);
|
||||
api.use(['ddp-server'], 'server');
|
||||
|
||||
api.addAssets('ddp.d.ts', 'server');
|
||||
|
||||
api.export('DDP');
|
||||
api.export('DDPServer', 'server');
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@ base64@1.0.12
|
||||
binary-heap@1.0.11
|
||||
boilerplate-generator@1.7.1
|
||||
callback-hook@1.3.0
|
||||
check@1.3.1
|
||||
check@1.3.2
|
||||
ddp@1.4.0
|
||||
ddp-client@2.4.1
|
||||
ddp-common@1.4.0
|
||||
@@ -36,14 +36,14 @@ mongo-id@1.0.8
|
||||
npm-mongo@3.9.0
|
||||
ordered-dict@1.1.0
|
||||
promise@0.11.2
|
||||
random@1.2.0
|
||||
random@1.2.1
|
||||
react-fast-refresh@0.1.1
|
||||
reload@1.3.1
|
||||
retry@1.1.0
|
||||
routepolicy@1.1.0
|
||||
socket-stream-client@0.3.3
|
||||
tinytest@1.1.0
|
||||
tracker@1.2.0
|
||||
underscore@1.0.10
|
||||
tracker@1.2.1
|
||||
underscore@1.0.11
|
||||
webapp@1.10.1
|
||||
webapp-hashing@1.1.0
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
Package.describe({
|
||||
summary: "An implementation of a diff algorithm on arrays and objects.",
|
||||
version: '1.1.1',
|
||||
version: '1.1.2',
|
||||
documentation: null
|
||||
});
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user