mirror of
https://github.com/meteor/meteor.git
synced 2026-05-02 03:01:46 -04:00
Merge branch 'devel' into dev/add-tinytest-filter-option
This commit is contained in:
@@ -76,8 +76,8 @@ run_save_node_bin: &run_save_node_bin
|
||||
fi
|
||||
|
||||
# This environment is set to every job (and the initial build).
|
||||
build_machine_environment: &build_machine_environment
|
||||
# Specify that we want an actual machine (ala Circle 1.0), not a Docker image.
|
||||
build_machine_environment:
|
||||
&build_machine_environment # Specify that we want an actual machine (ala Circle 1.0), not a Docker image.
|
||||
docker:
|
||||
- image: meteor/circleci:2024.09.11-android-34-node-20
|
||||
resource_class: large
|
||||
@@ -104,8 +104,8 @@ build_machine_environment: &build_machine_environment
|
||||
|
||||
# These will be evaled before each command.
|
||||
PRE_TEST_COMMANDS: |-
|
||||
ulimit -c unlimited; # Set core dump size as Ubuntu 14.04 lacks prlimit.
|
||||
ulimit -a # Display all ulimit settings for transparency.
|
||||
ulimit -c unlimited; # Set core dump size as Ubuntu 14.04 lacks prlimit.
|
||||
ulimit -a # Display all ulimit settings for transparency.
|
||||
|
||||
# This is only to make Meteor self-test not remind us that we can set
|
||||
# this argument for self-tests.
|
||||
@@ -178,7 +178,7 @@ jobs:
|
||||
command: |
|
||||
eval $PRE_TEST_COMMANDS;
|
||||
cd dev_bundle/lib
|
||||
../../meteor npm install @types/node@20.10.5 --save-dev
|
||||
../../meteor npm install @types/node@22.7.4 --save-dev
|
||||
# Ensure that meteor/tools has no TypeScript errors.
|
||||
../../meteor npm install -g typescript
|
||||
cd ../../
|
||||
@@ -765,7 +765,9 @@ jobs:
|
||||
if [[ -n "$CIRCLE_PULL_REQUEST" ]]; then
|
||||
PR_NUMBER=$(echo $CIRCLE_PULL_REQUEST | sed 's|.*/pull/\([0-9]*\)|\1|')
|
||||
PR_BRANCH=$(curl -s https://api.github.com/repos/meteor/meteor/pulls/$PR_NUMBER | jq -r .head.ref)
|
||||
git clone --branch $PR_BRANCH https://github.com/meteor/meteor.git ${CHECKOUT_METEOR_DOCS}
|
||||
git clone https://github.com/meteor/meteor.git ${CHECKOUT_METEOR_DOCS}
|
||||
cd ${CHECKOUT_METEOR_DOCS}
|
||||
git fetch origin pull/$PR_NUMBER/head:$PR_BRANCH
|
||||
else
|
||||
git clone --branch $CIRCLE_BRANCH https://github.com/meteor/meteor.git ${CHECKOUT_METEOR_DOCS}
|
||||
fi
|
||||
|
||||
62
.envrc
62
.envrc
@@ -27,6 +27,10 @@ function @test-self {
|
||||
@meteor self-test "$@"
|
||||
}
|
||||
|
||||
function @test-in-console {
|
||||
"$ROOT_DIR/packages/test-in-console/run.sh" "$@"
|
||||
}
|
||||
|
||||
function @check-syntax {
|
||||
node "$ROOT_DIR/scripts/admin/check-legacy-syntax/check-syntax.js"
|
||||
}
|
||||
@@ -50,4 +54,60 @@ function @docs-start {
|
||||
|
||||
function @docs-migration-start {
|
||||
npm run docs:dev --prefix "$ROOT_DIR/v3-docs/v3-migration-docs"
|
||||
}
|
||||
}
|
||||
|
||||
function @get-changes {
|
||||
git diff --numstat HEAD~1 HEAD | awk '($1 + $2) <= 5000 {print $3}'
|
||||
}
|
||||
|
||||
function @summarize-changes {
|
||||
changes=$(@get-changes)
|
||||
|
||||
if [ -n "$changes" ]; then
|
||||
changes=$(git diff HEAD~1 HEAD -- $(echo "$changes" | tr '\n' ' '))
|
||||
else
|
||||
changes=$(git diff HEAD~1 HEAD)
|
||||
fi
|
||||
|
||||
echo "$changes" | llm -s "Summarize the following changes in a few sentences:"
|
||||
}
|
||||
|
||||
function @packages-bumped {
|
||||
git diff --name-only devel...$(git branch --show-current) | grep "packages/.*/package.js$" | while IFS= read -r file; do
|
||||
if ! git show devel:$file > /dev/null 2>&1; then
|
||||
continue
|
||||
fi
|
||||
|
||||
old=$(git show devel:$file | grep -o "version: *['\"][^'\"]*['\"]" | sed "s/version: *.['\"]//;s/['\"].*//")
|
||||
version=$(grep -o "version: *['\"][^'\"]*['\"]" "$file" | sed "s/version: *.['\"]//;s/['\"].*//")
|
||||
name=$(grep -o "name: *['\"][^'\"]*['\"]" "$file" | sed "s/name: *.['\"]//;s/['\"].*//")
|
||||
|
||||
pkg_name=$(echo "$file" | sed -E 's|packages/([^/]*/)?([^/]*)/package\.js|\2|')
|
||||
|
||||
version_in_red=$(tput setaf 1)$version$(tput sgr0)
|
||||
|
||||
if [[ "$version" != "$old" ]]; then
|
||||
echo "- $pkg_name@$version_in_red"
|
||||
fi
|
||||
done
|
||||
}
|
||||
|
||||
function @packages-bumped-npm {
|
||||
git diff --name-only devel...$(git branch --show-current) | grep "npm-packages/.*/package.json$" | while IFS= read -r file; do
|
||||
if ! git show devel:$file > /dev/null 2>&1; then
|
||||
continue
|
||||
fi
|
||||
|
||||
old=$(git show devel:$file | grep -o "version: *['\"][^'\"]*['\"]" | sed "s/version: *.['\"]//;s/['\"].*//")
|
||||
version=$(grep -o "\"version\": *['\"][^'\"]*['\"]" "$file" | sed "s/\"version\": *.['\"]//;s/['\"].*//")
|
||||
name=$(grep -o "\"name\": *['\"][^'\"]*['\"]" "$file" | sed "s/\"name\": *.['\"]//;s/['\"].*//")
|
||||
|
||||
pkg_name=$(echo "$file" | sed -E 's|npm-packages/([^/]*/)?([^/]*)/package\.json|\2|')
|
||||
|
||||
version_in_red=$(tput setaf 1)$version$(tput sgr0)
|
||||
|
||||
if [[ "$version" != "$old" ]]; then
|
||||
echo "- $pkg_name@$version_in_red"
|
||||
fi
|
||||
done
|
||||
}
|
||||
|
||||
2
.github/workflows/check-code-style.yml
vendored
2
.github/workflows/check-code-style.yml
vendored
@@ -13,7 +13,7 @@ jobs:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 20.x
|
||||
node-version: 22.x
|
||||
- run: npm ci
|
||||
- name: Run ESLint@8
|
||||
run: npx eslint@8 "./npm-packages/meteor-installer/**/*.js"
|
||||
|
||||
3
.github/workflows/check-syntax.yml
vendored
3
.github/workflows/check-syntax.yml
vendored
@@ -1,6 +1,5 @@
|
||||
name: Check legacy syntax
|
||||
on:
|
||||
- push
|
||||
- pull_request
|
||||
jobs:
|
||||
check-code-style:
|
||||
@@ -9,7 +8,7 @@ jobs:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 20.x
|
||||
node-version: 22.x
|
||||
- run: cd scripts/admin/check-legacy-syntax && npm ci
|
||||
- name: Check syntax
|
||||
run: cd scripts/admin/check-legacy-syntax && node check-syntax.js
|
||||
|
||||
2
.github/workflows/guide.yml
vendored
2
.github/workflows/guide.yml
vendored
@@ -13,7 +13,7 @@ jobs:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 12.x
|
||||
node-version: 22.x
|
||||
- name: Build the Guide
|
||||
run: npm ci && npm run build
|
||||
- name: Deploy to Netlify for preview
|
||||
|
||||
11
.github/workflows/meteor-selftest-windows.yml
vendored
11
.github/workflows/meteor-selftest-windows.yml
vendored
@@ -22,15 +22,22 @@ env:
|
||||
jobs:
|
||||
test:
|
||||
runs-on: windows-2019-meteor
|
||||
concurrency:
|
||||
group: ${{ github.head_ref }}-meteor-selftest-windows
|
||||
cancel-in-progress: true
|
||||
|
||||
steps:
|
||||
- name: cleanup
|
||||
shell: powershell
|
||||
run: Remove-Item -Recurse -Force ${{ github.workspace }}\*
|
||||
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v2
|
||||
with:
|
||||
node-version: 20.x
|
||||
node-version: 22.x
|
||||
|
||||
- name: Install dependencies
|
||||
shell: pwsh
|
||||
@@ -45,7 +52,7 @@ jobs:
|
||||
.\scripts\windows\ci\test.ps1
|
||||
|
||||
- name: Cache dependencies
|
||||
uses: actions/cache@v2
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: |
|
||||
.\dev_bundle
|
||||
|
||||
@@ -21,7 +21,7 @@ jobs:
|
||||
- name: Use Node.js ${{ matrix.node-version }}
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 20.x
|
||||
node-version: 22.x
|
||||
cache: npm
|
||||
- run: npm ci
|
||||
- run: npm test
|
||||
|
||||
52
.github/workflows/test-deprecated-packages.yml
vendored
Normal file
52
.github/workflows/test-deprecated-packages.yml
vendored
Normal file
@@ -0,0 +1,52 @@
|
||||
name: Test Deprecated Packages
|
||||
|
||||
# Disabled until we figure out how to fix the error from puppeteer
|
||||
# Runs on Travis CI for now
|
||||
#
|
||||
#on:
|
||||
# push:
|
||||
# branches:
|
||||
# - main
|
||||
# pull_request:
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
concurrency:
|
||||
group: ${{ github.head_ref }}-test-deprecated-packages
|
||||
cancel-in-progress: true
|
||||
timeout-minutes: 60
|
||||
|
||||
env:
|
||||
PUPPETEER_DOWNLOAD_PATH: /home/runner/.npm/chromium
|
||||
|
||||
steps:
|
||||
- name: Update and install dependencies
|
||||
run: sudo apt-get update && sudo apt-get install -y libnss3 g++-12
|
||||
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Set up Node.js
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: 20.15.1
|
||||
|
||||
- name: Cache Node.js modules
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
path: |
|
||||
~/.npm
|
||||
.meteor
|
||||
.babel-cache
|
||||
dev_bundle
|
||||
/home/runner/.npm/chromium
|
||||
key: ${{ runner.os }}-node-${{ hashFiles('meteor', '**/package-lock.json') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-node-
|
||||
|
||||
- name: Install dependencies
|
||||
run: npm install
|
||||
|
||||
- name: Run tests
|
||||
run: ./packages/test-in-console/run.sh
|
||||
4
.gitignore
vendored
4
.gitignore
vendored
@@ -35,3 +35,7 @@ packages/**/.npm
|
||||
|
||||
# doc files should not be committed
|
||||
packages/**/*.docs.js
|
||||
|
||||
#cursor
|
||||
.cursorignore
|
||||
.cursorrules
|
||||
|
||||
@@ -4,7 +4,7 @@ dist: jammy
|
||||
sudo: required
|
||||
services: xvfb
|
||||
node_js:
|
||||
- "20.15.1"
|
||||
- "22.14.0"
|
||||
cache:
|
||||
directories:
|
||||
- ".meteor"
|
||||
@@ -16,6 +16,7 @@ env:
|
||||
- CXX=g++-12
|
||||
- phantom=false
|
||||
- PUPPETEER_DOWNLOAD_PATH=~/.npm/chromium
|
||||
- TEST_PACKAGES_EXCLUDE=stylus
|
||||
addons:
|
||||
apt:
|
||||
sources:
|
||||
|
||||
32
README.md
32
README.md
@@ -1,11 +1,13 @@
|
||||
<div align="center">
|
||||
<a href="https://www.meteor.com" target="_blank">
|
||||
<img align="center" width="225" src="https://user-images.githubusercontent.com/841294/26841702-0902bbee-4af3-11e7-9805-0618da66a246.png">
|
||||
<picture>
|
||||
<source media="(prefers-color-scheme: dark)" srcset="https://dmtgy0px4zdqn.cloudfront.net/images/meteor-logo.webp">
|
||||
<source media="(prefers-color-scheme: light)" srcset="https://github.com/user-attachments/assets/0467afb6-4f36-4cad-9d78-237150d5d881">
|
||||
<img alt="Meteor logo" src="https://github.com/user-attachments/assets/0467afb6-4f36-4cad-9d78-237150d5d881" width="300">
|
||||
</picture>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<br>
|
||||
|
||||
<div align="center">
|
||||
|
||||
[](https://app.travis-ci.com/github/meteor/meteor)
|
||||
@@ -54,23 +56,20 @@ How about trying a tutorial to get started with your favorite technology?
|
||||
| [<img align="left" width="25" src="https://upload.wikimedia.org/wikipedia/commons/a/a7/React-icon.svg"> React](https://docs.meteor.com/tutorials/react/) |
|
||||
| - |
|
||||
| [<img align="left" width="25" src="https://progsoft.net/images/blaze-css-icon-3e80acb3996047afd09f1150f53fcd78e98c1e1b.png"> Blaze](https://blaze-tutorial.meteor.com/) |
|
||||
| [<img align="left" width="25" src="https://vuejs.org/images/logo.png"> Vue](https://vue-tutorial.meteor.com/) |
|
||||
| [<img align="left" width="25" src="https://upload.wikimedia.org/wikipedia/commons/thumb/1/1b/Svelte_Logo.svg/1200px-Svelte_Logo.svg.png"> Svelte](https://svelte-tutorial.meteor.com/) |
|
||||
|
||||
Next, read the [documentation](https://docs.meteor.com/) and get some [examples](https://github.com/meteor/examples).
|
||||
| [<img align="left" width="25" src="https://vuejs.org/images/logo.png"> Vue](https://docs.meteor.com/tutorials/vue/meteorjs3-vue3-vue-meteor-tracker.html) |
|
||||
|
||||
# 🚀 Quick Start
|
||||
|
||||
On your platform, use this line:
|
||||
|
||||
```shell
|
||||
> npm install -g meteor
|
||||
> npx meteor
|
||||
```
|
||||
|
||||
🚀 To create a project:
|
||||
|
||||
```shell
|
||||
> meteor create my-app
|
||||
> meteor create
|
||||
```
|
||||
|
||||
☄️ Run it:
|
||||
@@ -84,10 +83,9 @@ meteor
|
||||
|
||||
**Building an application with Meteor?**
|
||||
|
||||
* Deploy on [Meteor Cloud](https://www.meteor.com/cloud)
|
||||
* Deploy on [Galaxy](https://www.meteor.com/cloud)
|
||||
* Discuss on [Forums](https://forums.meteor.com/)
|
||||
* Join the Meteor Discord by clicking this [invite link](https://discord.gg/hZkTCaVjmT).
|
||||
* Announcement list. Subscribe in the [footer](https://www.meteor.com/).
|
||||
|
||||
|
||||
Interested in helping or contributing to Meteor? These resources will help:
|
||||
@@ -96,15 +94,3 @@ Interested in helping or contributing to Meteor? These resources will help:
|
||||
* [Contribution guidelines](CONTRIBUTING.md)
|
||||
* [Feature requests](https://github.com/meteor/meteor/discussions/)
|
||||
* [Issue tracker](https://github.com/meteor/meteor/issues)
|
||||
|
||||
To uninstall Meteor:
|
||||
- If installed via npm, run:
|
||||
```shell
|
||||
meteor-installer uninstall
|
||||
```
|
||||
- If installed via curl, run:
|
||||
```shell
|
||||
rm -rf ~/.meteor
|
||||
sudo rm /usr/local/bin/meteor
|
||||
```
|
||||
To find more information about installation, [read here](https://docs.meteor.com/about/install.html#uninstall).
|
||||
|
||||
@@ -4,7 +4,8 @@
|
||||
|
||||
| Version | Support Status
|
||||
| ------- | --------------
|
||||
| 2.x.y | ✅ all security issues
|
||||
| 3.x.y | ✅ all security issues
|
||||
| 2.x.y | ⚠️ only major security issues (Until 2025-07)
|
||||
| <= 1.12.x | ❌ no longer supported
|
||||
|
||||
## Reporting a Vulnerability
|
||||
|
||||
@@ -88,7 +88,6 @@ sidebar_categories:
|
||||
- packages/server-render
|
||||
- packages/spacebars
|
||||
- packages/standard-minifier-css
|
||||
- packages/underscore
|
||||
- packages/url
|
||||
- packages/webapp
|
||||
- packages/packages-listing
|
||||
@@ -393,7 +392,6 @@ redirects:
|
||||
/#/full/oauth-encryption: 'packages/oauth-encryption.html'
|
||||
/#/full/random: 'packages/random.html'
|
||||
/#/full/spiderable: 'packages/spiderable.html'
|
||||
/#/full/underscore: 'packages/underscore.html'
|
||||
/#/full/webapp: 'packages/webapp.html'
|
||||
'#meteor_isclient': 'api/core.html#Meteor-isClient'
|
||||
'#meteor_isserver': 'api/core.html#Meteor-isServer'
|
||||
@@ -669,6 +667,5 @@ redirects:
|
||||
'#oauth-encryption': 'packages/oauth-encryption.html'
|
||||
'#random': 'packages/random.html'
|
||||
'#spiderable': 'packages/spiderable.html'
|
||||
'#underscore': 'packages/underscore.html'
|
||||
'#webapp': 'packages/webapp.html'
|
||||
'#pkg_spacebars': 'packages/spacebars.html'
|
||||
|
||||
@@ -2935,6 +2935,7 @@ N/A
|
||||
setMinimumBrowserVersions({
|
||||
chrome: 49,
|
||||
firefox: 45,
|
||||
firefoxIOS: 100,
|
||||
edge: 12,
|
||||
ie: Infinity, // Sorry, IE11.
|
||||
mobile_safari: [9, 2], // 9.2.0+
|
||||
|
||||
@@ -4651,6 +4651,7 @@ N/A
|
||||
setMinimumBrowserVersions({
|
||||
chrome: 49,
|
||||
firefox: 45,
|
||||
firefoxIOS: 100,
|
||||
edge: 12,
|
||||
ie: Infinity, // Sorry, IE11.
|
||||
mobile_safari: [9, 2], // 9.2.0+
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
"packages/ddp/sockjs-0.3.4.js",
|
||||
"packages/test-in-browser/diff_match_patch_uncompressed.js",
|
||||
"packages/jquery/jquery.js",
|
||||
"packages/underscore/underscore.js",
|
||||
"packages/json/json2.js",
|
||||
"packages/minimongo/minimongo_tests.js",
|
||||
"tools/node_modules",
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
title: Docs
|
||||
---
|
||||
|
||||
> Meteor 2.x uses the deprecated Node.js 14. Meteor 3.0 has been released and runs on Node.js 20. Check out our [v3 docs](https://v3-docs.meteor.com) and [migration guide](https://v3-migration-docs.meteor.com/).
|
||||
> Meteor 2.x runs on a deprecated Node.js version (14). Meteor 3 has been released with support for the latest Node.js LTS version. For more information, please consult our [migration guide](https://v3-migration-docs.meteor.com/) and the [latest docs](https://v3-docs.meteor.com).
|
||||
|
||||
<!-- XXX: note that this content is somewhat duplicated on the guide, and should be updated in parallel -->
|
||||
<h2 id="what-is-meteor">What is Meteor?</h2>
|
||||
|
||||
@@ -8,7 +8,7 @@ You need to install the Meteor command line tool to create, run, and manage your
|
||||
|
||||
<h3 id="prereqs-node">Node.js version</h3>
|
||||
|
||||
> Meteor 2.x uses the deprecated Node.js 14. Meteor 3.0 has been released and runs on Node.js 20. Check out our [v3 docs](https://v3-docs.meteor.com) and [migration guide](https://v3-migration-docs.meteor.com/).
|
||||
> Meteor 2.x runs on a deprecated Node.js version (14). Meteor 3 has been released with support for the latest Node.js LTS version. For more information, please consult our [migration guide](https://v3-migration-docs.meteor.com/) and the [latest docs](https://v3-docs.meteor.com).
|
||||
|
||||
- Node.js version >= 10 and <= 14 is required.
|
||||
- We recommend you using [nvm](https://github.com/nvm-sh/nvm) or [Volta](https://volta.sh/) for managing Node.js versions.
|
||||
@@ -30,7 +30,8 @@ You need to install the Meteor command line tool to create, run, and manage your
|
||||
|
||||
Install the latest official version of Meteor.js from your terminal by running one of the commands below. You can check our [changelog](https://docs.meteor.com/changelog.html) for the release notes.
|
||||
|
||||
> Run `node -v` to ensure you are using Node.js 14. Meteor 3.0, currently in its Release Candidate version, runs on Node.js v20.
|
||||
> Meteor 2.x runs on a deprecated Node.js version (14). Meteor 3 is released with support for the latest Node.js LTS version.
|
||||
> For more information, please consult our [migration guide](https://guide.meteor.com/3.0-migration.html) and the [new docs](https://docs.meteor.com/).
|
||||
|
||||
For Windows, Linux and OS X, you can run the following command:
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@ title: Introduction
|
||||
description: This is the guide for using Meteor, a full-stack JavaScript platform for developing modern web and mobile applications.
|
||||
---
|
||||
|
||||
> Meteor 2.x uses the deprecated Node.js 14. Meteor 3.0 has been released and runs on Node.js 20. Check out our [v3 docs](https://v3-docs.meteor.com) and [migration guide](https://v3-migration-docs.meteor.com/).
|
||||
> Meteor 2.x runs on a deprecated Node.js version (14). Meteor 3 has been released with support for the latest Node.js LTS version. For more information, please consult our [migration guide](https://v3-migration-docs.meteor.com/) and the [latest docs](https://v3-docs.meteor.com).
|
||||
|
||||
<!-- XXX: note that this content is somewhat duplicated on the docs, and should be updated in parallel -->
|
||||
<h2 id="what-is-meteor">What is Meteor?</h2>
|
||||
|
||||
@@ -238,9 +238,11 @@ import { Tracker } from 'meteor/tracker';
|
||||
const withDiv = function withDiv(callback) {
|
||||
const el = document.createElement('div');
|
||||
document.body.appendChild(el);
|
||||
let view = null
|
||||
try {
|
||||
callback(el);
|
||||
view = callback(el);
|
||||
} finally {
|
||||
if (view) Blaze.remove(view)
|
||||
document.body.removeChild(el);
|
||||
}
|
||||
};
|
||||
@@ -248,9 +250,10 @@ const withDiv = function withDiv(callback) {
|
||||
export const withRenderedTemplate = function withRenderedTemplate(template, data, callback) {
|
||||
withDiv((el) => {
|
||||
const ourTemplate = isString(template) ? Template[template] : template;
|
||||
Blaze.renderWithData(ourTemplate, data, el);
|
||||
const view = Blaze.renderWithData(ourTemplate, data, el);
|
||||
Tracker.flush();
|
||||
callback(el);
|
||||
return view
|
||||
});
|
||||
};
|
||||
```
|
||||
|
||||
4
meteor
4
meteor
@@ -1,6 +1,6 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
BUNDLE_VERSION=20.18.0.1
|
||||
BUNDLE_VERSION=22.14.0.4
|
||||
|
||||
|
||||
# OS Check. Put here because here is where we download the precompiled
|
||||
@@ -123,6 +123,7 @@ fi
|
||||
|
||||
DEV_BUNDLE="$SCRIPT_DIR/dev_bundle"
|
||||
METEOR="$SCRIPT_DIR/tools/index.js"
|
||||
PROCESS_REQUIRES="$SCRIPT_DIR/tools/node-process-warnings.js"
|
||||
|
||||
# Set the nofile ulimit as high as permitted by the hard-limit/kernel
|
||||
if [ "$(ulimit -Sn)" != "unlimited" ]; then
|
||||
@@ -148,5 +149,6 @@ fi
|
||||
exec "$DEV_BUNDLE/bin/node" \
|
||||
--max-old-space-size=4096 \
|
||||
--no-wasm-code-gc \
|
||||
--require="$PROCESS_REQUIRES"\
|
||||
${TOOL_NODE_FLAGS} \
|
||||
"$METEOR" "$@"
|
||||
|
||||
@@ -48,6 +48,7 @@ SET BABEL_CACHE_DIR=%~dp0\.babel-cache
|
||||
|
||||
"%~dp0\dev_bundle\bin\node.exe" ^
|
||||
--no-wasm-code-gc ^
|
||||
--require="%~dp0\tools\node-process-warnings.js" ^
|
||||
%TOOL_NODE_FLAGS% ^
|
||||
"%~dp0\tools\index.js" %*
|
||||
|
||||
|
||||
@@ -10,11 +10,11 @@ var packageJson = {
|
||||
dependencies: {
|
||||
// Explicit dependency because we are replacing it with a bundled version
|
||||
// and we want to make sure there are no dependencies on a higher version
|
||||
npm: "10.8.2",
|
||||
npm: "10.9.2",
|
||||
pacote: "https://github.com/meteor/pacote/tarball/a81b0324686e85d22c7688c47629d4009000e8b8",
|
||||
"node-gyp": "9.4.0",
|
||||
"@mapbox/node-pre-gyp": "1.0.11",
|
||||
typescript: "5.6.2",
|
||||
typescript: "5.6.3",
|
||||
"@meteorjs/babel": "7.20.0",
|
||||
"@meteorjs/reify": "0.25.3",
|
||||
// So that Babel can emit require("@babel/runtime/helpers/...") calls.
|
||||
|
||||
20
npm-packages/meteor-babel/package-lock.json
generated
20
npm-packages/meteor-babel/package-lock.json
generated
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@meteorjs/babel",
|
||||
"version": "7.20.0",
|
||||
"version": "7.20.1",
|
||||
"lockfileVersion": 1,
|
||||
"requires": true,
|
||||
"dependencies": {
|
||||
@@ -786,9 +786,9 @@
|
||||
}
|
||||
},
|
||||
"@meteorjs/reify": {
|
||||
"version": "0.25.3",
|
||||
"resolved": "https://registry.npmjs.org/@meteorjs/reify/-/reify-0.25.3.tgz",
|
||||
"integrity": "sha512-OVtWOLNvonGwA9Uowzp18q6L2Z3V/kPItS1bNyJMryfXFnosM2O0Hm3pYcxRfP36/0tc1BCiV3dA8yrr8RgMUA==",
|
||||
"version": "0.25.4",
|
||||
"resolved": "https://registry.npmjs.org/@meteorjs/reify/-/reify-0.25.4.tgz",
|
||||
"integrity": "sha512-/HwynJK85QtS2Rm26M9TS8aEMnqVJ2TIzJNJTGAQz+G6cTYmJGWaU4nFH96oxiDIBbnT6Y3TfX92HDuS9TtNRg==",
|
||||
"requires": {
|
||||
"acorn": "^8.8.1",
|
||||
"magic-string": "^0.25.3",
|
||||
@@ -804,14 +804,14 @@
|
||||
}
|
||||
},
|
||||
"@types/estree": {
|
||||
"version": "1.0.5",
|
||||
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz",
|
||||
"integrity": "sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw=="
|
||||
"version": "1.0.6",
|
||||
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.6.tgz",
|
||||
"integrity": "sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw=="
|
||||
},
|
||||
"acorn": {
|
||||
"version": "8.12.1",
|
||||
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.12.1.tgz",
|
||||
"integrity": "sha512-tcpGyI9zbizT9JbV6oYE477V6mTlXvvi0T0G3SNIYE2apm/G5huBa1+K89VGeovbg+jycCrfhl3ADxErOuO6Jg=="
|
||||
"version": "8.13.0",
|
||||
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.13.0.tgz",
|
||||
"integrity": "sha512-8zSiw54Oxrdym50NlZ9sUusyO1Z1ZchgRLWRaK6c86XJFClyCgFKetdowBg5bKxyp/u+CDBJG4Mpp0m3HLZl9w=="
|
||||
},
|
||||
"ansi-colors": {
|
||||
"version": "3.2.3",
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@meteorjs/babel",
|
||||
"author": "Meteor <dev@meteor.com>",
|
||||
"version": "7.20.0",
|
||||
"version": "7.20.1",
|
||||
"license": "MIT",
|
||||
"type": "commonjs",
|
||||
"description": "Babel wrapper package for use with Meteor",
|
||||
@@ -42,7 +42,7 @@
|
||||
"@babel/template": "^7.16.7",
|
||||
"@babel/traverse": "^7.17.0",
|
||||
"@babel/types": "^7.17.0",
|
||||
"@meteorjs/reify": "0.25.3",
|
||||
"@meteorjs/reify": "0.25.4",
|
||||
"babel-preset-meteor": "^7.10.0",
|
||||
"babel-preset-minify": "^0.5.1",
|
||||
"convert-source-map": "^1.6.0",
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
const os = require('os');
|
||||
const path = require('path');
|
||||
|
||||
const METEOR_LATEST_VERSION = '3.0.4';
|
||||
const METEOR_LATEST_VERSION = '3.2';
|
||||
const sudoUser = process.env.SUDO_USER || '';
|
||||
function isRoot() {
|
||||
return process.getuid && process.getuid() === 0;
|
||||
|
||||
1585
npm-packages/meteor-installer/package-lock.json
generated
1585
npm-packages/meteor-installer/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "meteor",
|
||||
"version": "3.0.4",
|
||||
"version": "3.2.0",
|
||||
"description": "Install Meteor",
|
||||
"main": "install.js",
|
||||
"scripts": {
|
||||
|
||||
@@ -1,3 +1,15 @@
|
||||
v1.2.13 - 2025-02-27
|
||||
|
||||
* Update `elliptic` to v6.6.1 to address a security vulnerability.
|
||||
|
||||
v1.2.12 - 2024-10-31
|
||||
|
||||
* Update `elliptic` to v6.6.0 to address a security vulnerability.
|
||||
|
||||
v1.2.11 - 2024-10-25
|
||||
|
||||
* Update `rimraf` to v5 to remove vulnerable `inflight` dependency.
|
||||
|
||||
v1.2.8 - 2024-04-01
|
||||
* Add new dependency `@meteorjs/crypto-browserify` to replace `crypto-browserify` as it had unsafe dependencies.
|
||||
|
||||
|
||||
522
npm-packages/meteor-node-stubs/package-lock.json
generated
522
npm-packages/meteor-node-stubs/package-lock.json
generated
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "meteor-node-stubs",
|
||||
"version": "1.2.10",
|
||||
"version": "1.2.12",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "meteor-node-stubs",
|
||||
"version": "1.2.10",
|
||||
"version": "1.2.12",
|
||||
"bundleDependencies": [
|
||||
"@meteorjs/crypto-browserify",
|
||||
"assert",
|
||||
@@ -41,7 +41,7 @@
|
||||
"console-browserify": "^1.2.0",
|
||||
"constants-browserify": "^1.0.0",
|
||||
"domain-browser": "^4.23.0",
|
||||
"elliptic": "^6.5.7",
|
||||
"elliptic": "^6.6.1",
|
||||
"events": "^3.3.0",
|
||||
"https-browserify": "^1.0.0",
|
||||
"os-browserify": "^0.3.0",
|
||||
@@ -60,7 +60,24 @@
|
||||
"vm-browserify": "^1.1.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"rimraf": "^2.7.1"
|
||||
"rimraf": "^5.0.10"
|
||||
}
|
||||
},
|
||||
"node_modules/@isaacs/cliui": {
|
||||
"version": "8.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz",
|
||||
"integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"string-width": "^5.1.2",
|
||||
"string-width-cjs": "npm:string-width@^4.2.0",
|
||||
"strip-ansi": "^7.0.1",
|
||||
"strip-ansi-cjs": "npm:strip-ansi@^6.0.1",
|
||||
"wrap-ansi": "^8.1.0",
|
||||
"wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/@meteorjs/crypto-browserify": {
|
||||
@@ -102,6 +119,40 @@
|
||||
"node": ">=4"
|
||||
}
|
||||
},
|
||||
"node_modules/@pkgjs/parseargs": {
|
||||
"version": "0.11.0",
|
||||
"resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz",
|
||||
"integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==",
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"engines": {
|
||||
"node": ">=14"
|
||||
}
|
||||
},
|
||||
"node_modules/ansi-regex": {
|
||||
"version": "6.1.0",
|
||||
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz",
|
||||
"integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/chalk/ansi-regex?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/ansi-styles": {
|
||||
"version": "6.2.1",
|
||||
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz",
|
||||
"integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/chalk/ansi-styles?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/asn1.js": {
|
||||
"version": "4.10.1",
|
||||
"resolved": "https://registry.npmjs.org/asn1.js/-/asn1.js-4.10.1.tgz",
|
||||
@@ -177,13 +228,12 @@
|
||||
"inBundle": true
|
||||
},
|
||||
"node_modules/brace-expansion": {
|
||||
"version": "1.1.11",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
|
||||
"integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz",
|
||||
"integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"balanced-match": "^1.0.0",
|
||||
"concat-map": "0.0.1"
|
||||
"balanced-match": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/brorand": {
|
||||
@@ -390,10 +440,22 @@
|
||||
"safe-buffer": "^5.0.1"
|
||||
}
|
||||
},
|
||||
"node_modules/concat-map": {
|
||||
"version": "0.0.1",
|
||||
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
|
||||
"integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==",
|
||||
"node_modules/color-convert": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
|
||||
"integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"color-name": "~1.1.4"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=7.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/color-name": {
|
||||
"version": "1.1.4",
|
||||
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
|
||||
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/console-browserify": {
|
||||
@@ -457,6 +519,21 @@
|
||||
"sha.js": "^2.4.8"
|
||||
}
|
||||
},
|
||||
"node_modules/cross-spawn": {
|
||||
"version": "7.0.6",
|
||||
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz",
|
||||
"integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"path-key": "^3.1.0",
|
||||
"shebang-command": "^2.0.0",
|
||||
"which": "^2.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 8"
|
||||
}
|
||||
},
|
||||
"node_modules/define-data-property": {
|
||||
"version": "1.1.4",
|
||||
"resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz",
|
||||
@@ -531,10 +608,16 @@
|
||||
"url": "https://bevry.me/fund"
|
||||
}
|
||||
},
|
||||
"node_modules/eastasianwidth": {
|
||||
"version": "0.2.0",
|
||||
"resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz",
|
||||
"integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/elliptic": {
|
||||
"version": "6.5.7",
|
||||
"resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.5.7.tgz",
|
||||
"integrity": "sha512-ESVCtTwiA+XhY3wyh24QqRGBoP3rEdDUl3EDUUo9tft074fi19IrdpH7hLCMMP3CIj7jb3W96rn8lt/BqIlt5Q==",
|
||||
"version": "6.6.1",
|
||||
"resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.6.1.tgz",
|
||||
"integrity": "sha512-RaddvvMatK2LJHqFJ+YA4WysVN5Ita9E35botqIYspQ4TkRAlCicdzKOjlyv/1Za5RyTNn7di//eEV0uTAfe3g==",
|
||||
"inBundle": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
@@ -553,6 +636,12 @@
|
||||
"integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==",
|
||||
"inBundle": true
|
||||
},
|
||||
"node_modules/emoji-regex": {
|
||||
"version": "9.2.2",
|
||||
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz",
|
||||
"integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/es-define-property": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.0.tgz",
|
||||
@@ -604,11 +693,21 @@
|
||||
"is-callable": "^1.1.3"
|
||||
}
|
||||
},
|
||||
"node_modules/fs.realpath": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
|
||||
"integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=",
|
||||
"dev": true
|
||||
"node_modules/foreground-child": {
|
||||
"version": "3.3.0",
|
||||
"resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.0.tgz",
|
||||
"integrity": "sha512-Ld2g8rrAyMYFXBhEqMz8ZAHBi4J4uS1i/CxGMDnjyFWddMXLVcDp051DZfu+t7+ab7Wv6SMqpWmyFIj5UbfFvg==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"cross-spawn": "^7.0.0",
|
||||
"signal-exit": "^4.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/isaacs"
|
||||
}
|
||||
},
|
||||
"node_modules/function-bind": {
|
||||
"version": "1.1.2",
|
||||
@@ -640,20 +739,20 @@
|
||||
}
|
||||
},
|
||||
"node_modules/glob": {
|
||||
"version": "7.1.7",
|
||||
"resolved": "https://registry.npmjs.org/glob/-/glob-7.1.7.tgz",
|
||||
"integrity": "sha512-OvD9ENzPLbegENnYP5UUfJIirTg4+XwMWGaQfQTY0JenxNvvIKP3U3/tAQSPIu/lHxXYSZmpXlUHeqAIdKzBLQ==",
|
||||
"version": "10.4.5",
|
||||
"resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz",
|
||||
"integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"fs.realpath": "^1.0.0",
|
||||
"inflight": "^1.0.4",
|
||||
"inherits": "2",
|
||||
"minimatch": "^3.0.4",
|
||||
"once": "^1.3.0",
|
||||
"path-is-absolute": "^1.0.0"
|
||||
"foreground-child": "^3.1.0",
|
||||
"jackspeak": "^3.1.2",
|
||||
"minimatch": "^9.0.4",
|
||||
"minipass": "^7.1.2",
|
||||
"package-json-from-dist": "^1.0.0",
|
||||
"path-scurry": "^1.11.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": "*"
|
||||
"bin": {
|
||||
"glob": "dist/esm/bin.mjs"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/isaacs"
|
||||
@@ -796,16 +895,6 @@
|
||||
],
|
||||
"inBundle": true
|
||||
},
|
||||
"node_modules/inflight": {
|
||||
"version": "1.0.6",
|
||||
"resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
|
||||
"integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"once": "^1.3.0",
|
||||
"wrappy": "1"
|
||||
}
|
||||
},
|
||||
"node_modules/inherits": {
|
||||
"version": "2.0.4",
|
||||
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
|
||||
@@ -840,6 +929,15 @@
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/is-fullwidth-code-point": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
|
||||
"integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/is-generator-function": {
|
||||
"version": "1.0.10",
|
||||
"resolved": "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.0.10.tgz",
|
||||
@@ -892,6 +990,33 @@
|
||||
"integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==",
|
||||
"inBundle": true
|
||||
},
|
||||
"node_modules/isexe": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
|
||||
"integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/jackspeak": {
|
||||
"version": "3.4.3",
|
||||
"resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz",
|
||||
"integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@isaacs/cliui": "^8.0.2"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/isaacs"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@pkgjs/parseargs": "^0.11.0"
|
||||
}
|
||||
},
|
||||
"node_modules/lru-cache": {
|
||||
"version": "10.4.3",
|
||||
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz",
|
||||
"integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/md5.js": {
|
||||
"version": "1.3.5",
|
||||
"resolved": "https://registry.npmjs.org/md5.js/-/md5.js-1.3.5.tgz",
|
||||
@@ -935,15 +1060,27 @@
|
||||
"inBundle": true
|
||||
},
|
||||
"node_modules/minimatch": {
|
||||
"version": "3.1.2",
|
||||
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
|
||||
"integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
|
||||
"version": "9.0.5",
|
||||
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz",
|
||||
"integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"brace-expansion": "^1.1.7"
|
||||
"brace-expansion": "^2.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": "*"
|
||||
"node": ">=16 || 14 >=14.17"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/isaacs"
|
||||
}
|
||||
},
|
||||
"node_modules/minipass": {
|
||||
"version": "7.1.2",
|
||||
"resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz",
|
||||
"integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=16 || 14 >=14.17"
|
||||
}
|
||||
},
|
||||
"node_modules/object-inspect": {
|
||||
@@ -1002,21 +1139,18 @@
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/once": {
|
||||
"version": "1.4.0",
|
||||
"resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
|
||||
"integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"wrappy": "1"
|
||||
}
|
||||
},
|
||||
"node_modules/os-browserify": {
|
||||
"version": "0.3.0",
|
||||
"resolved": "https://registry.npmjs.org/os-browserify/-/os-browserify-0.3.0.tgz",
|
||||
"integrity": "sha1-hUNzx/XCMVkU/Jv8a9gjj92h7Cc=",
|
||||
"inBundle": true
|
||||
},
|
||||
"node_modules/package-json-from-dist": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz",
|
||||
"integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/pako": {
|
||||
"version": "1.0.11",
|
||||
"resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz",
|
||||
@@ -1059,13 +1193,29 @@
|
||||
"integrity": "sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==",
|
||||
"inBundle": true
|
||||
},
|
||||
"node_modules/path-is-absolute": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
|
||||
"integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=",
|
||||
"node_modules/path-key": {
|
||||
"version": "3.1.1",
|
||||
"resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz",
|
||||
"integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/path-scurry": {
|
||||
"version": "1.11.1",
|
||||
"resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz",
|
||||
"integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"lru-cache": "^10.2.0",
|
||||
"minipass": "^5.0.0 || ^6.0.2 || ^7.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=16 || 14 >=14.18"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/isaacs"
|
||||
}
|
||||
},
|
||||
"node_modules/pbkdf2": {
|
||||
@@ -1184,15 +1334,18 @@
|
||||
}
|
||||
},
|
||||
"node_modules/rimraf": {
|
||||
"version": "2.7.1",
|
||||
"resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz",
|
||||
"integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==",
|
||||
"version": "5.0.10",
|
||||
"resolved": "https://registry.npmjs.org/rimraf/-/rimraf-5.0.10.tgz",
|
||||
"integrity": "sha512-l0OE8wL34P4nJH/H2ffoaniAokM2qSmrtXHmlpvYr5AVVX8msAyW0l8NVJFDxlSK4u3Uh/f41cQheDVdnYijwQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"glob": "^7.1.3"
|
||||
"glob": "^10.3.7"
|
||||
},
|
||||
"bin": {
|
||||
"rimraf": "bin.js"
|
||||
"rimraf": "dist/esm/bin.mjs"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/isaacs"
|
||||
}
|
||||
},
|
||||
"node_modules/ripemd160": {
|
||||
@@ -1262,6 +1415,27 @@
|
||||
"sha.js": "bin.js"
|
||||
}
|
||||
},
|
||||
"node_modules/shebang-command": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
|
||||
"integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"shebang-regex": "^3.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/shebang-regex": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz",
|
||||
"integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/side-channel": {
|
||||
"version": "1.0.6",
|
||||
"resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.6.tgz",
|
||||
@@ -1281,6 +1455,18 @@
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/signal-exit": {
|
||||
"version": "4.1.0",
|
||||
"resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz",
|
||||
"integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=14"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/isaacs"
|
||||
}
|
||||
},
|
||||
"node_modules/stream-browserify": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/stream-browserify/-/stream-browserify-3.0.0.tgz",
|
||||
@@ -1312,6 +1498,102 @@
|
||||
"safe-buffer": "~5.2.0"
|
||||
}
|
||||
},
|
||||
"node_modules/string-width": {
|
||||
"version": "5.1.2",
|
||||
"resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz",
|
||||
"integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"eastasianwidth": "^0.2.0",
|
||||
"emoji-regex": "^9.2.2",
|
||||
"strip-ansi": "^7.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/string-width-cjs": {
|
||||
"name": "string-width",
|
||||
"version": "4.2.3",
|
||||
"resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
|
||||
"integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"emoji-regex": "^8.0.0",
|
||||
"is-fullwidth-code-point": "^3.0.0",
|
||||
"strip-ansi": "^6.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/string-width-cjs/node_modules/ansi-regex": {
|
||||
"version": "5.0.1",
|
||||
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
|
||||
"integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/string-width-cjs/node_modules/emoji-regex": {
|
||||
"version": "8.0.0",
|
||||
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
|
||||
"integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/string-width-cjs/node_modules/strip-ansi": {
|
||||
"version": "6.0.1",
|
||||
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
|
||||
"integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"ansi-regex": "^5.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/strip-ansi": {
|
||||
"version": "7.1.0",
|
||||
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz",
|
||||
"integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"ansi-regex": "^6.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/chalk/strip-ansi?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/strip-ansi-cjs": {
|
||||
"name": "strip-ansi",
|
||||
"version": "6.0.1",
|
||||
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
|
||||
"integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"ansi-regex": "^5.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/strip-ansi-cjs/node_modules/ansi-regex": {
|
||||
"version": "5.0.1",
|
||||
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
|
||||
"integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/timers-browserify": {
|
||||
"version": "2.0.12",
|
||||
"resolved": "https://registry.npmjs.org/timers-browserify/-/timers-browserify-2.0.12.tgz",
|
||||
@@ -1369,6 +1651,21 @@
|
||||
"integrity": "sha512-2ham8XPWTONajOR0ohOKOHXkm3+gaBmGut3SRuu75xLd/RRaY6vqgh8NBYYk7+RW3u5AtzPQZG8F10LHkl0lAQ==",
|
||||
"inBundle": true
|
||||
},
|
||||
"node_modules/which": {
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
|
||||
"integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"isexe": "^2.0.0"
|
||||
},
|
||||
"bin": {
|
||||
"node-which": "bin/node-which"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 8"
|
||||
}
|
||||
},
|
||||
"node_modules/which-typed-array": {
|
||||
"version": "1.1.13",
|
||||
"resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.13.tgz",
|
||||
@@ -1388,12 +1685,97 @@
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/wrappy": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
|
||||
"integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=",
|
||||
"node_modules/wrap-ansi": {
|
||||
"version": "8.1.0",
|
||||
"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz",
|
||||
"integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"ansi-styles": "^6.1.0",
|
||||
"string-width": "^5.0.1",
|
||||
"strip-ansi": "^7.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/chalk/wrap-ansi?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/wrap-ansi-cjs": {
|
||||
"name": "wrap-ansi",
|
||||
"version": "7.0.0",
|
||||
"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz",
|
||||
"integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"ansi-styles": "^4.0.0",
|
||||
"string-width": "^4.1.0",
|
||||
"strip-ansi": "^6.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/chalk/wrap-ansi?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/wrap-ansi-cjs/node_modules/ansi-regex": {
|
||||
"version": "5.0.1",
|
||||
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
|
||||
"integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/wrap-ansi-cjs/node_modules/ansi-styles": {
|
||||
"version": "4.3.0",
|
||||
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
|
||||
"integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"color-convert": "^2.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/chalk/ansi-styles?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/wrap-ansi-cjs/node_modules/emoji-regex": {
|
||||
"version": "8.0.0",
|
||||
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
|
||||
"integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/wrap-ansi-cjs/node_modules/string-width": {
|
||||
"version": "4.2.3",
|
||||
"resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
|
||||
"integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"emoji-regex": "^8.0.0",
|
||||
"is-fullwidth-code-point": "^3.0.0",
|
||||
"strip-ansi": "^6.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/wrap-ansi-cjs/node_modules/strip-ansi": {
|
||||
"version": "6.0.1",
|
||||
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
|
||||
"integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"ansi-regex": "^5.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/xtend": {
|
||||
"version": "4.0.2",
|
||||
"resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz",
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"name": "meteor-node-stubs",
|
||||
"author": "Ben Newman <ben@meteor.com>",
|
||||
"description": "Stub implementations of Node built-in modules, a la Browserify",
|
||||
"version": "1.2.10",
|
||||
"version": "1.2.13",
|
||||
"main": "index.js",
|
||||
"license": "MIT",
|
||||
"homepage": "https://github.com/meteor/meteor/blob/devel/npm-packages/meteor-node-stubs/README.md",
|
||||
@@ -18,7 +18,7 @@
|
||||
"console-browserify": "^1.2.0",
|
||||
"constants-browserify": "^1.0.0",
|
||||
"domain-browser": "^4.23.0",
|
||||
"elliptic": "^6.5.7",
|
||||
"elliptic": "^6.6.1",
|
||||
"events": "^3.3.0",
|
||||
"https-browserify": "^1.0.0",
|
||||
"os-browserify": "^0.3.0",
|
||||
@@ -62,7 +62,7 @@
|
||||
"vm-browserify"
|
||||
],
|
||||
"devDependencies": {
|
||||
"rimraf": "^2.7.1"
|
||||
"rimraf": "^5.0.10"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
||||
@@ -2,6 +2,7 @@ var fs = require("fs");
|
||||
var path = require("path");
|
||||
var depsDir = path.join(__dirname, "..", "deps");
|
||||
var map = require("../map.json");
|
||||
var rr = require("rimraf");
|
||||
|
||||
// Each file in the `deps` directory expresses the dependencies of a stub.
|
||||
// For example, `deps/http.js` calls `require("http-browserify")` to
|
||||
@@ -14,16 +15,15 @@ var map = require("../map.json");
|
||||
// bundled. Note that these modules should not be `require`d at runtime,
|
||||
// but merely scanned at bundling time.
|
||||
|
||||
fs.mkdir(depsDir, function () {
|
||||
require("rimraf")("deps/*.js", function (error) {
|
||||
if (error) throw error;
|
||||
Object.keys(map).forEach(function (id) {
|
||||
fs.writeFileSync(
|
||||
path.join(depsDir, id + ".js"),
|
||||
typeof map[id] === "string"
|
||||
? "require(" + JSON.stringify(map[id]) + ");\n"
|
||||
: ""
|
||||
);
|
||||
});
|
||||
});
|
||||
rr.rimrafSync(depsDir);
|
||||
|
||||
fs.mkdirSync(depsDir);
|
||||
|
||||
Object.keys(map).forEach(function (id) {
|
||||
fs.writeFileSync(
|
||||
path.join(depsDir, id + ".js"),
|
||||
typeof map[id] === "string"
|
||||
? "require(" + JSON.stringify(map[id]) + ");\n"
|
||||
: ""
|
||||
);
|
||||
});
|
||||
|
||||
130
package-lock.json
generated
130
package-lock.json
generated
@@ -13,7 +13,10 @@
|
||||
"@babel/eslint-parser": "^7.21.3",
|
||||
"@babel/eslint-plugin": "^7.19.1",
|
||||
"@babel/preset-react": "^7.18.6",
|
||||
"@types/lodash.isempty": "^4.4.9",
|
||||
"@types/node": "^18.16.18",
|
||||
"@types/sockjs": "^0.3.36",
|
||||
"@types/sockjs-client": "^1.5.4",
|
||||
"@typescript-eslint/eslint-plugin": "^5.56.0",
|
||||
"@typescript-eslint/parser": "^5.56.0",
|
||||
"eslint": "^8.36.0",
|
||||
@@ -25,7 +28,7 @@
|
||||
"eslint-plugin-prettier": "^4.2.1",
|
||||
"eslint-plugin-react": "^7.32.2",
|
||||
"eslint-plugin-react-hooks": "^4.6.0",
|
||||
"prettier": "^2.8.6",
|
||||
"prettier": "^2.8.8",
|
||||
"typescript": "^5.4.5"
|
||||
}
|
||||
},
|
||||
@@ -1096,6 +1099,21 @@
|
||||
"integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@types/lodash": {
|
||||
"version": "4.17.10",
|
||||
"resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.17.10.tgz",
|
||||
"integrity": "sha512-YpS0zzoduEhuOWjAotS6A5AVCva7X4lVlYLF0FYHAY9sdraBfnatttHItlWeZdGhuEkf+OzMNg2ZYAx8t+52uQ==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@types/lodash.isempty": {
|
||||
"version": "4.4.9",
|
||||
"resolved": "https://registry.npmjs.org/@types/lodash.isempty/-/lodash.isempty-4.4.9.tgz",
|
||||
"integrity": "sha512-DPSFfnT2JmZiAWNWOU8IRZws/Ha6zyGF5m06TydfsY+0dVoQqby2J61Na2QU4YtwiZ+moC6cJS6zWYBJq4wBVw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@types/lodash": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/node": {
|
||||
"version": "18.19.3",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.3.tgz",
|
||||
@@ -1111,6 +1129,21 @@
|
||||
"integrity": "sha512-dn1l8LaMea/IjDoHNd9J52uBbInB796CDffS6VdIxvqYCPSG0V0DzHp76GpaWnlhg88uYyPbXCDIowa86ybd5A==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@types/sockjs": {
|
||||
"version": "0.3.36",
|
||||
"resolved": "https://registry.npmjs.org/@types/sockjs/-/sockjs-0.3.36.tgz",
|
||||
"integrity": "sha512-MK9V6NzAS1+Ud7JV9lJLFqW85VbC9dq3LmwZCuBe4wBDgKC0Kj/jd8Xl+nSviU+Qc3+m7umHHyHg//2KSa0a0Q==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@types/node": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/sockjs-client": {
|
||||
"version": "1.5.4",
|
||||
"resolved": "https://registry.npmjs.org/@types/sockjs-client/-/sockjs-client-1.5.4.tgz",
|
||||
"integrity": "sha512-zk+uFZeWyvJ5ZFkLIwoGA/DfJ+pYzcZ8eH4H/EILCm2OBZyHH6Hkdna1/UWL/CFruh5wj6ES7g75SvUB0VsH5w==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@typescript-eslint/eslint-plugin": {
|
||||
"version": "5.62.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.62.0.tgz",
|
||||
@@ -1817,6 +1850,19 @@
|
||||
"concat-map": "0.0.1"
|
||||
}
|
||||
},
|
||||
"node_modules/braces": {
|
||||
"version": "3.0.3",
|
||||
"resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz",
|
||||
"integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"fill-range": "^7.1.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/browserslist": {
|
||||
"version": "4.22.2",
|
||||
"resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.22.2.tgz",
|
||||
@@ -2992,39 +3038,6 @@
|
||||
"node": ">=8.6.0"
|
||||
}
|
||||
},
|
||||
"node_modules/fast-glob/node_modules/braces": {
|
||||
"version": "3.0.2",
|
||||
"resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz",
|
||||
"integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"fill-range": "^7.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/fast-glob/node_modules/fill-range": {
|
||||
"version": "7.0.1",
|
||||
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz",
|
||||
"integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"to-regex-range": "^5.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/fast-glob/node_modules/is-number": {
|
||||
"version": "7.0.0",
|
||||
"resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz",
|
||||
"integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=0.12.0"
|
||||
}
|
||||
},
|
||||
"node_modules/fast-glob/node_modules/micromatch": {
|
||||
"version": "4.0.5",
|
||||
"resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz",
|
||||
@@ -3038,18 +3051,6 @@
|
||||
"node": ">=8.6"
|
||||
}
|
||||
},
|
||||
"node_modules/fast-glob/node_modules/to-regex-range": {
|
||||
"version": "5.0.1",
|
||||
"resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
|
||||
"integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"is-number": "^7.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8.0"
|
||||
}
|
||||
},
|
||||
"node_modules/fast-json-stable-stringify": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz",
|
||||
@@ -3083,6 +3084,19 @@
|
||||
"node": "^10.12.0 || >=12.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/fill-range": {
|
||||
"version": "7.1.1",
|
||||
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz",
|
||||
"integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"to-regex-range": "^5.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/flat-cache": {
|
||||
"version": "3.2.0",
|
||||
"resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.2.0.tgz",
|
||||
@@ -3626,6 +3640,16 @@
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/is-number": {
|
||||
"version": "7.0.0",
|
||||
"resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz",
|
||||
"integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=0.12.0"
|
||||
}
|
||||
},
|
||||
"node_modules/is-number-object": {
|
||||
"version": "1.0.7",
|
||||
"resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.0.7.tgz",
|
||||
@@ -4236,6 +4260,7 @@
|
||||
"resolved": "https://registry.npmjs.org/prettier/-/prettier-2.8.8.tgz",
|
||||
"integrity": "sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"bin": {
|
||||
"prettier": "bin-prettier.js"
|
||||
},
|
||||
@@ -4695,6 +4720,19 @@
|
||||
"node": ">=4"
|
||||
}
|
||||
},
|
||||
"node_modules/to-regex-range": {
|
||||
"version": "5.0.1",
|
||||
"resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
|
||||
"integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"is-number": "^7.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8.0"
|
||||
}
|
||||
},
|
||||
"node_modules/tsconfig-paths": {
|
||||
"version": "3.15.0",
|
||||
"resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.15.0.tgz",
|
||||
|
||||
@@ -17,7 +17,10 @@
|
||||
"@babel/eslint-parser": "^7.21.3",
|
||||
"@babel/eslint-plugin": "^7.19.1",
|
||||
"@babel/preset-react": "^7.18.6",
|
||||
"@types/lodash.isempty": "^4.4.9",
|
||||
"@types/node": "^18.16.18",
|
||||
"@types/sockjs": "^0.3.36",
|
||||
"@types/sockjs-client": "^1.5.4",
|
||||
"@typescript-eslint/eslint-plugin": "^5.56.0",
|
||||
"@typescript-eslint/parser": "^5.56.0",
|
||||
"eslint": "^8.36.0",
|
||||
@@ -29,7 +32,7 @@
|
||||
"eslint-plugin-prettier": "^4.2.1",
|
||||
"eslint-plugin-react": "^7.32.2",
|
||||
"eslint-plugin-react-hooks": "^4.6.0",
|
||||
"prettier": "^2.8.6",
|
||||
"prettier": "^2.8.8",
|
||||
"typescript": "^5.4.5"
|
||||
},
|
||||
"jshintConfig": {
|
||||
|
||||
@@ -1,45 +0,0 @@
|
||||
{
|
||||
"lockfileVersion": 4,
|
||||
"dependencies": {
|
||||
"@types/node": {
|
||||
"version": "22.7.5",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-22.7.5.tgz",
|
||||
"integrity": "sha512-jML7s2NAzMWc//QSJ1a3prpk78cOPchGvXJsC3C6R6PSMoooztvRVQEz89gmBTBY1SPMaqo5teB4uNHPdetShQ=="
|
||||
},
|
||||
"@types/notp": {
|
||||
"version": "2.0.5",
|
||||
"resolved": "https://registry.npmjs.org/@types/notp/-/notp-2.0.5.tgz",
|
||||
"integrity": "sha512-ZsZS0PYUa6ZE4K3yOGerBvaxCp4ePf6ZmkFbPeilcqz2Ui/lmXox7KlRt7XZkXzqUgXhFLkc09ixyVmFLCU3gQ=="
|
||||
},
|
||||
"node-2fa": {
|
||||
"version": "2.0.3",
|
||||
"resolved": "https://registry.npmjs.org/node-2fa/-/node-2fa-2.0.3.tgz",
|
||||
"integrity": "sha512-PQldrOhjuoZyoydMvMSctllPN1ZPZ1/NwkEcgYwY9faVqE/OymxR+3awPpbWZxm6acLKqvmNqQmdqTsqYyflFw=="
|
||||
},
|
||||
"notp": {
|
||||
"version": "2.0.3",
|
||||
"resolved": "https://registry.npmjs.org/notp/-/notp-2.0.3.tgz",
|
||||
"integrity": "sha512-oBig/2uqkjQ5AkBuw4QJYwkEWa/q+zHxI5/I5z6IeP2NT0alpJFsP/trrfCC+9xOAgQSZXssNi962kp5KBmypQ=="
|
||||
},
|
||||
"qrcode-svg": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/qrcode-svg/-/qrcode-svg-1.1.0.tgz",
|
||||
"integrity": "sha512-XyQCIXux1zEIA3NPb0AeR8UMYvXZzWEhgdBgBjH9gO7M48H9uoHzviNz8pXw3UzrAcxRRRn9gxHewAVK7bn9qw=="
|
||||
},
|
||||
"thirty-two": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/thirty-two/-/thirty-two-1.0.2.tgz",
|
||||
"integrity": "sha512-OEI0IWCe+Dw46019YLl6V10Us5bi574EvlJEOcAkB29IzQ/mYD1A6RyNHLjZPiHCmuodxvgF6U+vZO1L15lxVA=="
|
||||
},
|
||||
"tslib": {
|
||||
"version": "2.7.0",
|
||||
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz",
|
||||
"integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA=="
|
||||
},
|
||||
"undici-types": {
|
||||
"version": "6.19.8",
|
||||
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz",
|
||||
"integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw=="
|
||||
}
|
||||
}
|
||||
}
|
||||
11
packages/accounts-base/accounts-base.d.ts
vendored
11
packages/accounts-base/accounts-base.d.ts
vendored
@@ -82,6 +82,11 @@ export namespace Accounts {
|
||||
passwordEnrollTokenExpirationInDays?: number | undefined;
|
||||
ambiguousErrorMessages?: boolean | undefined;
|
||||
bcryptRounds?: number | undefined;
|
||||
argon2Enabled?: string | false;
|
||||
argon2Type?: string | undefined;
|
||||
argon2TimeCost: number | undefined;
|
||||
argon2MemoryCost: number | undefined;
|
||||
argon2Parallelism: number | undefined;
|
||||
defaultFieldSelector?: { [key: string]: 0 | 1 } | undefined;
|
||||
collection?: string | undefined;
|
||||
loginTokenExpirationHours?: number | undefined;
|
||||
@@ -353,10 +358,10 @@ export namespace Accounts {
|
||||
|
||||
/**
|
||||
*
|
||||
* Check whether the provided password matches the bcrypt'ed password in
|
||||
* Check whether the provided password matches the encrypted 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
|
||||
* it will be run through SHA256 before bcrypt or argon2) or an object with
|
||||
* properties `digest` and `algorithm` (in which case we bcrypt/argon2
|
||||
* `password.digest`).
|
||||
*/
|
||||
function _checkPasswordAsync(
|
||||
|
||||
@@ -14,6 +14,11 @@ const VALID_CONFIG_KEYS = [
|
||||
'passwordEnrollTokenExpiration',
|
||||
'ambiguousErrorMessages',
|
||||
'bcryptRounds',
|
||||
'argon2Enabled',
|
||||
'argon2Type',
|
||||
'argon2TimeCost',
|
||||
'argon2MemoryCost',
|
||||
'argon2Parallelism',
|
||||
'defaultFieldSelector',
|
||||
'collection',
|
||||
'loginTokenExpirationHours',
|
||||
@@ -194,41 +199,6 @@ export class AccountsCommon {
|
||||
? this.users.findOneAsync(userId, this._addDefaultFieldSelector(options))
|
||||
: null;
|
||||
}
|
||||
// Set up config for the accounts system. Call this on both the client
|
||||
// and the server.
|
||||
//
|
||||
// Note that this method gets overridden on AccountsServer.prototype, but
|
||||
// the overriding method calls the overridden method.
|
||||
//
|
||||
// XXX we should add some enforcement that this is called on both the
|
||||
// client and the server. Otherwise, a user can
|
||||
// 'forbidClientAccountCreation' only on the client and while it looks
|
||||
// like their app is secure, the server will still accept createUser
|
||||
// calls. https://github.com/meteor/meteor/issues/828
|
||||
//
|
||||
// @param options {Object} an object with fields:
|
||||
// - sendVerificationEmail {Boolean}
|
||||
// Send email address verification emails to new users created from
|
||||
// client signups.
|
||||
// - forbidClientAccountCreation {Boolean}
|
||||
// Do not allow clients to create accounts directly.
|
||||
// - restrictCreationByEmailDomain {Function or String}
|
||||
// Require created users to have an email matching the function or
|
||||
// having the string as domain.
|
||||
// - loginExpirationInDays {Number}
|
||||
// Number of days since login until a user is logged out (login token
|
||||
// expires).
|
||||
// - collection {String|Mongo.Collection}
|
||||
// A collection name or a Mongo.Collection object to hold the users.
|
||||
// - passwordResetTokenExpirationInDays {Number}
|
||||
// Number of days since password reset token creation until the
|
||||
// token can't be used any longer (password reset token expires).
|
||||
// - ambiguousErrorMessages {Boolean}
|
||||
// Return ambiguous error messages from login failures to prevent
|
||||
// user enumeration.
|
||||
// - bcryptRounds {Number}
|
||||
// Allows override of number of bcrypt rounds (aka work factor) used
|
||||
// to store passwords.
|
||||
|
||||
/**
|
||||
* @summary Set global accounts options. You can also set these in `Meteor.settings.packages.accounts` without the need to call this function.
|
||||
@@ -244,8 +214,13 @@ export class AccountsCommon {
|
||||
* @param {Number} options.passwordResetTokenExpiration The number of milliseconds from when a link to reset password is sent until token expires and user can't reset password with the link anymore. If `passwordResetTokenExpirationInDays` is set, it takes precedent.
|
||||
* @param {Number} options.passwordEnrollTokenExpirationInDays The number of days from when a link to set initial password is sent until token expires and user can't set password with the link anymore. Defaults to 30.
|
||||
* @param {Number} options.passwordEnrollTokenExpiration The number of milliseconds from when a link to set initial password is sent until token expires and user can't set password with the link anymore. If `passwordEnrollTokenExpirationInDays` is set, it takes precedent.
|
||||
* @param {Boolean} options.ambiguousErrorMessages Return ambiguous error messages from login failures to prevent user enumeration. Defaults to `false`, but in production environments it is recommended it defaults to `true`.
|
||||
* @param {Boolean} options.ambiguousErrorMessages Return ambiguous error messages from login failures to prevent user enumeration. Defaults to `true`.
|
||||
* @param {Number} options.bcryptRounds Allows override of number of bcrypt rounds (aka work factor) used to store passwords. The default is 10.
|
||||
* @param {Boolean} options.argon2Enabled Enable argon2 algorithm usage in replacement for bcrypt. The default is `false`.
|
||||
* @param {'argon2id' | 'argon2i' | 'argon2d'} options.argon2Type Allows override of the argon2 algorithm type. The default is `argon2id`.
|
||||
* @param {Number} options.argon2TimeCost Allows override of number of argon2 iterations (aka time cost) used to store passwords. The default is 2.
|
||||
* @param {Number} options.argon2MemoryCost Allows override of the amount of memory (in KiB) used by the argon2 algorithm. The default is 19456 (19MB).
|
||||
* @param {Number} options.argon2Parallelism Allows override of the number of threads used by the argon2 algorithm. The default is 1.
|
||||
* @param {MongoFieldSpecifier} options.defaultFieldSelector To exclude by default large custom fields from `Meteor.user()` and `Meteor.findUserBy...()` functions when called without a field selector, and all `onLogin`, `onLoginFailure` and `onLogout` callbacks. Example: `Accounts.config({ defaultFieldSelector: { myBigArray: 0 }})`. Beware when using this. If, for instance, you do not include `email` when excluding the fields, you can have problems with functions like `forgotPassword` that will break because they won't have the required data available. It's recommend that you always keep the fields `_id`, `username`, and `email`.
|
||||
* @param {String|Mongo.Collection} options.collection A collection name or a Mongo.Collection object to hold the users.
|
||||
* @param {Number} options.loginTokenExpirationHours When using the package `accounts-2fa`, use this to set the amount of time a token sent is valid. As it's just a number, you can use, for example, 0.5 to make the token valid for just half hour. The default is 1 hour.
|
||||
|
||||
@@ -1806,21 +1806,6 @@ const setupUsersCollection = async users => {
|
||||
|
||||
return true;
|
||||
},
|
||||
updateAsync: (userId, user, fields, modifier) => {
|
||||
// make sure it is our record
|
||||
if (user._id !== userId) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// user can only modify the 'profile' field. sets to multiple
|
||||
// sub-keys (eg profile.foo and profile.bar) are merged into entry
|
||||
// in the fields list.
|
||||
if (fields.length !== 1 || fields[0] !== 'profile') {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
},
|
||||
fetch: ['_id'] // we only look at _id.
|
||||
});
|
||||
|
||||
@@ -1861,4 +1846,3 @@ const generateCasePermutationsForString = string => {
|
||||
}
|
||||
return permutations;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
Package.describe({
|
||||
summary: "A user account system",
|
||||
version: "3.0.3",
|
||||
version: "3.1.0",
|
||||
});
|
||||
|
||||
Package.onUse((api) => {
|
||||
|
||||
@@ -74,34 +74,38 @@ Meteor.startup(() => {
|
||||
Accounts.oauth.tryLoginAfterPopupClosed = (
|
||||
credentialToken,
|
||||
callback,
|
||||
shouldRetry = true
|
||||
timeout = 1000
|
||||
) => {
|
||||
const credentialSecret =
|
||||
OAuth._retrieveCredentialSecret(credentialToken);
|
||||
let startTime = Date.now();
|
||||
let calledOnce = false;
|
||||
let intervalId;
|
||||
const checkForCredentialSecret = (clearInterval = false) => {
|
||||
const credentialSecret = OAuth._retrieveCredentialSecret(credentialToken);
|
||||
if (!calledOnce && (credentialSecret || clearInterval)) {
|
||||
calledOnce = true;
|
||||
Meteor.clearInterval(intervalId);
|
||||
Accounts.callLoginMethod({
|
||||
methodArguments: [{ oauth: { credentialToken, credentialSecret } }],
|
||||
userCallback: callback ? err => callback(convertError(err)) : () => {},
|
||||
});
|
||||
} else if (clearInterval) {
|
||||
Meteor.clearInterval(intervalId);
|
||||
}
|
||||
};
|
||||
|
||||
// Check immediately
|
||||
checkForCredentialSecret();
|
||||
|
||||
// Then check on an interval
|
||||
// In some case the function OAuth._retrieveCredentialSecret() can return null, because the local storage might not
|
||||
// be ready. So we retry after a timeout.
|
||||
|
||||
if (!credentialSecret) {
|
||||
if (!shouldRetry) {
|
||||
return;
|
||||
intervalId = Meteor.setInterval(() => {
|
||||
if (Date.now() - startTime > timeout) {
|
||||
checkForCredentialSecret(true);
|
||||
} else {
|
||||
checkForCredentialSecret();
|
||||
}
|
||||
Meteor.setTimeout(
|
||||
() =>
|
||||
Accounts.oauth.tryLoginAfterPopupClosed(
|
||||
credentialToken,
|
||||
callback,
|
||||
false
|
||||
),
|
||||
500
|
||||
);
|
||||
return;
|
||||
}
|
||||
// continue with the rest of the function
|
||||
Accounts.callLoginMethod({
|
||||
methodArguments: [{ oauth: { credentialToken, credentialSecret } }],
|
||||
userCallback: callback && (err => callback(convertError(err))),
|
||||
});
|
||||
}, 250);
|
||||
};
|
||||
|
||||
Accounts.oauth.credentialRequestCompleteHandler = callback =>
|
||||
@@ -112,4 +116,3 @@ Accounts.oauth.credentialRequestCompleteHandler = callback =>
|
||||
Accounts.oauth.tryLoginAfterPopupClosed(credentialTokenOrError, callback);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
Package.describe({
|
||||
summary: "Common code for OAuth-based login services",
|
||||
version: '1.4.5',
|
||||
version: '1.4.6',
|
||||
});
|
||||
|
||||
Package.onUse(api => {
|
||||
|
||||
@@ -1,306 +0,0 @@
|
||||
{
|
||||
"lockfileVersion": 4,
|
||||
"dependencies": {
|
||||
"@mapbox/node-pre-gyp": {
|
||||
"version": "1.0.11",
|
||||
"resolved": "https://registry.npmjs.org/@mapbox/node-pre-gyp/-/node-pre-gyp-1.0.11.tgz",
|
||||
"integrity": "sha512-Yhlar6v9WQgUp/He7BdgzOz8lqMQ8sU+jkCq7Wx8Myc5YFJLbEe7lgui/V7G1qB1DJykHSGwreceSaD60Y0PUQ=="
|
||||
},
|
||||
"abbrev": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz",
|
||||
"integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q=="
|
||||
},
|
||||
"agent-base": {
|
||||
"version": "6.0.2",
|
||||
"resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz",
|
||||
"integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ=="
|
||||
},
|
||||
"ansi-regex": {
|
||||
"version": "5.0.1",
|
||||
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
|
||||
"integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="
|
||||
},
|
||||
"aproba": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/aproba/-/aproba-2.0.0.tgz",
|
||||
"integrity": "sha512-lYe4Gx7QT+MKGbDsA+Z+he/Wtef0BiwDOlK/XkBrdfsh9J/jPPXbX0tE9x9cl27Tmu5gg3QUbUrQYa/y+KOHPQ=="
|
||||
},
|
||||
"are-we-there-yet": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-2.0.0.tgz",
|
||||
"integrity": "sha512-Ci/qENmwHnsYo9xKIcUJN5LeDKdJ6R1Z1j9V/J5wyq8nh/mYPEpIKJbBZXtZjG04HiK7zV/p6Vs9952MrMeUIw=="
|
||||
},
|
||||
"balanced-match": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
|
||||
"integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="
|
||||
},
|
||||
"bcrypt": {
|
||||
"version": "5.0.1",
|
||||
"resolved": "https://registry.npmjs.org/bcrypt/-/bcrypt-5.0.1.tgz",
|
||||
"integrity": "sha512-9BTgmrhZM2t1bNuDtrtIMVSmmxZBrJ71n8Wg+YgdjHuIWYF7SjjmCPZFB+/5i/o/PIeRpwVJR3P+NrpIItUjqw=="
|
||||
},
|
||||
"brace-expansion": {
|
||||
"version": "1.1.11",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
|
||||
"integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA=="
|
||||
},
|
||||
"chownr": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz",
|
||||
"integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ=="
|
||||
},
|
||||
"color-support": {
|
||||
"version": "1.1.3",
|
||||
"resolved": "https://registry.npmjs.org/color-support/-/color-support-1.1.3.tgz",
|
||||
"integrity": "sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg=="
|
||||
},
|
||||
"concat-map": {
|
||||
"version": "0.0.1",
|
||||
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
|
||||
"integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg=="
|
||||
},
|
||||
"console-control-strings": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz",
|
||||
"integrity": "sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ=="
|
||||
},
|
||||
"debug": {
|
||||
"version": "4.3.7",
|
||||
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz",
|
||||
"integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ=="
|
||||
},
|
||||
"delegates": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz",
|
||||
"integrity": "sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ=="
|
||||
},
|
||||
"detect-libc": {
|
||||
"version": "2.0.3",
|
||||
"resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.3.tgz",
|
||||
"integrity": "sha512-bwy0MGW55bG41VqxxypOsdSdGqLwXPI/focwgTYCFMbdUiBAxLg9CFzG08sz2aqzknwiX7Hkl0bQENjg8iLByw=="
|
||||
},
|
||||
"emoji-regex": {
|
||||
"version": "8.0.0",
|
||||
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
|
||||
"integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="
|
||||
},
|
||||
"fs-minipass": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz",
|
||||
"integrity": "sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==",
|
||||
"dependencies": {
|
||||
"minipass": {
|
||||
"version": "3.3.6",
|
||||
"resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz",
|
||||
"integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw=="
|
||||
}
|
||||
}
|
||||
},
|
||||
"fs.realpath": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
|
||||
"integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw=="
|
||||
},
|
||||
"gauge": {
|
||||
"version": "3.0.2",
|
||||
"resolved": "https://registry.npmjs.org/gauge/-/gauge-3.0.2.tgz",
|
||||
"integrity": "sha512-+5J6MS/5XksCuXq++uFRsnUd7Ovu1XenbeuIuNRJxYWjgQbPuFhT14lAvsWfqfAmnwluf1OwMjz39HjfLPci0Q=="
|
||||
},
|
||||
"glob": {
|
||||
"version": "7.2.3",
|
||||
"resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz",
|
||||
"integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q=="
|
||||
},
|
||||
"has-unicode": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz",
|
||||
"integrity": "sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ=="
|
||||
},
|
||||
"https-proxy-agent": {
|
||||
"version": "5.0.1",
|
||||
"resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz",
|
||||
"integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA=="
|
||||
},
|
||||
"inflight": {
|
||||
"version": "1.0.6",
|
||||
"resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
|
||||
"integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA=="
|
||||
},
|
||||
"inherits": {
|
||||
"version": "2.0.4",
|
||||
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
|
||||
"integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="
|
||||
},
|
||||
"is-fullwidth-code-point": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
|
||||
"integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg=="
|
||||
},
|
||||
"make-dir": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz",
|
||||
"integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==",
|
||||
"dependencies": {
|
||||
"semver": {
|
||||
"version": "6.3.1",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz",
|
||||
"integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="
|
||||
}
|
||||
}
|
||||
},
|
||||
"minimatch": {
|
||||
"version": "3.1.2",
|
||||
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
|
||||
"integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw=="
|
||||
},
|
||||
"minipass": {
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/minipass/-/minipass-5.0.0.tgz",
|
||||
"integrity": "sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ=="
|
||||
},
|
||||
"minizlib": {
|
||||
"version": "2.1.2",
|
||||
"resolved": "https://registry.npmjs.org/minizlib/-/minizlib-2.1.2.tgz",
|
||||
"integrity": "sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==",
|
||||
"dependencies": {
|
||||
"minipass": {
|
||||
"version": "3.3.6",
|
||||
"resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz",
|
||||
"integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw=="
|
||||
}
|
||||
}
|
||||
},
|
||||
"mkdirp": {
|
||||
"version": "1.0.4",
|
||||
"resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz",
|
||||
"integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw=="
|
||||
},
|
||||
"ms": {
|
||||
"version": "2.1.3",
|
||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
|
||||
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="
|
||||
},
|
||||
"node-addon-api": {
|
||||
"version": "3.2.1",
|
||||
"resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-3.2.1.tgz",
|
||||
"integrity": "sha512-mmcei9JghVNDYydghQmeDX8KoAm0FAiYyIcUt/N4nhyAipB17pllZQDOJD2fotxABnt4Mdz+dKTO7eftLg4d0A=="
|
||||
},
|
||||
"node-fetch": {
|
||||
"version": "2.7.0",
|
||||
"resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz",
|
||||
"integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A=="
|
||||
},
|
||||
"nopt": {
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/nopt/-/nopt-5.0.0.tgz",
|
||||
"integrity": "sha512-Tbj67rffqceeLpcRXrT7vKAN8CwfPeIBgM7E6iBkmKLV7bEMwpGgYLGv0jACUsECaa/vuxP0IjEont6umdMgtQ=="
|
||||
},
|
||||
"npmlog": {
|
||||
"version": "5.0.1",
|
||||
"resolved": "https://registry.npmjs.org/npmlog/-/npmlog-5.0.1.tgz",
|
||||
"integrity": "sha512-AqZtDUWOMKs1G/8lwylVjrdYgqA4d9nu8hc+0gzRxlDb1I10+FHBGMXs6aiQHFdCUUlqH99MUMuLfzWDNDtfxw=="
|
||||
},
|
||||
"object-assign": {
|
||||
"version": "4.1.1",
|
||||
"resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
|
||||
"integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg=="
|
||||
},
|
||||
"once": {
|
||||
"version": "1.4.0",
|
||||
"resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
|
||||
"integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w=="
|
||||
},
|
||||
"path-is-absolute": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
|
||||
"integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg=="
|
||||
},
|
||||
"readable-stream": {
|
||||
"version": "3.6.2",
|
||||
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz",
|
||||
"integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA=="
|
||||
},
|
||||
"rimraf": {
|
||||
"version": "3.0.2",
|
||||
"resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz",
|
||||
"integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA=="
|
||||
},
|
||||
"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=="
|
||||
},
|
||||
"semver": {
|
||||
"version": "7.6.3",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz",
|
||||
"integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A=="
|
||||
},
|
||||
"set-blocking": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz",
|
||||
"integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw=="
|
||||
},
|
||||
"signal-exit": {
|
||||
"version": "3.0.7",
|
||||
"resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz",
|
||||
"integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ=="
|
||||
},
|
||||
"string_decoder": {
|
||||
"version": "1.3.0",
|
||||
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz",
|
||||
"integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA=="
|
||||
},
|
||||
"string-width": {
|
||||
"version": "4.2.3",
|
||||
"resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
|
||||
"integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="
|
||||
},
|
||||
"strip-ansi": {
|
||||
"version": "6.0.1",
|
||||
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
|
||||
"integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="
|
||||
},
|
||||
"tar": {
|
||||
"version": "6.2.1",
|
||||
"resolved": "https://registry.npmjs.org/tar/-/tar-6.2.1.tgz",
|
||||
"integrity": "sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A=="
|
||||
},
|
||||
"tr46": {
|
||||
"version": "0.0.3",
|
||||
"resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz",
|
||||
"integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw=="
|
||||
},
|
||||
"util-deprecate": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
|
||||
"integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw=="
|
||||
},
|
||||
"webidl-conversions": {
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz",
|
||||
"integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ=="
|
||||
},
|
||||
"whatwg-url": {
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz",
|
||||
"integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw=="
|
||||
},
|
||||
"wide-align": {
|
||||
"version": "1.1.5",
|
||||
"resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.5.tgz",
|
||||
"integrity": "sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg=="
|
||||
},
|
||||
"wrappy": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
|
||||
"integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ=="
|
||||
},
|
||||
"yallist": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
|
||||
"integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A=="
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,49 +1,51 @@
|
||||
Package.describe({
|
||||
summary: "Password support for accounts",
|
||||
// Note: 2.2.0-beta.3 was published during the Meteor 1.6 prerelease
|
||||
// process, so it might be best to skip to 2.3.x instead of reusing
|
||||
// 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: "3.0.2",
|
||||
});
|
||||
|
||||
Npm.depends({
|
||||
bcrypt: "5.0.1",
|
||||
});
|
||||
|
||||
Package.onUse((api) => {
|
||||
api.use(["accounts-base", "sha", "ejson", "ddp"], ["client", "server"]);
|
||||
|
||||
// Export Accounts (etc) to packages using this one.
|
||||
api.imply("accounts-base", ["client", "server"]);
|
||||
|
||||
api.use("email", "server");
|
||||
api.use("random", "server");
|
||||
api.use("check", "server");
|
||||
api.use("ecmascript");
|
||||
|
||||
api.addFiles("email_templates.js", "server");
|
||||
api.addFiles("password_server.js", "server");
|
||||
api.addFiles("password_client.js", "client");
|
||||
});
|
||||
|
||||
Package.onTest((api) => {
|
||||
api.use([
|
||||
"accounts-password",
|
||||
"sha",
|
||||
"tinytest",
|
||||
"test-helpers",
|
||||
"tracker",
|
||||
"accounts-base",
|
||||
"random",
|
||||
"email",
|
||||
"check",
|
||||
"ddp",
|
||||
"ecmascript",
|
||||
]);
|
||||
api.addFiles("password_tests_setup.js", "server");
|
||||
api.addFiles("password_tests.js", ["client", "server"]);
|
||||
api.addFiles("email_tests_setup.js", "server");
|
||||
api.addFiles("email_tests.js", "client");
|
||||
});
|
||||
Package.describe({
|
||||
summary: "Password support for accounts",
|
||||
// Note: 2.2.0-beta.3 was published during the Meteor 1.6 prerelease
|
||||
// process, so it might be best to skip to 2.3.x instead of reusing
|
||||
// 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: "3.1.0",
|
||||
});
|
||||
|
||||
Npm.depends({
|
||||
bcrypt: "5.0.1",
|
||||
argon2: "0.41.1",
|
||||
});
|
||||
|
||||
Package.onUse((api) => {
|
||||
api.use(["accounts-base", "sha", "ejson", "ddp"], ["client", "server"]);
|
||||
|
||||
// Export Accounts (etc) to packages using this one.
|
||||
api.imply("accounts-base", ["client", "server"]);
|
||||
|
||||
api.use("email", "server");
|
||||
api.use("random", "server");
|
||||
api.use("check", "server");
|
||||
api.use("ecmascript");
|
||||
|
||||
api.addFiles("email_templates.js", "server");
|
||||
api.addFiles("password_server.js", "server");
|
||||
api.addFiles("password_client.js", "client");
|
||||
});
|
||||
|
||||
Package.onTest((api) => {
|
||||
api.use([
|
||||
"accounts-password",
|
||||
"sha",
|
||||
"tinytest",
|
||||
"test-helpers",
|
||||
"tracker",
|
||||
"accounts-base",
|
||||
"random",
|
||||
"email",
|
||||
"check",
|
||||
"ddp",
|
||||
"ecmascript"
|
||||
]);
|
||||
api.addFiles("password_tests_setup.js", "server");
|
||||
api.addFiles("password_tests.js", ["client", "server"]);
|
||||
api.addFiles("email_tests_setup.js", "server");
|
||||
api.addFiles("email_tests.js", "client");
|
||||
api.addFiles("password_argon_tests.js", ["client", "server"]);
|
||||
});
|
||||
|
||||
221
packages/accounts-password/password_argon_tests.js
Normal file
221
packages/accounts-password/password_argon_tests.js
Normal file
@@ -0,0 +1,221 @@
|
||||
if (Meteor.isServer) {
|
||||
Tinytest.addAsync("passwords Argon - migration from bcrypt encryption to argon2", async (test) => {
|
||||
Accounts._options.argon2Enabled = false;
|
||||
const username = Random.id();
|
||||
const email = `${username}@bcrypt.com`;
|
||||
const password = "password";
|
||||
const userId = await Accounts.createUser(
|
||||
{
|
||||
username: username,
|
||||
email: email,
|
||||
password: password
|
||||
}
|
||||
);
|
||||
Accounts._options.argon2Enabled = true;
|
||||
let user = await Meteor.users.findOneAsync(userId);
|
||||
const isValid = await Accounts._checkPasswordAsync(user, password);
|
||||
test.equal(isValid.userId, userId, "checkPassword with bcrypt - User ID should be returned");
|
||||
test.equal(typeof isValid.error, "undefined", "checkPassword with bcrypt - No error should be returned");
|
||||
|
||||
// wait for the migration to happen
|
||||
await waitUntil(
|
||||
async () => {
|
||||
user = await Meteor.users.findOneAsync(userId);
|
||||
return (
|
||||
typeof user.services.password.bcrypt === "undefined" &&
|
||||
typeof user.services.password.argon2 === "string"
|
||||
);
|
||||
},
|
||||
{ description: "bcrypt should be unset and argon2 should be set" }
|
||||
);
|
||||
|
||||
// password is still valid using argon2
|
||||
const isValidArgon = await Accounts._checkPasswordAsync(user, password);
|
||||
test.equal(isValidArgon.userId, userId, "checkPassword with argon2 - User ID should be returned");
|
||||
test.equal(typeof isValidArgon.error, "undefined", "checkPassword with argon2 - No error should be returned");
|
||||
|
||||
// cleanup
|
||||
Accounts._options.argon2Enabled = false;
|
||||
await Meteor.users.removeAsync(userId);
|
||||
});
|
||||
|
||||
|
||||
Tinytest.addAsync("passwords Argon - setPassword", async (test) => {
|
||||
Accounts._options.argon2Enabled = true;
|
||||
const username = Random.id();
|
||||
const email = `${username}-intercept@example.com`;
|
||||
|
||||
const userId = await Accounts.createUser({ username: username, email: email });
|
||||
|
||||
let user = await Meteor.users.findOneAsync(userId);
|
||||
// no services yet.
|
||||
test.equal(user.services.password, undefined);
|
||||
|
||||
// set a new password.
|
||||
await Accounts.setPasswordAsync(userId, "new password");
|
||||
user = await Meteor.users.findOneAsync(userId);
|
||||
const oldSaltedHash = user.services.password.argon2;
|
||||
test.isTrue(oldSaltedHash);
|
||||
// Send a reset password email (setting a reset token) and insert a login
|
||||
// token.
|
||||
await Accounts.sendResetPasswordEmail(userId, email);
|
||||
await Accounts._insertLoginToken(userId, Accounts._generateStampedLoginToken());
|
||||
const user2 = await Meteor.users.findOneAsync(userId);
|
||||
test.isTrue(user2.services.password.reset);
|
||||
test.isTrue(user2.services.resume.loginTokens);
|
||||
|
||||
// reset with the same password, see we get a different salted hash
|
||||
await Accounts.setPasswordAsync(userId, "new password", { logout: false });
|
||||
user = await Meteor.users.findOneAsync(userId);
|
||||
const newSaltedHash = user.services.password.argon2;
|
||||
test.isTrue(newSaltedHash);
|
||||
test.notEqual(oldSaltedHash, newSaltedHash);
|
||||
// No more reset token.
|
||||
const user3 = await Meteor.users.findOneAsync(userId);
|
||||
test.isFalse(user3.services.password.reset);
|
||||
// But loginTokens are still here since we did logout: false.
|
||||
test.isTrue(user3.services.resume.loginTokens);
|
||||
|
||||
// reset again, see that the login tokens are gone.
|
||||
await Accounts.setPasswordAsync(userId, "new password");
|
||||
user = await Meteor.users.findOneAsync(userId);
|
||||
const newerSaltedHash = user.services.password.argon2;
|
||||
test.isTrue(newerSaltedHash);
|
||||
test.notEqual(oldSaltedHash, newerSaltedHash);
|
||||
test.notEqual(newSaltedHash, newerSaltedHash);
|
||||
// No more tokens.
|
||||
const user4 = await Meteor.users.findOneAsync(userId);
|
||||
test.isFalse(user4.services.password.reset);
|
||||
test.isFalse(user4.services.resume.loginTokens);
|
||||
|
||||
// cleanup
|
||||
Accounts._options.argon2Enabled = false;
|
||||
await Meteor.users.removeAsync(userId);
|
||||
});
|
||||
|
||||
Tinytest.addAsync("passwords Argon - migration from argon2 encryption to bcrypt", async (test) => {
|
||||
Accounts._options.argon2Enabled = true;
|
||||
const username = Random.id();
|
||||
const email = `${username}@bcrypt.com`;
|
||||
const password = "password";
|
||||
const userId = await Accounts.createUser(
|
||||
{
|
||||
username: username,
|
||||
email: email,
|
||||
password: password
|
||||
}
|
||||
);
|
||||
Accounts._options.argon2Enabled = false;
|
||||
let user = await Meteor.users.findOneAsync(userId);
|
||||
const isValidArgon = await Accounts._checkPasswordAsync(user, password);
|
||||
test.equal(isValidArgon.userId, userId, "checkPassword with argon2 - User ID should be returned");
|
||||
test.equal(typeof isValidArgon.error, "undefined", "checkPassword with argon2 - No error should be returned");
|
||||
|
||||
// wait for the migration to happen
|
||||
await waitUntil(
|
||||
async () => {
|
||||
user = await Meteor.users.findOneAsync(userId);
|
||||
return (
|
||||
typeof user.services.password.bcrypt === "string" &&
|
||||
typeof user.services.password.argon2 === "undefined"
|
||||
);
|
||||
},
|
||||
{ description: "bcrypt should be string and argon2 should be undefined" }
|
||||
);
|
||||
|
||||
// password is still valid using bcrypt
|
||||
const isValidBcrypt = await Accounts._checkPasswordAsync(user, password);
|
||||
test.equal(isValidBcrypt.userId, userId, "checkPassword with argon2 - User ID should be returned");
|
||||
test.equal(typeof isValidBcrypt.error, "undefined", "checkPassword with argon2 - No error should be returned");
|
||||
|
||||
// cleanup
|
||||
await Meteor.users.removeAsync(userId);
|
||||
});
|
||||
|
||||
const getUserHashArgon2Params = function (user) {
|
||||
const hash = user?.services?.password?.argon2;
|
||||
return Accounts._getArgon2Params(hash);
|
||||
}
|
||||
const hashPasswordWithSha = function (password) {
|
||||
return {
|
||||
digest: SHA256(password),
|
||||
algorithm: "sha-256"
|
||||
};
|
||||
}
|
||||
|
||||
testAsyncMulti("passwords Argon - allow custom argon2 Params and ensure migration if changed", [
|
||||
async function(test) {
|
||||
Accounts._options.argon2Enabled = true;
|
||||
// Verify that a argon2 hash generated for a new account uses the
|
||||
// default params.
|
||||
let username = Random.id();
|
||||
this.password = hashPasswordWithSha("abc123");
|
||||
this.userId1 = await Accounts.createUserAsync({ username, password: this.password });
|
||||
this.user1 = await Meteor.users.findOneAsync(this.userId1);
|
||||
let argon2Params = getUserHashArgon2Params(this.user1);
|
||||
test.equal(argon2Params.type, Accounts._argon2Type());
|
||||
test.equal(argon2Params.memoryCost, Accounts._argon2MemoryCost());
|
||||
test.equal(argon2Params.timeCost, Accounts._argon2TimeCost());
|
||||
test.equal(argon2Params.parallelism, Accounts._argon2Parallelism());
|
||||
|
||||
|
||||
// When a custom number of argon2 TimeCost is set via Accounts.config,
|
||||
// and an account was already created using the default number of TimeCost,
|
||||
// make sure that a new hash is created (and stored) using the new number
|
||||
// of TimeCost, the next time the password is checked.
|
||||
this.customType = "argon2d"; // argon2.argon2d = 2
|
||||
this.customTimeCost = 4;
|
||||
this.customMemoryCost = 32768;
|
||||
this.customParallelism = 1;
|
||||
Accounts._options.argon2Type = this.customType;
|
||||
Accounts._options.argon2TimeCost = this.customTimeCost;
|
||||
Accounts._options.argon2MemoryCost = this.customMemoryCost;
|
||||
Accounts._options.argon2Parallelism = this.customParallelism;
|
||||
|
||||
await Accounts._checkPasswordAsync(this.user1, this.password);
|
||||
},
|
||||
async function(test) {
|
||||
const defaultType = Accounts._argon2Type();
|
||||
const defaultTimeCost = Accounts._argon2TimeCost();
|
||||
const defaultMemoryCost = Accounts._argon2MemoryCost();
|
||||
const defaultParallelism = Accounts._argon2Parallelism();
|
||||
let params;
|
||||
let username;
|
||||
|
||||
let resolve;
|
||||
const promise = new Promise(res => resolve = res);
|
||||
|
||||
Meteor.setTimeout(async () => {
|
||||
this.user1 = await Meteor.users.findOneAsync(this.userId1);
|
||||
params = getUserHashArgon2Params(this.user1);
|
||||
test.equal(params.type, 2);
|
||||
test.equal(params.timeCost, this.customTimeCost);
|
||||
test.equal(params.memoryCost, this.customMemoryCost);
|
||||
test.equal(params.parallelism, this.customParallelism);
|
||||
|
||||
// When a custom number of argon2 TimeCost is set, make sure it's
|
||||
// used for new argon2 password hashes.
|
||||
username = Random.id();
|
||||
const userId2 = await Accounts.createUser({ username, password: this.password });
|
||||
const user2 = await Meteor.users.findOneAsync(userId2);
|
||||
params = getUserHashArgon2Params(user2);
|
||||
test.equal(params.type, 2);
|
||||
test.equal(params.timeCost, this.customTimeCost);
|
||||
test.equal(params.memoryCost, this.customMemoryCost);
|
||||
test.equal(params.parallelism, this.customParallelism);
|
||||
|
||||
// Cleanup
|
||||
Accounts._options.argon2Enabled = false;
|
||||
Accounts._options.argon2Type = defaultType;
|
||||
Accounts._options.argon2TimeCost = defaultTimeCost;
|
||||
Accounts._options.argon2MemoryCost = defaultMemoryCost;
|
||||
Accounts._options.argon2Parallelism = defaultParallelism;
|
||||
await Meteor.users.removeAsync(this.userId1);
|
||||
await Meteor.users.removeAsync(userId2);
|
||||
resolve();
|
||||
}, 1000);
|
||||
|
||||
return promise;
|
||||
}
|
||||
]);
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
import { hash as bcryptHash, compare as bcryptCompare } from 'bcrypt';
|
||||
import argon2 from "argon2";
|
||||
import { hash as bcryptHash, compare as bcryptCompare } from "bcrypt";
|
||||
import { Accounts } from "meteor/accounts-base";
|
||||
|
||||
// Utility for grabbing user
|
||||
@@ -6,8 +7,9 @@ const getUserById =
|
||||
async (id, options) =>
|
||||
await Meteor.users.findOneAsync(id, Accounts._addDefaultFieldSelector(options));
|
||||
|
||||
// User records have a 'services.password.bcrypt' field on them to hold
|
||||
// their hashed passwords.
|
||||
// User records have two fields that are used for password-based login:
|
||||
// - 'services.password.bcrypt', which stores the bcrypt password, which will be deprecated
|
||||
// - 'services.password.argon2', which stores the argon2 password
|
||||
//
|
||||
// When the client sends a password to the server, it can either be a
|
||||
// string (the plaintext password) or an object with keys 'digest' and
|
||||
@@ -17,90 +19,268 @@ const getUserById =
|
||||
// strings.
|
||||
//
|
||||
// When the server receives a plaintext password as a string, it always
|
||||
// hashes it with SHA256 before passing it into bcrypt. When the server
|
||||
// hashes it with SHA256 before passing it into bcrypt / argon2. When the server
|
||||
// receives a password as an object, it asserts that the algorithm is
|
||||
// "sha-256" and then passes the digest to bcrypt.
|
||||
|
||||
// "sha-256" and then passes the digest to bcrypt / argon2.
|
||||
|
||||
Accounts._bcryptRounds = () => Accounts._options.bcryptRounds || 10;
|
||||
|
||||
// Given a 'password' from the client, extract the string that we should
|
||||
// bcrypt. 'password' can be one of:
|
||||
// - String (the plaintext password)
|
||||
// - Object with 'digest' and 'algorithm' keys. 'algorithm' must be "sha-256".
|
||||
//
|
||||
Accounts._argon2Enabled = () => Accounts._options.argon2Enabled || false;
|
||||
|
||||
const ARGON2_TYPES = {
|
||||
argon2i: argon2.argon2i,
|
||||
argon2d: argon2.argon2d,
|
||||
argon2id: argon2.argon2id
|
||||
};
|
||||
|
||||
Accounts._argon2Type = () => ARGON2_TYPES[Accounts._options.argon2Type] || argon2.argon2id;
|
||||
Accounts._argon2TimeCost = () => Accounts._options.argon2TimeCost || 2;
|
||||
Accounts._argon2MemoryCost = () => Accounts._options.argon2MemoryCost || 19456;
|
||||
Accounts._argon2Parallelism = () => Accounts._options.argon2Parallelism || 1;
|
||||
|
||||
/**
|
||||
* Extracts the string to be encrypted using bcrypt or Argon2 from the given `password`.
|
||||
*
|
||||
* @param {string|Object} password - The password provided by the client. It can be:
|
||||
* - A plaintext string password.
|
||||
* - An object with the following properties:
|
||||
* @property {string} digest - The hashed password.
|
||||
* @property {string} algorithm - The hashing algorithm used. Must be "sha-256".
|
||||
*
|
||||
* @returns {string} - The resulting password string to encrypt.
|
||||
*
|
||||
* @throws {Error} - If the `algorithm` in the password object is not "sha-256".
|
||||
*/
|
||||
const getPasswordString = password => {
|
||||
if (typeof password === "string") {
|
||||
password = SHA256(password);
|
||||
} else { // 'password' is an object
|
||||
}
|
||||
else { // 'password' is an object
|
||||
if (password.algorithm !== "sha-256") {
|
||||
throw new Error("Invalid password hash algorithm. " +
|
||||
"Only 'sha-256' is allowed.");
|
||||
"Only 'sha-256' is allowed.");
|
||||
}
|
||||
password = password.digest;
|
||||
}
|
||||
return password;
|
||||
};
|
||||
|
||||
// Use bcrypt to hash the password for storage in the database.
|
||||
// `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`).
|
||||
//
|
||||
const hashPassword = async password => {
|
||||
/**
|
||||
* Encrypt the given `password` using either bcrypt or Argon2.
|
||||
* @param password can be a string (in which case it will be run through SHA256 before encryption) or an object with properties `digest` and `algorithm` (in which case we bcrypt or Argon2 `password.digest`).
|
||||
* @returns {Promise<string>} The encrypted password.
|
||||
*/
|
||||
const hashPassword = async (password) => {
|
||||
password = getPasswordString(password);
|
||||
return await bcryptHash(password, Accounts._bcryptRounds());
|
||||
if (Accounts._argon2Enabled() === true) {
|
||||
return await argon2.hash(password, {
|
||||
type: Accounts._argon2Type(),
|
||||
timeCost: Accounts._argon2TimeCost(),
|
||||
memoryCost: Accounts._argon2MemoryCost(),
|
||||
parallelism: Accounts._argon2Parallelism()
|
||||
});
|
||||
}
|
||||
else {
|
||||
return await bcryptHash(password, Accounts._bcryptRounds());
|
||||
}
|
||||
};
|
||||
|
||||
// Extract the number of rounds used in the specified bcrypt hash.
|
||||
const getRoundsFromBcryptHash = hash => {
|
||||
const getRoundsFromBcryptHash = (hash) => {
|
||||
let rounds;
|
||||
if (hash) {
|
||||
const hashSegments = hash.split('$');
|
||||
const hashSegments = hash.split("$");
|
||||
if (hashSegments.length > 2) {
|
||||
rounds = parseInt(hashSegments[2], 10);
|
||||
}
|
||||
}
|
||||
return rounds;
|
||||
};
|
||||
Accounts._getRoundsFromBcryptHash = getRoundsFromBcryptHash;
|
||||
|
||||
// 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`).
|
||||
//
|
||||
// The user parameter needs at least user._id and user.services
|
||||
Accounts._checkPasswordUserFields = {_id: 1, services: 1};
|
||||
//
|
||||
|
||||
/**
|
||||
* Extract readable parameters from an Argon2 hash string.
|
||||
* @param {string} hash - The Argon2 hash string.
|
||||
* @returns {object} An object containing the parsed parameters.
|
||||
* @throws {Error} If the hash format is invalid.
|
||||
*/
|
||||
function getArgon2Params(hash) {
|
||||
const regex = /^\$(argon2(?:i|d|id))\$v=\d+\$m=(\d+),t=(\d+),p=(\d+)/;
|
||||
|
||||
const match = hash.match(regex);
|
||||
|
||||
if (!match) {
|
||||
throw new Error("Invalid Argon2 hash format.");
|
||||
}
|
||||
|
||||
const [, type, memoryCost, timeCost, parallelism] = match;
|
||||
|
||||
return {
|
||||
type: ARGON2_TYPES[type],
|
||||
timeCost: parseInt(timeCost, 10),
|
||||
memoryCost: parseInt(memoryCost, 10),
|
||||
parallelism: parseInt(parallelism, 10)
|
||||
};
|
||||
}
|
||||
|
||||
Accounts._getArgon2Params = getArgon2Params;
|
||||
|
||||
const getUserPasswordHash = user => {
|
||||
return user.services?.password?.argon2 || user.services?.password?.bcrypt;
|
||||
};
|
||||
|
||||
Accounts._checkPasswordUserFields = { _id: 1, services: 1 };
|
||||
|
||||
const isBcrypt = (hash) => {
|
||||
// bcrypt hashes start with $2a$ or $2b$
|
||||
return hash.startsWith("$2");
|
||||
};
|
||||
|
||||
const isArgon = (hash) => {
|
||||
// argon2 hashes start with $argon2i$, $argon2d$ or $argon2id$
|
||||
return hash.startsWith("$argon2");
|
||||
}
|
||||
|
||||
const updateUserPasswordDefered = (user, formattedPassword) => {
|
||||
Meteor.defer(async () => {
|
||||
await updateUserPassword(user, formattedPassword);
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Hashes the provided password and returns an object that can be used to update the user's password.
|
||||
* @param formattedPassword
|
||||
* @returns {Promise<{$set: {"services.password.bcrypt": string}}|{$unset: {"services.password.bcrypt": number}, $set: {"services.password.argon2": string}}>}
|
||||
*/
|
||||
const getUpdatorForUserPassword = async (formattedPassword) => {
|
||||
const encryptedPassword = await hashPassword(formattedPassword);
|
||||
if (Accounts._argon2Enabled() === false) {
|
||||
return {
|
||||
$set: {
|
||||
"services.password.bcrypt": encryptedPassword
|
||||
},
|
||||
$unset: {
|
||||
"services.password.argon2": 1
|
||||
}
|
||||
};
|
||||
}
|
||||
else if (Accounts._argon2Enabled() === true) {
|
||||
return {
|
||||
$set: {
|
||||
"services.password.argon2": encryptedPassword
|
||||
},
|
||||
$unset: {
|
||||
"services.password.bcrypt": 1
|
||||
}
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
const updateUserPassword = async (user, formattedPassword) => {
|
||||
const updator = await getUpdatorForUserPassword(formattedPassword);
|
||||
await Meteor.users.updateAsync({ _id: user._id }, updator);
|
||||
};
|
||||
|
||||
/**
|
||||
* Checks whether the provided password matches the hashed password stored in the user's database record.
|
||||
*
|
||||
* @param {Object} user - The user object containing at least:
|
||||
* @property {string} _id - The user's unique identifier.
|
||||
* @property {Object} services - The user's services data.
|
||||
* @property {Object} services.password - The user's password object.
|
||||
* @property {string} [services.password.argon2] - The Argon2 hashed password.
|
||||
* @property {string} [services.password.bcrypt] - The bcrypt hashed password, deprecated
|
||||
*
|
||||
* @param {string|Object} password - The password provided by the client. It can be:
|
||||
* - A plaintext string password.
|
||||
* - An object with the following properties:
|
||||
* @property {string} digest - The hashed password.
|
||||
* @property {string} algorithm - The hashing algorithm used. Must be "sha-256".
|
||||
*
|
||||
* @returns {Promise<Object>} - A result object with the following properties:
|
||||
* @property {string} userId - The user's unique identifier.
|
||||
* @property {Object} [error] - An error object if the password does not match or an error occurs.
|
||||
*
|
||||
* @throws {Error} - If an unexpected error occurs during the process.
|
||||
*/
|
||||
const checkPasswordAsync = async (user, password) => {
|
||||
const result = {
|
||||
userId: user._id
|
||||
};
|
||||
|
||||
const formattedPassword = getPasswordString(password);
|
||||
const hash = user.services.password.bcrypt;
|
||||
const hashRounds = getRoundsFromBcryptHash(hash);
|
||||
const hash = getUserPasswordHash(user);
|
||||
|
||||
if (! await bcryptCompare(formattedPassword, hash)) {
|
||||
result.error = Accounts._handleError("Incorrect password", false);
|
||||
} else if (hash && Accounts._bcryptRounds() != hashRounds) {
|
||||
// The password checks out, but the user's bcrypt hash needs to be updated.
|
||||
|
||||
Meteor.defer(async () => {
|
||||
await Meteor.users.updateAsync({ _id: user._id }, {
|
||||
$set: {
|
||||
'services.password.bcrypt':
|
||||
await bcryptHash(formattedPassword, Accounts._bcryptRounds())
|
||||
const argon2Enabled = Accounts._argon2Enabled();
|
||||
if (argon2Enabled === false) {
|
||||
if (isArgon(hash)) {
|
||||
// this is a rollback feature, enabling to switch back from argon2 to bcrypt if needed
|
||||
// TODO : deprecate this
|
||||
console.warn("User has an argon2 password and argon2 is not enabled, rolling back to bcrypt encryption");
|
||||
const match = await argon2.verify(hash, formattedPassword);
|
||||
if (!match) {
|
||||
result.error = Accounts._handleError("Incorrect password", false);
|
||||
}
|
||||
else{
|
||||
// The password checks out, but the user's stored password needs to be updated to argon2
|
||||
updateUserPasswordDefered(user, { digest: formattedPassword, algorithm: "sha-256" });
|
||||
}
|
||||
}
|
||||
else {
|
||||
const hashRounds = getRoundsFromBcryptHash(hash);
|
||||
const match = await bcryptCompare(formattedPassword, hash);
|
||||
if (!match) {
|
||||
result.error = Accounts._handleError("Incorrect password", false);
|
||||
}
|
||||
else if (hash) {
|
||||
const paramsChanged = hashRounds !== Accounts._bcryptRounds();
|
||||
// The password checks out, but the user's bcrypt hash needs to be updated
|
||||
// to match current bcrypt settings
|
||||
if (paramsChanged === true) {
|
||||
updateUserPasswordDefered(user, { digest: formattedPassword, algorithm: "sha-256" });
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (argon2Enabled === true) {
|
||||
if (isBcrypt(hash)) {
|
||||
// migration code from bcrypt to argon2
|
||||
const match = await bcryptCompare(formattedPassword, hash);
|
||||
if (!match) {
|
||||
result.error = Accounts._handleError("Incorrect password", false);
|
||||
}
|
||||
else {
|
||||
// The password checks out, but the user's stored password needs to be updated to argon2
|
||||
updateUserPasswordDefered(user, { digest: formattedPassword, algorithm: "sha-256" });
|
||||
}
|
||||
}
|
||||
else {
|
||||
// argon2 password
|
||||
const argon2Params = getArgon2Params(hash);
|
||||
const match = await argon2.verify(hash, formattedPassword);
|
||||
if (!match) {
|
||||
result.error = Accounts._handleError("Incorrect password", false);
|
||||
}
|
||||
else if (hash) {
|
||||
const paramsChanged = argon2Params.memoryCost !== Accounts._argon2MemoryCost() ||
|
||||
argon2Params.timeCost !== Accounts._argon2TimeCost() ||
|
||||
argon2Params.parallelism !== Accounts._argon2Parallelism() ||
|
||||
argon2Params.type !== Accounts._argon2Type();
|
||||
if (paramsChanged === true) {
|
||||
// The password checks out, but the user's argon2 hash needs to be updated with the right params
|
||||
updateUserPasswordDefered(user, { digest: formattedPassword, algorithm: "sha-256" });
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return result;
|
||||
};
|
||||
|
||||
Accounts._checkPasswordAsync = checkPasswordAsync;
|
||||
Accounts._checkPasswordAsync = checkPasswordAsync;
|
||||
|
||||
///
|
||||
/// LOGIN
|
||||
@@ -185,9 +365,7 @@ Accounts.registerLoginHandler("password", async options => {
|
||||
Accounts._handleError("User not found");
|
||||
}
|
||||
|
||||
|
||||
if (!user.services || !user.services.password ||
|
||||
!user.services.password.bcrypt) {
|
||||
if (!getUserPasswordHash(user)) {
|
||||
Accounts._handleError("User has no password set");
|
||||
}
|
||||
|
||||
@@ -267,51 +445,54 @@ Accounts.setUsername =
|
||||
// `digest` and `algorithm` (representing the SHA256 of the password).
|
||||
Meteor.methods(
|
||||
{
|
||||
changePassword: async function (oldPassword, newPassword) {
|
||||
check(oldPassword, passwordValidator);
|
||||
check(newPassword, passwordValidator);
|
||||
changePassword: async function(oldPassword, newPassword) {
|
||||
check(oldPassword, passwordValidator);
|
||||
check(newPassword, passwordValidator);
|
||||
|
||||
if (!this.userId) {
|
||||
throw new Meteor.Error(401, "Must be logged in");
|
||||
}
|
||||
if (!this.userId) {
|
||||
throw new Meteor.Error(401, "Must be logged in");
|
||||
}
|
||||
|
||||
const user = await getUserById(this.userId, {fields: {
|
||||
services: 1,
|
||||
...Accounts._checkPasswordUserFields,
|
||||
}});
|
||||
if (!user) {
|
||||
Accounts._handleError("User not found");
|
||||
}
|
||||
const user = await getUserById(this.userId, {
|
||||
fields: {
|
||||
services: 1,
|
||||
...Accounts._checkPasswordUserFields
|
||||
}
|
||||
});
|
||||
if (!user) {
|
||||
Accounts._handleError("User not found");
|
||||
}
|
||||
|
||||
if (!user.services || !user.services.password || !user.services.password.bcrypt) {
|
||||
Accounts._handleError("User has no password set");
|
||||
}
|
||||
if (!getUserPasswordHash(user)) {
|
||||
Accounts._handleError("User has no password set");
|
||||
}
|
||||
|
||||
const result = await checkPasswordAsync(user, oldPassword);
|
||||
if (result.error) {
|
||||
throw result.error;
|
||||
}
|
||||
const result = await checkPasswordAsync(user, oldPassword);
|
||||
if (result.error) {
|
||||
throw result.error;
|
||||
}
|
||||
|
||||
const hashed = await hashPassword(newPassword);
|
||||
// It would be better if this removed ALL existing tokens and replaced
|
||||
// the token for the current connection with a new one, but that would
|
||||
// be tricky, so we'll settle for just replacing all tokens other than
|
||||
// the one for the current connection.
|
||||
const currentToken = Accounts._getLoginToken(this.connection.id);
|
||||
const updator = await getUpdatorForUserPassword(newPassword);
|
||||
|
||||
// It would be better if this removed ALL existing tokens and replaced
|
||||
// the token for the current connection with a new one, but that would
|
||||
// be tricky, so we'll settle for just replacing all tokens other than
|
||||
// the one for the current connection.
|
||||
const currentToken = Accounts._getLoginToken(this.connection.id);
|
||||
await Meteor.users.updateAsync(
|
||||
{ _id: this.userId },
|
||||
{
|
||||
$set: { 'services.password.bcrypt': hashed },
|
||||
$pull: {
|
||||
'services.resume.loginTokens': { hashedToken: { $ne: currentToken } }
|
||||
},
|
||||
$unset: { 'services.password.reset': 1 }
|
||||
await Meteor.users.updateAsync(
|
||||
{ _id: this.userId },
|
||||
{
|
||||
$set: updator.$set,
|
||||
$pull: {
|
||||
"services.resume.loginTokens": { hashedToken: { $ne: currentToken } }
|
||||
},
|
||||
$unset: { "services.password.reset": 1, ...updator.$unset }
|
||||
}
|
||||
);
|
||||
|
||||
return { passwordChanged: true };
|
||||
}
|
||||
);
|
||||
|
||||
return {passwordChanged: true};
|
||||
}});
|
||||
});
|
||||
|
||||
|
||||
// Force change the users password.
|
||||
@@ -320,37 +501,34 @@ Meteor.methods(
|
||||
* @summary Forcibly change the password for a user.
|
||||
* @locus Server
|
||||
* @param {String} userId The id of the user to update.
|
||||
* @param {String} newPassword A new password for the user.
|
||||
* @param {String} newPlaintextPassword A new password for the user.
|
||||
* @param {Object} [options]
|
||||
* @param {Object} options.logout Logout all current connections with this userId (default: true)
|
||||
* @importFromPackage accounts-base
|
||||
*/
|
||||
Accounts.setPasswordAsync =
|
||||
async (userId, newPlaintextPassword, options) => {
|
||||
check(userId, String);
|
||||
check(newPlaintextPassword, Match.Where(str => Match.test(str, String) && str.length <= Meteor.settings?.packages?.accounts?.passwordMaxLength || 256));
|
||||
check(options, Match.Maybe({ logout: Boolean }));
|
||||
options = { logout: true , ...options };
|
||||
check(userId, String);
|
||||
check(newPlaintextPassword, Match.Where(str => Match.test(str, String) && str.length <= Meteor.settings?.packages?.accounts?.passwordMaxLength || 256));
|
||||
check(options, Match.Maybe({ logout: Boolean }));
|
||||
options = { logout: true, ...options };
|
||||
|
||||
const user = await getUserById(userId, { fields: { _id: 1 } });
|
||||
if (!user) {
|
||||
throw new Meteor.Error(403, "User not found");
|
||||
}
|
||||
const user = await getUserById(userId, { fields: { _id: 1 } });
|
||||
if (!user) {
|
||||
throw new Meteor.Error(403, "User not found");
|
||||
}
|
||||
|
||||
const update = {
|
||||
$unset: {
|
||||
'services.password.reset': 1
|
||||
},
|
||||
$set: {'services.password.bcrypt': await hashPassword(newPlaintextPassword)}
|
||||
let updator = await getUpdatorForUserPassword(newPlaintextPassword);
|
||||
updator.$unset = updator.$unset || {};
|
||||
updator.$unset["services.password.reset"] = 1;
|
||||
|
||||
if (options.logout) {
|
||||
updator.$unset["services.resume.loginTokens"] = 1;
|
||||
}
|
||||
|
||||
await Meteor.users.updateAsync({ _id: user._id }, updator);
|
||||
};
|
||||
|
||||
if (options.logout) {
|
||||
update.$unset['services.resume.loginTokens'] = 1;
|
||||
}
|
||||
|
||||
await Meteor.users.updateAsync({_id: user._id}, update);
|
||||
};
|
||||
|
||||
///
|
||||
/// RESETTING VIA EMAIL
|
||||
///
|
||||
@@ -430,25 +608,32 @@ Accounts.generateResetToken =
|
||||
// if this method is called from the enroll account work-flow then
|
||||
// store the token record in 'services.password.enroll' db field
|
||||
// else store the token record in in 'services.password.reset' db field
|
||||
if(reason === 'enrollAccount') {
|
||||
await Meteor.users.updateAsync({_id: user._id}, {
|
||||
$set : {
|
||||
'services.password.enroll': tokenRecord
|
||||
if (reason === "enrollAccount") {
|
||||
await Meteor.users.updateAsync(
|
||||
{ _id: user._id },
|
||||
{
|
||||
$set: {
|
||||
"services.password.enroll": tokenRecord
|
||||
}
|
||||
}
|
||||
});
|
||||
);
|
||||
// before passing to template, update user object with new token
|
||||
Meteor._ensure(user, 'services', 'password').enroll = tokenRecord;
|
||||
} else {
|
||||
await Meteor.users.updateAsync({_id: user._id}, {
|
||||
$set : {
|
||||
'services.password.reset': tokenRecord
|
||||
Meteor._ensure(user, "services", "password").enroll = tokenRecord;
|
||||
}
|
||||
else {
|
||||
await Meteor.users.updateAsync(
|
||||
{ _id: user._id },
|
||||
{
|
||||
$set: {
|
||||
"services.password.reset": tokenRecord
|
||||
}
|
||||
}
|
||||
});
|
||||
);
|
||||
// before passing to template, update user object with new token
|
||||
Meteor._ensure(user, 'services', 'password').reset = tokenRecord;
|
||||
Meteor._ensure(user, "services", "password").reset = tokenRecord;
|
||||
}
|
||||
|
||||
return {email, user, token};
|
||||
return { email, user, token };
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -534,7 +719,7 @@ Accounts.sendResetPasswordEmail =
|
||||
const options = await Accounts.generateOptionsForEmail(realEmail, user, url, 'resetPassword');
|
||||
await Email.sendAsync(options);
|
||||
|
||||
if (Meteor.isDevelopment) {
|
||||
if (Meteor.isDevelopment && !Meteor.isPackageTest) {
|
||||
console.log(`\nReset password URL: ${ url }`);
|
||||
}
|
||||
return { email: realEmail, user, token, url, options };
|
||||
@@ -570,7 +755,7 @@ Accounts.sendEnrollmentEmail =
|
||||
await Accounts.generateOptionsForEmail(realEmail, user, url, 'enrollAccount');
|
||||
|
||||
await Email.sendAsync(options);
|
||||
if (Meteor.isDevelopment) {
|
||||
if (Meteor.isDevelopment && !Meteor.isPackageTest) {
|
||||
console.log(`\nEnrollment email URL: ${ url }`);
|
||||
}
|
||||
return { email: realEmail, user, token, url, options };
|
||||
@@ -642,8 +827,6 @@ Meteor.methods(
|
||||
error: new Meteor.Error(403, "Token has invalid email address")
|
||||
};
|
||||
|
||||
const hashed = await hashPassword(newPassword);
|
||||
|
||||
// NOTE: We're about to invalidate tokens on the user, who we might be
|
||||
// logged in as. Make sure to avoid logging ourselves out if this
|
||||
// happens. But also make sure not to leave the connection in a state
|
||||
@@ -653,6 +836,8 @@ Meteor.methods(
|
||||
const resetToOldToken = () =>
|
||||
Accounts._setLoginToken(user._id, this.connection, oldToken);
|
||||
|
||||
const updator = await getUpdatorForUserPassword(newPassword);
|
||||
|
||||
try {
|
||||
// Update the user record by:
|
||||
// - Changing the password to the new one
|
||||
@@ -664,29 +849,36 @@ Meteor.methods(
|
||||
affectedRecords = await Meteor.users.updateAsync(
|
||||
{
|
||||
_id: user._id,
|
||||
'emails.address': email,
|
||||
'services.password.enroll.token': token
|
||||
"emails.address": email,
|
||||
"services.password.enroll.token": token
|
||||
},
|
||||
{
|
||||
$set: {
|
||||
'services.password.bcrypt': hashed,
|
||||
'emails.$.verified': true
|
||||
"emails.$.verified": true,
|
||||
...updator.$set
|
||||
},
|
||||
$unset: { 'services.password.enroll': 1 }
|
||||
$unset: {
|
||||
"services.password.enroll": 1,
|
||||
...updator.$unset
|
||||
}
|
||||
});
|
||||
} else {
|
||||
}
|
||||
else {
|
||||
affectedRecords = await Meteor.users.updateAsync(
|
||||
{
|
||||
_id: user._id,
|
||||
'emails.address': email,
|
||||
'services.password.reset.token': token
|
||||
"emails.address": email,
|
||||
"services.password.reset.token": token
|
||||
},
|
||||
{
|
||||
$set: {
|
||||
'services.password.bcrypt': hashed,
|
||||
'emails.$.verified': true
|
||||
"emails.$.verified": true,
|
||||
...updator.$set
|
||||
},
|
||||
$unset: { 'services.password.reset': 1 }
|
||||
$unset: {
|
||||
"services.password.reset": 1,
|
||||
...updator.$unset
|
||||
}
|
||||
});
|
||||
}
|
||||
if (affectedRecords !== 1)
|
||||
@@ -704,15 +896,16 @@ Meteor.methods(
|
||||
await Accounts._clearAllLoginTokens(user._id);
|
||||
|
||||
if (Accounts._check2faEnabled?.(user)) {
|
||||
return {
|
||||
userId: user._id,
|
||||
error: Accounts._handleError(
|
||||
'Changed password, but user not logged in because 2FA is enabled',
|
||||
false,
|
||||
'2fa-enabled'
|
||||
),
|
||||
};
|
||||
}return { userId: user._id };
|
||||
return {
|
||||
userId: user._id,
|
||||
error: Accounts._handleError(
|
||||
'Changed password, but user not logged in because 2FA is enabled',
|
||||
false,
|
||||
'2fa-enabled'
|
||||
),
|
||||
};
|
||||
}
|
||||
return { userId: user._id };
|
||||
}
|
||||
);
|
||||
}
|
||||
@@ -748,7 +941,7 @@ Accounts.sendVerificationEmail =
|
||||
const url = Accounts.urls.verifyEmail(token, extraParams);
|
||||
const options = await Accounts.generateOptionsForEmail(realEmail, user, url, 'verifyEmail');
|
||||
await Email.sendAsync(options);
|
||||
if (Meteor.isDevelopment) {
|
||||
if (Meteor.isDevelopment && !Meteor.isPackageTest) {
|
||||
console.log(`\nVerification email URL: ${ url }`);
|
||||
}
|
||||
return { email: realEmail, user, token, url, options };
|
||||
@@ -990,7 +1183,13 @@ const createUser =
|
||||
const user = { services: {} };
|
||||
if (password) {
|
||||
const hashed = await hashPassword(password);
|
||||
user.services.password = { bcrypt: hashed };
|
||||
const argon2Enabled = Accounts._argon2Enabled();
|
||||
if (argon2Enabled === false) {
|
||||
user.services.password = { bcrypt: hashed };
|
||||
}
|
||||
else {
|
||||
user.services.password = { argon2: hashed };
|
||||
}
|
||||
}
|
||||
|
||||
return await Accounts._createUserCheckingDuplicates({ user, email, username, options });
|
||||
@@ -1074,17 +1273,7 @@ Accounts.createUserVerifyingEmail =
|
||||
// method calling Accounts.createUser could set?
|
||||
//
|
||||
|
||||
Accounts.createUserAsync =
|
||||
async (options, callback) => {
|
||||
options = { ...options };
|
||||
|
||||
// XXX allow an optional callback?
|
||||
if (callback) {
|
||||
throw new Error("Accounts.createUser with callback not supported on the server yet.");
|
||||
}
|
||||
|
||||
return createUser(options);
|
||||
};
|
||||
Accounts.createUserAsync = createUser
|
||||
|
||||
// Create user directly on the server.
|
||||
//
|
||||
|
||||
@@ -10,7 +10,7 @@ const makeTestConnAsync =
|
||||
})
|
||||
const simplePollAsync = (fn) =>
|
||||
new Promise((resolve, reject) => simplePoll(fn,resolve,reject))
|
||||
function hashPassword(password) {
|
||||
function hashPasswordWithSha(password) {
|
||||
return {
|
||||
digest: SHA256(password),
|
||||
algorithm: "sha-256"
|
||||
@@ -486,7 +486,7 @@ if (Meteor.isClient) (() => {
|
||||
function (test, expect) {
|
||||
this.secondConn = DDP.connect(Meteor.absoluteUrl());
|
||||
this.secondConn.call('login',
|
||||
{ user: { username: this.username }, password: hashPassword(this.password) },
|
||||
{ user: { username: this.username }, password: hashPasswordWithSha(this.password) },
|
||||
expect((err, result) => {
|
||||
test.isFalse(err);
|
||||
this.secondConn.setUserId(result.id);
|
||||
@@ -802,7 +802,7 @@ if (Meteor.isClient) (() => {
|
||||
// Can update own profile using ID.
|
||||
await Meteor.users.updateAsync(
|
||||
this.userId, { $set: { 'profile.updated': 42 } },
|
||||
);
|
||||
);
|
||||
test.equal(42, Meteor.user().profile.updated);
|
||||
},
|
||||
logoutStep
|
||||
@@ -1212,10 +1212,10 @@ if (Meteor.isServer) (() => {
|
||||
|
||||
// This test properly belongs in accounts-base/accounts_tests.js, but
|
||||
// this is where the tests that actually log in are.
|
||||
Tinytest.addAsync('accounts - user() out of context', async test => {
|
||||
Tinytest.addAsync('accounts - userAsync() out of context', async test => {
|
||||
await test.throwsAsync(
|
||||
async () =>
|
||||
await Meteor.user()
|
||||
await Meteor.userAsync()
|
||||
);
|
||||
await Meteor.users.removeAsync({});
|
||||
});
|
||||
@@ -1230,7 +1230,7 @@ if (Meteor.isServer) (() => {
|
||||
const username = Random.id();
|
||||
const id = await Accounts.createUser({
|
||||
username: username,
|
||||
password: hashPassword('password')
|
||||
password: hashPasswordWithSha('password')
|
||||
});
|
||||
|
||||
const {
|
||||
@@ -1245,7 +1245,7 @@ if (Meteor.isServer) (() => {
|
||||
|
||||
const result = await clientConn.callAsync('login', {
|
||||
user: { username: username },
|
||||
password: hashPassword('password')
|
||||
password: hashPasswordWithSha('password')
|
||||
});
|
||||
|
||||
test.isTrue(result);
|
||||
@@ -1278,7 +1278,7 @@ if (Meteor.isServer) (() => {
|
||||
const userId = await Accounts.createUser({
|
||||
username: username,
|
||||
email: email,
|
||||
password: hashPassword("old-password")
|
||||
password: hashPasswordWithSha("old-password")
|
||||
});
|
||||
const user = await Meteor.users.findOneAsync(userId);
|
||||
|
||||
@@ -1297,7 +1297,7 @@ if (Meteor.isServer) (() => {
|
||||
|
||||
await test.throwsAsync(
|
||||
async () =>
|
||||
await Meteor.callAsync("resetPassword", resetPasswordToken, hashPassword("new-password")),
|
||||
await Meteor.callAsync("resetPassword", resetPasswordToken, hashPasswordWithSha("new-password")),
|
||||
/Token has invalid email address/
|
||||
);
|
||||
await test.throwsAsync(
|
||||
@@ -1306,7 +1306,7 @@ if (Meteor.isServer) (() => {
|
||||
"login",
|
||||
{
|
||||
user: { username: username },
|
||||
password: hashPassword("new-password")
|
||||
password: hashPasswordWithSha("new-password")
|
||||
}
|
||||
),
|
||||
/Something went wrong. Please check your credentials./);
|
||||
@@ -1321,7 +1321,7 @@ if (Meteor.isServer) (() => {
|
||||
const userId = await Accounts.createUser({
|
||||
username: username,
|
||||
email: email,
|
||||
password: hashPassword("old-password")
|
||||
password: hashPasswordWithSha("old-password")
|
||||
});
|
||||
|
||||
const user = await Meteor.users.findOneAsync(userId);
|
||||
@@ -1338,11 +1338,11 @@ if (Meteor.isServer) (() => {
|
||||
test.isTrue(await clientConn.callAsync(
|
||||
"resetPassword",
|
||||
resetPasswordToken,
|
||||
hashPassword("new-password")
|
||||
hashPasswordWithSha("new-password")
|
||||
));
|
||||
test.isTrue(await clientConn.callAsync("login", {
|
||||
user: { username },
|
||||
password: hashPassword("new-password")
|
||||
password: hashPasswordWithSha("new-password")
|
||||
}));
|
||||
});
|
||||
|
||||
@@ -1355,7 +1355,7 @@ if (Meteor.isServer) (() => {
|
||||
const userId = await Accounts.createUser({
|
||||
username: username,
|
||||
email: email,
|
||||
password: hashPassword("old-password")
|
||||
password: hashPasswordWithSha("old-password")
|
||||
});
|
||||
|
||||
const user = await Meteor.users.findOneAsync(userId);
|
||||
@@ -1373,7 +1373,7 @@ if (Meteor.isServer) (() => {
|
||||
await Meteor.users.updateAsync(userId, { $set: { "services.password.reset.when": new Date(Date.now() + -5 * 24 * 3600 * 1000) } });
|
||||
|
||||
try {
|
||||
await Meteor.callAsync("resetPassword", resetPasswordToken, hashPassword("new-password"))
|
||||
await Meteor.callAsync("resetPassword", resetPasswordToken, hashPasswordWithSha("new-password"))
|
||||
} catch (e) {
|
||||
test.throws(() => {
|
||||
throw e;
|
||||
@@ -1385,7 +1385,7 @@ if (Meteor.isServer) (() => {
|
||||
"login",
|
||||
{
|
||||
user: { username: username },
|
||||
password: hashPassword("new-password")
|
||||
password: hashPasswordWithSha("new-password")
|
||||
}
|
||||
),
|
||||
/Something went wrong. Please check your credentials./);
|
||||
@@ -1405,7 +1405,7 @@ if (Meteor.isServer) (() => {
|
||||
{
|
||||
username: username,
|
||||
email: email,
|
||||
password: hashPassword(password)
|
||||
password: hashPasswordWithSha(password)
|
||||
},
|
||||
);
|
||||
|
||||
@@ -1432,7 +1432,7 @@ if (Meteor.isServer) (() => {
|
||||
await Accounts.createUser(
|
||||
{
|
||||
email: email,
|
||||
password: hashPassword('password')
|
||||
password: hashPasswordWithSha('password')
|
||||
}
|
||||
);
|
||||
await Accounts.sendResetPasswordEmail(userId, email);
|
||||
@@ -1452,7 +1452,7 @@ if (Meteor.isServer) (() => {
|
||||
await Accounts.createUser(
|
||||
{
|
||||
email: email,
|
||||
password: hashPassword('password')
|
||||
password: hashPasswordWithSha('password')
|
||||
}
|
||||
);
|
||||
await Accounts.sendResetPasswordEmail(userId, email);
|
||||
@@ -1498,12 +1498,12 @@ if (Meteor.isServer) (() => {
|
||||
await clientConn.callAsync(
|
||||
"resetPassword",
|
||||
enrollPasswordToken,
|
||||
hashPassword("new-password"))
|
||||
hashPasswordWithSha("new-password"))
|
||||
);
|
||||
test.isTrue(
|
||||
await clientConn.callAsync("login", {
|
||||
user: { username },
|
||||
password: hashPassword("new-password")
|
||||
password: hashPasswordWithSha("new-password")
|
||||
})
|
||||
);
|
||||
|
||||
@@ -1535,7 +1535,7 @@ if (Meteor.isServer) (() => {
|
||||
await Meteor.users.updateAsync(userId, { $set: { "services.password.enroll.when": new Date(Date.now() + -35 * 24 * 3600 * 1000) } });
|
||||
|
||||
await test.throwsAsync(
|
||||
async () => await Meteor.callAsync("resetPassword", enrollPasswordToken, hashPassword("new-password")),
|
||||
async () => await Meteor.callAsync("resetPassword", enrollPasswordToken, hashPasswordWithSha("new-password")),
|
||||
/Token expired/
|
||||
);
|
||||
});
|
||||
@@ -1544,7 +1544,7 @@ if (Meteor.isServer) (() => {
|
||||
async test => {
|
||||
const email = `${ test.id }-intercept@example.com`;
|
||||
const userId =
|
||||
await Accounts.createUser({ email: email, password: hashPassword('password') });
|
||||
await Accounts.createUser({ email: email, password: hashPasswordWithSha('password') });
|
||||
|
||||
await Accounts.sendEnrollmentEmail(userId, email);
|
||||
const user1 = await Meteor.users.findOneAsync(userId);
|
||||
@@ -1561,7 +1561,7 @@ if (Meteor.isServer) (() => {
|
||||
const userId =
|
||||
await Accounts.createUser({
|
||||
email: email,
|
||||
password: hashPassword('password')
|
||||
password: hashPasswordWithSha('password')
|
||||
});
|
||||
|
||||
await Accounts.sendEnrollmentEmail(userId, email);
|
||||
@@ -1580,7 +1580,7 @@ if (Meteor.isServer) (() => {
|
||||
async test => {
|
||||
const email = `${ test.id }-intercept@example.com`;
|
||||
const userId =
|
||||
await Accounts.createUser({ email: email, password: hashPassword('password') });
|
||||
await Accounts.createUser({ email: email, password: hashPasswordWithSha('password') });
|
||||
|
||||
await Accounts.sendResetPasswordEmail(userId, email);
|
||||
const user1 = await Meteor.users.findOneAsync(userId);
|
||||
@@ -1727,108 +1727,108 @@ if (Meteor.isServer) (() => {
|
||||
});
|
||||
|
||||
Tinytest.addAsync("passwords - add email when user has not an existing email",
|
||||
async test => {
|
||||
const userId = await Accounts.createUser({
|
||||
username: `user${ Random.id() }`
|
||||
});
|
||||
async test => {
|
||||
const userId = await Accounts.createUser({
|
||||
username: `user${ Random.id() }`
|
||||
});
|
||||
|
||||
const newEmail = `${ Random.id() }@turing.com`;
|
||||
await Accounts.addEmailAsync(userId, newEmail);
|
||||
const u1 = await Accounts._findUserByQuery({ id: userId })
|
||||
test.equal(u1.emails, [
|
||||
{ address: newEmail, verified: false },
|
||||
]);
|
||||
});
|
||||
const newEmail = `${ Random.id() }@turing.com`;
|
||||
await Accounts.addEmailAsync(userId, newEmail);
|
||||
const u1 = await Accounts._findUserByQuery({ id: userId })
|
||||
test.equal(u1.emails, [
|
||||
{ address: newEmail, verified: false },
|
||||
]);
|
||||
});
|
||||
|
||||
Tinytest.addAsync("passwords - add email when the user has an existing email " +
|
||||
"only differing in case",
|
||||
async test => {
|
||||
const origEmail = `${ Random.id() }@turing.com`;
|
||||
const userId = await Accounts.createUser({
|
||||
email: origEmail
|
||||
const origEmail = `${ Random.id() }@turing.com`;
|
||||
const userId = await Accounts.createUser({
|
||||
email: origEmail
|
||||
});
|
||||
|
||||
const newEmail = `${ Random.id() }@turing.com`;
|
||||
await Accounts.addEmailAsync(userId, newEmail);
|
||||
|
||||
const thirdEmail = origEmail.toUpperCase();
|
||||
await Accounts.addEmailAsync(userId, thirdEmail, true);
|
||||
const u1 = await Accounts._findUserByQuery({ id: userId })
|
||||
test.equal(u1.emails, [
|
||||
{ address: thirdEmail, verified: true },
|
||||
{ address: newEmail, verified: false }
|
||||
]);
|
||||
});
|
||||
|
||||
const newEmail = `${ Random.id() }@turing.com`;
|
||||
await Accounts.addEmailAsync(userId, newEmail);
|
||||
|
||||
const thirdEmail = origEmail.toUpperCase();
|
||||
await Accounts.addEmailAsync(userId, thirdEmail, true);
|
||||
const u1 = await Accounts._findUserByQuery({ id: userId })
|
||||
test.equal(u1.emails, [
|
||||
{ address: thirdEmail, verified: true },
|
||||
{ address: newEmail, verified: false }
|
||||
]);
|
||||
});
|
||||
|
||||
Tinytest.addAsync("passwords - add email should fail when there is an existing " +
|
||||
"user with an email only differing in case",
|
||||
async test => {
|
||||
const user1Email = `${ Random.id() }@turing.com`;
|
||||
const userId1 = await Accounts.createUser({
|
||||
email: user1Email
|
||||
const user1Email = `${ Random.id() }@turing.com`;
|
||||
const userId1 = await Accounts.createUser({
|
||||
email: user1Email
|
||||
});
|
||||
|
||||
const user2Email = `${ Random.id() }@turing.com`;
|
||||
const userId2 = await Accounts.createUser({
|
||||
email: user2Email
|
||||
});
|
||||
|
||||
const dupEmail = user1Email.toUpperCase();
|
||||
await test.throwsAsync(
|
||||
async () => await Accounts.addEmailAsync(userId2, dupEmail),
|
||||
/Email already exists/
|
||||
);
|
||||
|
||||
const u1 = await Accounts._findUserByQuery({ id: userId1 })
|
||||
test.equal(u1.emails, [
|
||||
{ address: user1Email, verified: false }
|
||||
]);
|
||||
const u2 = await Accounts._findUserByQuery({ id: userId2 })
|
||||
test.equal(u2.emails, [
|
||||
{ address: user2Email, verified: false }
|
||||
]);
|
||||
});
|
||||
|
||||
const user2Email = `${ Random.id() }@turing.com`;
|
||||
const userId2 = await Accounts.createUser({
|
||||
email: user2Email
|
||||
});
|
||||
|
||||
const dupEmail = user1Email.toUpperCase();
|
||||
await test.throwsAsync(
|
||||
async () => await Accounts.addEmailAsync(userId2, dupEmail),
|
||||
/Email already exists/
|
||||
);
|
||||
|
||||
const u1 = await Accounts._findUserByQuery({ id: userId1 })
|
||||
test.equal(u1.emails, [
|
||||
{ address: user1Email, verified: false }
|
||||
]);
|
||||
const u2 = await Accounts._findUserByQuery({ id: userId2 })
|
||||
test.equal(u2.emails, [
|
||||
{ address: user2Email, verified: false }
|
||||
]);
|
||||
});
|
||||
|
||||
Tinytest.addAsync("passwords - remove email",
|
||||
async test => {
|
||||
const origEmail = `${ Random.id() }@turing.com`;
|
||||
const userId = await Accounts.createUser({
|
||||
email: origEmail
|
||||
const origEmail = `${ Random.id() }@turing.com`;
|
||||
const userId = await Accounts.createUser({
|
||||
email: origEmail
|
||||
});
|
||||
|
||||
const newEmail = `${ Random.id() }@turing.com`;
|
||||
await Accounts.addEmailAsync(userId, newEmail);
|
||||
|
||||
const thirdEmail = `${ Random.id() }@turing.com`;
|
||||
await Accounts.addEmailAsync(userId, thirdEmail, true);
|
||||
const u1 = await Accounts._findUserByQuery({ id: userId })
|
||||
test.equal(u1.emails, [
|
||||
{ address: origEmail, verified: false },
|
||||
{ address: newEmail, verified: false },
|
||||
{ address: thirdEmail, verified: true }
|
||||
]);
|
||||
|
||||
await Accounts.removeEmail(userId, newEmail);
|
||||
const u2 = await Accounts._findUserByQuery({ id: userId })
|
||||
test.equal(u2.emails, [
|
||||
{ address: origEmail, verified: false },
|
||||
{ address: thirdEmail, verified: true }
|
||||
]);
|
||||
|
||||
await Accounts.removeEmail(userId, origEmail);
|
||||
const u3 = await Accounts._findUserByQuery({ id: userId })
|
||||
test.equal(u3.emails, [
|
||||
{ address: thirdEmail, verified: true }
|
||||
]);
|
||||
});
|
||||
|
||||
const newEmail = `${ Random.id() }@turing.com`;
|
||||
await Accounts.addEmailAsync(userId, newEmail);
|
||||
|
||||
const thirdEmail = `${ Random.id() }@turing.com`;
|
||||
await Accounts.addEmailAsync(userId, thirdEmail, true);
|
||||
const u1 = await Accounts._findUserByQuery({ id: userId })
|
||||
test.equal(u1.emails, [
|
||||
{ address: origEmail, verified: false },
|
||||
{ address: newEmail, verified: false },
|
||||
{ address: thirdEmail, verified: true }
|
||||
]);
|
||||
|
||||
await Accounts.removeEmail(userId, newEmail);
|
||||
const u2 = await Accounts._findUserByQuery({ id: userId })
|
||||
test.equal(u2.emails, [
|
||||
{ address: origEmail, verified: false },
|
||||
{ address: thirdEmail, verified: true }
|
||||
]);
|
||||
|
||||
await Accounts.removeEmail(userId, origEmail);
|
||||
const u3 = await Accounts._findUserByQuery({ id: userId })
|
||||
test.equal(u3.emails, [
|
||||
{ address: thirdEmail, verified: true }
|
||||
]);
|
||||
});
|
||||
|
||||
const getUserHashRounds = user =>
|
||||
Number(user.services.password.bcrypt.substring(4, 6));
|
||||
testAsyncMulti("passwords - allow custom bcrypt rounds",[
|
||||
async function (test) {
|
||||
// Verify that a bcrypt hash generated for a new account uses the
|
||||
let username = Random.id();
|
||||
this.password = hashPassword('abc123');
|
||||
this.password = hashPasswordWithSha('abc123');
|
||||
this.userId1 = await Accounts.createUser({ username, password: this.password });
|
||||
this.user1 = await Meteor.users.findOneAsync(this.userId1);
|
||||
let rounds = getUserHashRounds(this.user1);
|
||||
@@ -1876,24 +1876,52 @@ if (Meteor.isServer) (() => {
|
||||
|
||||
Tinytest.addAsync('passwords - extra params in email urls',
|
||||
async (test) => {
|
||||
const username = Random.id();
|
||||
const email = `${ username }-intercept@example.com`;
|
||||
const username = Random.id();
|
||||
const email = `${ username }-intercept@example.com`;
|
||||
|
||||
const userId = await Accounts.createUser({
|
||||
username: username,
|
||||
email: email
|
||||
const userId = await Accounts.createUser({
|
||||
username: username,
|
||||
email: email
|
||||
});
|
||||
|
||||
const extraParams = { test: 'success' };
|
||||
await Accounts.sendEnrollmentEmail(userId, email, null, extraParams);
|
||||
|
||||
const [enrollPasswordEmailOptions] =
|
||||
await Meteor.callAsync("getInterceptedEmails", email);
|
||||
|
||||
const re = new RegExp(`${Meteor.absoluteUrl()}(\\S*)`);
|
||||
const match = enrollPasswordEmailOptions.text.match(re);
|
||||
const url = new URL(match)
|
||||
test.equal(url.searchParams.get('test'), extraParams.test);
|
||||
});
|
||||
|
||||
const extraParams = { test: 'success' };
|
||||
await Accounts.sendEnrollmentEmail(userId, email, null, extraParams);
|
||||
Tinytest.addAsync('passwords - createUserAsync', async test => {
|
||||
const username = Random.id();
|
||||
const email = `${username}-intercept@example.com`;
|
||||
const password = 'password';
|
||||
|
||||
const [enrollPasswordEmailOptions] =
|
||||
await Meteor.callAsync("getInterceptedEmails", email);
|
||||
const userId = await Accounts.createUserAsync({
|
||||
username: username,
|
||||
email: email,
|
||||
password: password
|
||||
});
|
||||
|
||||
const re = new RegExp(`${Meteor.absoluteUrl()}(\\S*)`);
|
||||
const match = enrollPasswordEmailOptions.text.match(re);
|
||||
const url = new URL(match)
|
||||
test.equal(url.searchParams.get('test'), extraParams.test);
|
||||
test.isTrue(userId, 'User ID should be returned');
|
||||
const user = await Meteor.users.findOneAsync(userId);
|
||||
test.equal(user.username, username, 'Username should match');
|
||||
test.equal(user.emails[0].address, email, 'Email should match');
|
||||
|
||||
Accounts.config({
|
||||
ambiguousErrorMessages: false,
|
||||
})
|
||||
|
||||
await test.throwsAsync(async () => {
|
||||
await Accounts.createUserAsync({
|
||||
username: username,
|
||||
email: email,
|
||||
password: password
|
||||
});
|
||||
}, 'already exists');
|
||||
});
|
||||
|
||||
})();
|
||||
|
||||
@@ -121,7 +121,7 @@ Accounts.config({
|
||||
Meteor.methods(
|
||||
{
|
||||
testMeteorUser:
|
||||
async () => await Meteor.user(),
|
||||
async () => await Meteor.userAsync(),
|
||||
|
||||
clearUsernameAndProfile:
|
||||
async function () {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
Package.describe({
|
||||
summary: 'No-password login/sign-up support for accounts',
|
||||
version: '3.0.0',
|
||||
version: '3.0.1',
|
||||
});
|
||||
|
||||
Package.onUse(api => {
|
||||
|
||||
@@ -220,7 +220,7 @@ Meteor.methods({
|
||||
*/
|
||||
Accounts.sendLoginTokenEmail = async ({ userId, sequence, email, extra = {} }) => {
|
||||
const user = await getUserById(userId);
|
||||
const url = Accounts.urls.loginToken(email, sequence);
|
||||
const url = Accounts.urls.loginToken(email, sequence, extra);
|
||||
const options = await Accounts.generateOptionsForEmail(
|
||||
email,
|
||||
user,
|
||||
|
||||
@@ -49,7 +49,7 @@ const CollectionPrototype = AllowDeny.CollectionPrototype;
|
||||
* @memberOf Mongo.Collection
|
||||
* @instance
|
||||
* @param {Object} options
|
||||
* @param {Function} options.insertAsync,updateAsync,removeAsync Functions that look at a proposed modification to the database and return true if it should be allowed.
|
||||
* @param {Function} options.insert,update,remove Functions that look at a proposed modification to the database and return true if it should be allowed.
|
||||
* @param {String[]} options.fetch Optional performance enhancement. Limits the fields that will be fetched from the database for inspection by your `update` and `remove` functions.
|
||||
* @param {Function} options.transform Overrides `transform` on the [`Collection`](#collections). Pass `null` to disable transformation.
|
||||
*/
|
||||
@@ -64,7 +64,7 @@ CollectionPrototype.allow = function(options) {
|
||||
* @memberOf Mongo.Collection
|
||||
* @instance
|
||||
* @param {Object} options
|
||||
* @param {Function} options.insertAsync,updateAsync,removeAsync Functions that look at a proposed modification to the database and return true if it should be denied, even if an [allow](#allow) rule says otherwise.
|
||||
* @param {Function} options.insert,update,remove Functions that look at a proposed modification to the database and return true if it should be denied, even if an [allow](#allow) rule says otherwise.
|
||||
* @param {String[]} options.fetch Optional performance enhancement. Limits the fields that will be fetched from the database for inspection by your `update` and `remove` functions.
|
||||
* @param {Function} options.transform Overrides `transform` on the [`Collection`](#collections). Pass `null` to disable transformation.
|
||||
*/
|
||||
@@ -174,9 +174,14 @@ CollectionPrototype._defineMutationMethods = function(options) {
|
||||
// single-ID selectors.
|
||||
if (!isInsert(method)) throwIfSelectorIsNotId(args[0], method);
|
||||
|
||||
const syncMethodName = method.replace('Async', '');
|
||||
const syncValidatedMethodName = '_validated' + method.charAt(0).toUpperCase() + syncMethodName.slice(1);
|
||||
// it forces to use async validated behavior
|
||||
const validatedMethodName = syncValidatedMethodName + 'Async';
|
||||
|
||||
if (self._restricted) {
|
||||
// short circuit if there is no way it will pass.
|
||||
if (self._validators[method].allow.length === 0) {
|
||||
if (self._validators[syncMethodName].allow.length === 0) {
|
||||
throw new Meteor.Error(
|
||||
403,
|
||||
'Access denied. No allow validators set on restricted ' +
|
||||
@@ -186,11 +191,6 @@ CollectionPrototype._defineMutationMethods = function(options) {
|
||||
);
|
||||
}
|
||||
|
||||
const syncMethodName = method.replace('Async', '');
|
||||
const syncValidatedMethodName = '_validated' + method.charAt(0).toUpperCase() + syncMethodName.slice(1);
|
||||
// it forces to use async validated behavior on the server
|
||||
const validatedMethodName = Meteor.isServer ? syncValidatedMethodName + 'Async' : syncValidatedMethodName;
|
||||
|
||||
args.unshift(this.userId);
|
||||
isInsert(method) && args.push(generatedId);
|
||||
return self[validatedMethodName].apply(self, args);
|
||||
@@ -292,7 +292,7 @@ CollectionPrototype._validatedInsertAsync = async function(userId, doc,
|
||||
const self = this;
|
||||
// call user validators.
|
||||
// Any deny returns true means denied.
|
||||
if (await asyncSome(self._validators.insertAsync.deny, async (validator) => {
|
||||
if (await asyncSome(self._validators.insert.deny, async (validator) => {
|
||||
const result = validator(userId, docToValidate(validator, doc, generatedId));
|
||||
return Meteor._isPromise(result) ? await result : result;
|
||||
})) {
|
||||
@@ -300,7 +300,7 @@ CollectionPrototype._validatedInsertAsync = async function(userId, doc,
|
||||
}
|
||||
// Any allow returns true means proceed. Throw error if they all fail.
|
||||
|
||||
if (await asyncEvery(self._validators.insertAsync.allow, async (validator) => {
|
||||
if (await asyncEvery(self._validators.insert.allow, async (validator) => {
|
||||
const result = validator(userId, docToValidate(validator, doc, generatedId));
|
||||
return !(Meteor._isPromise(result) ? await result : result);
|
||||
})) {
|
||||
@@ -315,36 +315,6 @@ CollectionPrototype._validatedInsertAsync = async function(userId, doc,
|
||||
return self._collection.insertAsync.call(self._collection, doc);
|
||||
};
|
||||
|
||||
CollectionPrototype._validatedInsert = function (userId, doc,
|
||||
generatedId) {
|
||||
const self = this;
|
||||
|
||||
// call user validators.
|
||||
// Any deny returns true means denied.
|
||||
if (self._validators.insert.deny.some((validator) => {
|
||||
return validator(userId, docToValidate(validator, doc, generatedId));
|
||||
})) {
|
||||
throw new Meteor.Error(403, "Access denied");
|
||||
}
|
||||
// Any allow returns true means proceed. Throw error if they all fail.
|
||||
|
||||
if (self._validators.insert.allow.every((validator) => {
|
||||
return !validator(userId, docToValidate(validator, doc, generatedId));
|
||||
})) {
|
||||
throw new Meteor.Error(403, "Access denied");
|
||||
}
|
||||
|
||||
// If we generated an ID above, insert it now: after the validation, but
|
||||
// before actually inserting.
|
||||
if (generatedId !== null)
|
||||
doc._id = generatedId;
|
||||
|
||||
return (Meteor.isServer
|
||||
? self._collection.insertAsync
|
||||
: self._collection.insert
|
||||
).call(self._collection, doc);
|
||||
};
|
||||
|
||||
// Simulate a mongo `update` operation while validating that the access
|
||||
// control rules set by calls to `allow/deny` are satisfied. If all
|
||||
// pass, rewrite the mongo operation to use $in to set the list of
|
||||
@@ -414,7 +384,7 @@ CollectionPrototype._validatedUpdateAsync = async function(
|
||||
|
||||
// call user validators.
|
||||
// Any deny returns true means denied.
|
||||
if (await asyncSome(self._validators.updateAsync.deny, async (validator) => {
|
||||
if (await asyncSome(self._validators.update.deny, async (validator) => {
|
||||
const factoriedDoc = transformDoc(validator, doc);
|
||||
const result = validator(userId,
|
||||
factoriedDoc,
|
||||
@@ -424,8 +394,9 @@ CollectionPrototype._validatedUpdateAsync = async function(
|
||||
})) {
|
||||
throw new Meteor.Error(403, "Access denied");
|
||||
}
|
||||
|
||||
// Any allow returns true means proceed. Throw error if they all fail.
|
||||
if (await asyncEvery(self._validators.updateAsync.allow, async (validator) => {
|
||||
if (await asyncEvery(self._validators.update.allow, async (validator) => {
|
||||
const factoriedDoc = transformDoc(validator, doc);
|
||||
const result = validator(userId,
|
||||
factoriedDoc,
|
||||
@@ -447,102 +418,6 @@ CollectionPrototype._validatedUpdateAsync = async function(
|
||||
self._collection, selector, mutator, options);
|
||||
};
|
||||
|
||||
CollectionPrototype._validatedUpdate = function(
|
||||
userId, selector, mutator, options) {
|
||||
const self = this;
|
||||
|
||||
check(mutator, Object);
|
||||
|
||||
options = Object.assign(Object.create(null), options);
|
||||
|
||||
if (!LocalCollection._selectorIsIdPerhapsAsObject(selector))
|
||||
throw new Error("validated update should be of a single ID");
|
||||
|
||||
// We don't support upserts because they don't fit nicely into allow/deny
|
||||
// rules.
|
||||
if (options.upsert)
|
||||
throw new Meteor.Error(403, "Access denied. Upserts not " +
|
||||
"allowed in a restricted collection.");
|
||||
|
||||
const noReplaceError = "Access denied. In a restricted collection you can only" +
|
||||
" update documents, not replace them. Use a Mongo update operator, such " +
|
||||
"as '$set'.";
|
||||
|
||||
const mutatorKeys = Object.keys(mutator);
|
||||
|
||||
// compute modified fields
|
||||
const modifiedFields = {};
|
||||
|
||||
if (mutatorKeys.length === 0) {
|
||||
throw new Meteor.Error(403, noReplaceError);
|
||||
}
|
||||
mutatorKeys.forEach((op) => {
|
||||
const params = mutator[op];
|
||||
if (op.charAt(0) !== '$') {
|
||||
throw new Meteor.Error(403, noReplaceError);
|
||||
} else if (!hasOwn.call(ALLOWED_UPDATE_OPERATIONS, op)) {
|
||||
throw new Meteor.Error(
|
||||
403, "Access denied. Operator " + op + " not allowed in a restricted collection.");
|
||||
} else {
|
||||
Object.keys(params).forEach((field) => {
|
||||
// treat dotted fields as if they are replacing their
|
||||
// top-level part
|
||||
if (field.indexOf('.') !== -1)
|
||||
field = field.substring(0, field.indexOf('.'));
|
||||
|
||||
// record the field we are trying to change
|
||||
modifiedFields[field] = true;
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
const fields = Object.keys(modifiedFields);
|
||||
|
||||
const findOptions = {transform: null};
|
||||
if (!self._validators.fetchAllFields) {
|
||||
findOptions.fields = {};
|
||||
self._validators.fetch.forEach((fieldName) => {
|
||||
findOptions.fields[fieldName] = 1;
|
||||
});
|
||||
}
|
||||
|
||||
const doc = self._collection.findOne(selector, findOptions);
|
||||
if (!doc) // none satisfied!
|
||||
return 0;
|
||||
|
||||
// call user validators.
|
||||
// Any deny returns true means denied.
|
||||
if (self._validators.update.deny.some((validator) => {
|
||||
const factoriedDoc = transformDoc(validator, doc);
|
||||
return validator(userId,
|
||||
factoriedDoc,
|
||||
fields,
|
||||
mutator);
|
||||
})) {
|
||||
throw new Meteor.Error(403, "Access denied");
|
||||
}
|
||||
// Any allow returns true means proceed. Throw error if they all fail.
|
||||
if (self._validators.update.allow.every((validator) => {
|
||||
const factoriedDoc = transformDoc(validator, doc);
|
||||
return !validator(userId,
|
||||
factoriedDoc,
|
||||
fields,
|
||||
mutator);
|
||||
})) {
|
||||
throw new Meteor.Error(403, "Access denied");
|
||||
}
|
||||
|
||||
options._forbidReplace = true;
|
||||
|
||||
// Back when we supported arbitrary client-provided selectors, we actually
|
||||
// rewrote the selector to include an _id clause before passing to Mongo to
|
||||
// avoid races, but since selector is guaranteed to already just be an ID, we
|
||||
// don't have to any more.
|
||||
|
||||
return self._collection.update.call(
|
||||
self._collection, selector, mutator, options);
|
||||
};
|
||||
|
||||
// Only allow these operations in validated updates. Specifically
|
||||
// whitelist operations, rather than blacklist, so new complex
|
||||
// operations that are added aren't automatically allowed. A complex
|
||||
@@ -573,14 +448,14 @@ CollectionPrototype._validatedRemoveAsync = async function(userId, selector) {
|
||||
|
||||
// call user validators.
|
||||
// Any deny returns true means denied.
|
||||
if (await asyncSome(self._validators.removeAsync.deny, async (validator) => {
|
||||
if (await asyncSome(self._validators.remove.deny, async (validator) => {
|
||||
const result = validator(userId, transformDoc(validator, doc));
|
||||
return Meteor._isPromise(result) ? await result : result;
|
||||
})) {
|
||||
throw new Meteor.Error(403, "Access denied");
|
||||
}
|
||||
// Any allow returns true means proceed. Throw error if they all fail.
|
||||
if (await asyncEvery(self._validators.removeAsync.allow, async (validator) => {
|
||||
if (await asyncEvery(self._validators.remove.allow, async (validator) => {
|
||||
const result = validator(userId, transformDoc(validator, doc));
|
||||
return !(Meteor._isPromise(result) ? await result : result);
|
||||
})) {
|
||||
@@ -595,43 +470,6 @@ CollectionPrototype._validatedRemoveAsync = async function(userId, selector) {
|
||||
return self._collection.removeAsync.call(self._collection, selector);
|
||||
};
|
||||
|
||||
CollectionPrototype._validatedRemove = function(userId, selector) {
|
||||
const self = this;
|
||||
|
||||
const findOptions = {transform: null};
|
||||
if (!self._validators.fetchAllFields) {
|
||||
findOptions.fields = {};
|
||||
self._validators.fetch.forEach((fieldName) => {
|
||||
findOptions.fields[fieldName] = 1;
|
||||
});
|
||||
}
|
||||
|
||||
const doc = self._collection.findOne(selector, findOptions);
|
||||
if (!doc)
|
||||
return 0;
|
||||
|
||||
// call user validators.
|
||||
// Any deny returns true means denied.
|
||||
if (self._validators.remove.deny.some((validator) => {
|
||||
return validator(userId, transformDoc(validator, doc));
|
||||
})) {
|
||||
throw new Meteor.Error(403, "Access denied");
|
||||
}
|
||||
// Any allow returns true means proceed. Throw error if they all fail.
|
||||
if (self._validators.remove.allow.every((validator) => {
|
||||
return !validator(userId, transformDoc(validator, doc));
|
||||
})) {
|
||||
throw new Meteor.Error(403, "Access denied");
|
||||
}
|
||||
|
||||
// Back when we supported arbitrary client-provided selectors, we actually
|
||||
// rewrote the selector to {_id: {$in: [ids that we found]}} before passing to
|
||||
// Mongo to avoid races, but since selector is guaranteed to already just be
|
||||
// an ID, we don't have to any more.
|
||||
|
||||
return self._collection.remove.call(self._collection, selector);
|
||||
};
|
||||
|
||||
CollectionPrototype._callMutatorMethodAsync = function _callMutatorMethodAsync(name, args, options = {}) {
|
||||
|
||||
// For two out of three mutator methods, the first argument is a selector
|
||||
@@ -711,6 +549,13 @@ function addValidator(collection, allowOrDeny, options) {
|
||||
Object.keys(options).forEach((key) => {
|
||||
if (!validKeysRegEx.test(key))
|
||||
throw new Error(allowOrDeny + ": Invalid key: " + key);
|
||||
|
||||
// TODO deprecated async config on future versions
|
||||
const isAsyncKey = key.includes('Async');
|
||||
if (isAsyncKey) {
|
||||
const syncKey = key.replace('Async', '');
|
||||
Meteor.deprecate(allowOrDeny + `: The "${key}" key is deprecated. Use "${syncKey}" instead.`);
|
||||
}
|
||||
});
|
||||
|
||||
collection._restricted = true;
|
||||
@@ -740,7 +585,9 @@ function addValidator(collection, allowOrDeny, options) {
|
||||
options.transform
|
||||
);
|
||||
}
|
||||
collection._validators[name][allowOrDeny].push(options[name]);
|
||||
const isAsyncName = name.includes('Async');
|
||||
const validatorSyncName = isAsyncName ? name.replace('Async', '') : name;
|
||||
collection._validators[validatorSyncName][allowOrDeny].push(options[name]);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
Package.describe({
|
||||
name: 'allow-deny',
|
||||
version: '2.0.0',
|
||||
version: '2.1.0',
|
||||
// Brief, one-line summary of the package.
|
||||
summary: 'Implements functionality for allow/deny and client-side db operations',
|
||||
// URL to the Git repository containing the source code for this package.
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
Package.describe({
|
||||
name: "babel-compiler",
|
||||
summary: "Parser/transpiler for ECMAScript 2015+ syntax",
|
||||
version: '7.11.1',
|
||||
version: '7.11.3',
|
||||
});
|
||||
|
||||
Npm.depends({
|
||||
'@meteorjs/babel': '7.20.0',
|
||||
'json5': '2.1.1',
|
||||
'semver': '7.3.8'
|
||||
'@meteorjs/babel': '7.20.1',
|
||||
'json5': '2.2.3',
|
||||
'semver': '7.6.3'
|
||||
});
|
||||
|
||||
Package.onUse(function (api) {
|
||||
|
||||
@@ -1,10 +0,0 @@
|
||||
{
|
||||
"lockfileVersion": 4,
|
||||
"dependencies": {
|
||||
"parse5": {
|
||||
"version": "6.0.1",
|
||||
"resolved": "https://registry.npmjs.org/parse5/-/parse5-6.0.1.tgz",
|
||||
"integrity": "sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw=="
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2,7 +2,7 @@ Package.describe({
|
||||
// These tests are in a separate package so that we can Npm.depend on
|
||||
// parse5, a html parsing library.
|
||||
summary: "Tests for the boilerplate-generator package",
|
||||
version: '1.5.2',
|
||||
version: '1.5.3',
|
||||
documentation: null
|
||||
});
|
||||
|
||||
@@ -13,7 +13,6 @@ Npm.depends({
|
||||
Package.onTest(function (api) {
|
||||
api.use('ecmascript');
|
||||
api.use([
|
||||
'underscore',
|
||||
'tinytest',
|
||||
'boilerplate-generator'
|
||||
], 'server');
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { parse, serialize } from 'parse5';
|
||||
import { generateHTMLForArch } from './test-lib';
|
||||
import { _ } from 'meteor/underscore';
|
||||
|
||||
Tinytest.addAsync(
|
||||
"boilerplate-generator-tests - web.browser - basic output",
|
||||
@@ -66,10 +65,6 @@ Tinytest.addAsync(
|
||||
async function (test) {
|
||||
const newHtml = await generateHTMLForArch("web.browser", false);
|
||||
|
||||
_.templateSettings = {
|
||||
interpolate: /\{\{(.+?)\}\}/g
|
||||
};
|
||||
|
||||
test.matches(newHtml, /foo="foobar"/);
|
||||
test.matches(newHtml, /<link[^<>]*href="[^<>]*bootstrap[^<>]*">/);
|
||||
test.matches(newHtml, /<script[^<>]*src="[^<>]*templating[^<>]*">/);
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { parse, serialize } from 'parse5';
|
||||
import { generateHTMLForArch } from './test-lib';
|
||||
import { _ } from 'meteor/underscore';
|
||||
|
||||
Tinytest.addAsync(
|
||||
"boilerplate-generator-tests - web.cordova - basic output",
|
||||
@@ -60,9 +59,7 @@ Tinytest.addAsync(
|
||||
async function (test) {
|
||||
const newHtml = await generateHTMLForArch('web.cordova', false);
|
||||
|
||||
_.templateSettings = {
|
||||
interpolate: /\{\{(.+?)\}\}/g
|
||||
};
|
||||
|
||||
test.matches(newHtml, /<link[^<>]*href="[^<>]*bootstrap[^<>]*">/);
|
||||
test.matches(newHtml, /<script[^<>]*src="[^<>]*templating[^<>]*">/);
|
||||
test.matches(newHtml, /<script>var a/);
|
||||
|
||||
@@ -1,45 +0,0 @@
|
||||
{
|
||||
"lockfileVersion": 4,
|
||||
"dependencies": {
|
||||
"bluebird": {
|
||||
"version": "2.11.0",
|
||||
"resolved": "https://registry.npmjs.org/bluebird/-/bluebird-2.11.0.tgz",
|
||||
"integrity": "sha512-UfFSr22dmHPQqPP9XWHRhq+gWnHCYguQGkXQlbyPtW5qTnhFWA8/iXg765tH0cAjy7l/zPJ1aBTO0g5XgA7kvQ=="
|
||||
},
|
||||
"combined-stream2": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/combined-stream2/-/combined-stream2-1.1.2.tgz",
|
||||
"integrity": "sha512-sVqUHJmbdVm+HZWy4l34BPLczxI4fltN4Bm2vcvASsqBIXW4xFb4TRkwM8bw/UUXK9/OdHdAwi2cRYVEKrxzbg=="
|
||||
},
|
||||
"debug": {
|
||||
"version": "2.6.9",
|
||||
"resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
|
||||
"integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA=="
|
||||
},
|
||||
"lodash._reinterpolate": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/lodash._reinterpolate/-/lodash._reinterpolate-3.0.0.tgz",
|
||||
"integrity": "sha512-xYHt68QRoYGjeeM/XOE1uJtvXQAgvszfBhjV4yvsQH0u2i9I6cI6c6/eG4Hh3UAOVn0y/xAXwmTzEay49Q//HA=="
|
||||
},
|
||||
"lodash.template": {
|
||||
"version": "4.5.0",
|
||||
"resolved": "https://registry.npmjs.org/lodash.template/-/lodash.template-4.5.0.tgz",
|
||||
"integrity": "sha512-84vYFxIkmidUiFxidA/KjjH9pAycqW+h980j7Fuz5qxRtO9pgB7MDFTdys1N7A5mcucRiDyEq4fusljItR1T/A=="
|
||||
},
|
||||
"lodash.templatesettings": {
|
||||
"version": "4.2.0",
|
||||
"resolved": "https://registry.npmjs.org/lodash.templatesettings/-/lodash.templatesettings-4.2.0.tgz",
|
||||
"integrity": "sha512-stgLz+i3Aa9mZgnjr/O+v9ruKZsPsndy7qPZOchbqk2cnTU1ZaldKK+v7m54WoKIyxiuMZTKT2H81F8BeAc3ZQ=="
|
||||
},
|
||||
"ms": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
|
||||
"integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="
|
||||
},
|
||||
"stream-length": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/stream-length/-/stream-length-1.0.2.tgz",
|
||||
"integrity": "sha512-aI+qKFiwoDV4rsXiS7WRoCt+v2RX1nUj17+KJC5r2gfh5xoSJIfP6Y3Do/HtvesFcTSWthIuJ3l1cvKQY/+nZg=="
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,45 +0,0 @@
|
||||
{
|
||||
"lockfileVersion": 4,
|
||||
"dependencies": {
|
||||
"lodash.groupby": {
|
||||
"version": "4.6.0",
|
||||
"resolved": "https://registry.npmjs.org/lodash.groupby/-/lodash.groupby-4.6.0.tgz",
|
||||
"integrity": "sha512-5dcWxm23+VAoz+awKmBaiBvzox8+RqMgFhi7UvX9DHZr2HdxHXM/Wrf8cfKpsW37RNrvtPn6hSwNqurSILbmJw=="
|
||||
},
|
||||
"lodash.has": {
|
||||
"version": "4.5.2",
|
||||
"resolved": "https://registry.npmjs.org/lodash.has/-/lodash.has-4.5.2.tgz",
|
||||
"integrity": "sha512-rnYUdIo6xRCJnQmbVFEwcxF144erlD+M3YcJUVesflU9paQaE8p+fJDcIQrlMYbxoANFL+AB9hZrzSBBk5PL+g=="
|
||||
},
|
||||
"lodash.isempty": {
|
||||
"version": "4.4.0",
|
||||
"resolved": "https://registry.npmjs.org/lodash.isempty/-/lodash.isempty-4.4.0.tgz",
|
||||
"integrity": "sha512-oKMuF3xEeqDltrGMfDxAPGIVMSSRv8tbRSODbrs4KGsRRLEhrW8N8Rd4DRgB2+621hY8A8XwwrTVhXWpxFvMzg=="
|
||||
},
|
||||
"lodash.isequal": {
|
||||
"version": "4.5.0",
|
||||
"resolved": "https://registry.npmjs.org/lodash.isequal/-/lodash.isequal-4.5.0.tgz",
|
||||
"integrity": "sha512-pDo3lu8Jhfjqls6GkMgpahsF9kCyayhgykjyLMNFTKWrpVdAQtYyB4muAMWozBB4ig/dtWAmsMxLEI8wuz+DYQ=="
|
||||
},
|
||||
"lodash.isobject": {
|
||||
"version": "3.0.2",
|
||||
"resolved": "https://registry.npmjs.org/lodash.isobject/-/lodash.isobject-3.0.2.tgz",
|
||||
"integrity": "sha512-3/Qptq2vr7WeJbB4KHUSKlq8Pl7ASXi3UG6CMbBm8WRtXi8+GHm7mKaU3urfpSEzWe2wCIChs6/sdocUsTKJiA=="
|
||||
},
|
||||
"lodash.isstring": {
|
||||
"version": "4.0.1",
|
||||
"resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz",
|
||||
"integrity": "sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw=="
|
||||
},
|
||||
"lodash.memoize": {
|
||||
"version": "4.1.2",
|
||||
"resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz",
|
||||
"integrity": "sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag=="
|
||||
},
|
||||
"lodash.zip": {
|
||||
"version": "4.2.0",
|
||||
"resolved": "https://registry.npmjs.org/lodash.zip/-/lodash.zip-4.2.0.tgz",
|
||||
"integrity": "sha512-C7IOaBBK/0gMORRBd8OETNx3kmOkgIWIPvyDpZSCTwUrpYmgZwJkjZeOD8ww4xbOUOs4/attY+pciKvadNfFbg=="
|
||||
}
|
||||
}
|
||||
}
|
||||
30
packages/ddp-client/.npm/package/npm-shrinkwrap.json
generated
30
packages/ddp-client/.npm/package/npm-shrinkwrap.json
generated
@@ -1,30 +0,0 @@
|
||||
{
|
||||
"lockfileVersion": 4,
|
||||
"dependencies": {
|
||||
"@sinonjs/commons": {
|
||||
"version": "1.8.6",
|
||||
"resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-1.8.6.tgz",
|
||||
"integrity": "sha512-Ky+XkAkqPZSm3NLBeUng77EBQl3cmeJhITaGHdYH8kjVB+aun3S4XBRti2zt17mtt0mIUDiNxYeoJm6drVvBJQ=="
|
||||
},
|
||||
"@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=="
|
||||
},
|
||||
"lodash.has": {
|
||||
"version": "4.5.2",
|
||||
"resolved": "https://registry.npmjs.org/lodash.has/-/lodash.has-4.5.2.tgz",
|
||||
"integrity": "sha512-rnYUdIo6xRCJnQmbVFEwcxF144erlD+M3YcJUVesflU9paQaE8p+fJDcIQrlMYbxoANFL+AB9hZrzSBBk5PL+g=="
|
||||
},
|
||||
"lodash.identity": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/lodash.identity/-/lodash.identity-3.0.0.tgz",
|
||||
"integrity": "sha512-AupTIzdLQxJS5wIYUQlgGyk2XRTfGXA+MCghDHqZk0pzUNYvd3EESS6dkChNauNYVIutcb0dfHw1ri9Q1yPV8Q=="
|
||||
},
|
||||
"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=="
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
import { DDP } from '../common/namespace.js';
|
||||
import { Meteor } from 'meteor/meteor';
|
||||
import { loadAsyncStubHelpers } from "./queueStubsHelpers";
|
||||
import { loadAsyncStubHelpers } from "./queue_stub_helpers";
|
||||
|
||||
// Meteor.refresh can be called on the client (if you're in common code) but it
|
||||
// only has an effect on the server.
|
||||
|
||||
202
packages/ddp-client/common/connection_stream_handlers.js
Normal file
202
packages/ddp-client/common/connection_stream_handlers.js
Normal file
@@ -0,0 +1,202 @@
|
||||
import { DDPCommon } from 'meteor/ddp-common';
|
||||
import { Meteor } from 'meteor/meteor';
|
||||
|
||||
export class ConnectionStreamHandlers {
|
||||
constructor(connection) {
|
||||
this._connection = connection;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles incoming raw messages from the DDP stream
|
||||
* @param {String} raw_msg The raw message received from the stream
|
||||
*/
|
||||
async onMessage(raw_msg) {
|
||||
let msg;
|
||||
try {
|
||||
msg = DDPCommon.parseDDP(raw_msg);
|
||||
} catch (e) {
|
||||
Meteor._debug('Exception while parsing DDP', e);
|
||||
return;
|
||||
}
|
||||
|
||||
// Any message counts as receiving a pong, as it demonstrates that
|
||||
// the server is still alive.
|
||||
if (this._connection._heartbeat) {
|
||||
this._connection._heartbeat.messageReceived();
|
||||
}
|
||||
|
||||
if (msg === null || !msg.msg) {
|
||||
if(!msg || !msg.testMessageOnConnect) {
|
||||
if (Object.keys(msg).length === 1 && msg.server_id) return;
|
||||
Meteor._debug('discarding invalid livedata message', msg);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Important: This was missing from previous version
|
||||
// We need to set the current version before routing the message
|
||||
if (msg.msg === 'connected') {
|
||||
this._connection._version = this._connection._versionSuggestion;
|
||||
}
|
||||
|
||||
await this._routeMessage(msg);
|
||||
}
|
||||
|
||||
/**
|
||||
* Routes messages to their appropriate handlers based on message type
|
||||
* @private
|
||||
* @param {Object} msg The parsed DDP message
|
||||
*/
|
||||
async _routeMessage(msg) {
|
||||
switch (msg.msg) {
|
||||
case 'connected':
|
||||
await this._connection._livedata_connected(msg);
|
||||
this._connection.options.onConnected();
|
||||
break;
|
||||
|
||||
case 'failed':
|
||||
await this._handleFailedMessage(msg);
|
||||
break;
|
||||
|
||||
case 'ping':
|
||||
if (this._connection.options.respondToPings) {
|
||||
this._connection._send({ msg: 'pong', id: msg.id });
|
||||
}
|
||||
break;
|
||||
|
||||
case 'pong':
|
||||
// noop, as we assume everything's a pong
|
||||
break;
|
||||
|
||||
case 'added':
|
||||
case 'changed':
|
||||
case 'removed':
|
||||
case 'ready':
|
||||
case 'updated':
|
||||
await this._connection._livedata_data(msg);
|
||||
break;
|
||||
|
||||
case 'nosub':
|
||||
await this._connection._livedata_nosub(msg);
|
||||
break;
|
||||
|
||||
case 'result':
|
||||
await this._connection._livedata_result(msg);
|
||||
break;
|
||||
|
||||
case 'error':
|
||||
this._connection._livedata_error(msg);
|
||||
break;
|
||||
|
||||
default:
|
||||
Meteor._debug('discarding unknown livedata message type', msg);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles failed connection messages
|
||||
* @private
|
||||
* @param {Object} msg The failed message object
|
||||
*/
|
||||
_handleFailedMessage(msg) {
|
||||
if (this._connection._supportedDDPVersions.indexOf(msg.version) >= 0) {
|
||||
this._connection._versionSuggestion = msg.version;
|
||||
this._connection._stream.reconnect({ _force: true });
|
||||
} else {
|
||||
const description =
|
||||
'DDP version negotiation failed; server requested version ' +
|
||||
msg.version;
|
||||
this._connection._stream.disconnect({ _permanent: true, _error: description });
|
||||
this._connection.options.onDDPVersionNegotiationFailure(description);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles connection reset events
|
||||
*/
|
||||
onReset() {
|
||||
// Reset is called even on the first connection, so this is
|
||||
// the only place we send this message.
|
||||
const msg = this._buildConnectMessage();
|
||||
this._connection._send(msg);
|
||||
|
||||
// Mark non-retry calls as failed and handle outstanding methods
|
||||
this._handleOutstandingMethodsOnReset();
|
||||
|
||||
// Now, to minimize setup latency, go ahead and blast out all of
|
||||
// our pending methods ands subscriptions before we've even taken
|
||||
// the necessary RTT to know if we successfully reconnected.
|
||||
this._connection._callOnReconnectAndSendAppropriateOutstandingMethods();
|
||||
this._resendSubscriptions();
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds the initial connect message
|
||||
* @private
|
||||
* @returns {Object} The connect message object
|
||||
*/
|
||||
_buildConnectMessage() {
|
||||
const msg = { msg: 'connect' };
|
||||
if (this._connection._lastSessionId) {
|
||||
msg.session = this._connection._lastSessionId;
|
||||
}
|
||||
msg.version = this._connection._versionSuggestion || this._connection._supportedDDPVersions[0];
|
||||
this._connection._versionSuggestion = msg.version;
|
||||
msg.support = this._connection._supportedDDPVersions;
|
||||
return msg;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles outstanding methods during a reset
|
||||
* @private
|
||||
*/
|
||||
_handleOutstandingMethodsOnReset() {
|
||||
const blocks = this._connection._outstandingMethodBlocks;
|
||||
if (blocks.length === 0) return;
|
||||
|
||||
const currentMethodBlock = blocks[0].methods;
|
||||
blocks[0].methods = currentMethodBlock.filter(
|
||||
methodInvoker => {
|
||||
// Methods with 'noRetry' option set are not allowed to re-send after
|
||||
// recovering dropped connection.
|
||||
if (methodInvoker.sentMessage && methodInvoker.noRetry) {
|
||||
methodInvoker.receiveResult(
|
||||
new Meteor.Error(
|
||||
'invocation-failed',
|
||||
'Method invocation might have failed due to dropped connection. ' +
|
||||
'Failing because `noRetry` option was passed to Meteor.apply.'
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
// Only keep a method if it wasn't sent or it's allowed to retry.
|
||||
return !(methodInvoker.sentMessage && methodInvoker.noRetry);
|
||||
}
|
||||
);
|
||||
|
||||
// Clear empty blocks
|
||||
if (blocks.length > 0 && blocks[0].methods.length === 0) {
|
||||
blocks.shift();
|
||||
}
|
||||
|
||||
// Reset all method invokers as unsent
|
||||
Object.values(this._connection._methodInvokers).forEach(invoker => {
|
||||
invoker.sentMessage = false;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Resends all active subscriptions
|
||||
* @private
|
||||
*/
|
||||
_resendSubscriptions() {
|
||||
Object.entries(this._connection._subscriptions).forEach(([id, sub]) => {
|
||||
this._connection._sendQueued({
|
||||
msg: 'sub',
|
||||
id: id,
|
||||
name: sub.name,
|
||||
params: sub.params
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
206
packages/ddp-client/common/document_processors.js
Normal file
206
packages/ddp-client/common/document_processors.js
Normal file
@@ -0,0 +1,206 @@
|
||||
import { MongoID } from 'meteor/mongo-id';
|
||||
import { DiffSequence } from 'meteor/diff-sequence';
|
||||
import { hasOwn } from "meteor/ddp-common/utils";
|
||||
import { isEmpty } from "meteor/ddp-common/utils";
|
||||
|
||||
export class DocumentProcessors {
|
||||
constructor(connection) {
|
||||
this._connection = connection;
|
||||
}
|
||||
|
||||
/**
|
||||
* @summary Process an 'added' message from the server
|
||||
* @param {Object} msg The added message
|
||||
* @param {Object} updates The updates accumulator
|
||||
*/
|
||||
async _process_added(msg, updates) {
|
||||
const self = this._connection;
|
||||
const id = MongoID.idParse(msg.id);
|
||||
const serverDoc = self._getServerDoc(msg.collection, id);
|
||||
|
||||
if (serverDoc) {
|
||||
// Some outstanding stub wrote here.
|
||||
const isExisting = serverDoc.document !== undefined;
|
||||
|
||||
serverDoc.document = msg.fields || Object.create(null);
|
||||
serverDoc.document._id = id;
|
||||
|
||||
if (self._resetStores) {
|
||||
// During reconnect the server is sending adds for existing ids.
|
||||
// Always push an update so that document stays in the store after
|
||||
// reset. Use current version of the document for this update, so
|
||||
// that stub-written values are preserved.
|
||||
const currentDoc = await self._stores[msg.collection].getDoc(msg.id);
|
||||
if (currentDoc !== undefined) msg.fields = currentDoc;
|
||||
|
||||
self._pushUpdate(updates, msg.collection, msg);
|
||||
} else if (isExisting) {
|
||||
throw new Error('Server sent add for existing id: ' + msg.id);
|
||||
}
|
||||
} else {
|
||||
self._pushUpdate(updates, msg.collection, msg);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @summary Process a 'changed' message from the server
|
||||
* @param {Object} msg The changed message
|
||||
* @param {Object} updates The updates accumulator
|
||||
*/
|
||||
_process_changed(msg, updates) {
|
||||
const self = this._connection;
|
||||
const serverDoc = self._getServerDoc(msg.collection, MongoID.idParse(msg.id));
|
||||
|
||||
if (serverDoc) {
|
||||
if (serverDoc.document === undefined) {
|
||||
throw new Error('Server sent changed for nonexisting id: ' + msg.id);
|
||||
}
|
||||
DiffSequence.applyChanges(serverDoc.document, msg.fields);
|
||||
} else {
|
||||
self._pushUpdate(updates, msg.collection, msg);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @summary Process a 'removed' message from the server
|
||||
* @param {Object} msg The removed message
|
||||
* @param {Object} updates The updates accumulator
|
||||
*/
|
||||
_process_removed(msg, updates) {
|
||||
const self = this._connection;
|
||||
const serverDoc = self._getServerDoc(msg.collection, MongoID.idParse(msg.id));
|
||||
|
||||
if (serverDoc) {
|
||||
// Some outstanding stub wrote here.
|
||||
if (serverDoc.document === undefined) {
|
||||
throw new Error('Server sent removed for nonexisting id:' + msg.id);
|
||||
}
|
||||
serverDoc.document = undefined;
|
||||
} else {
|
||||
self._pushUpdate(updates, msg.collection, {
|
||||
msg: 'removed',
|
||||
collection: msg.collection,
|
||||
id: msg.id
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @summary Process a 'ready' message from the server
|
||||
* @param {Object} msg The ready message
|
||||
* @param {Object} updates The updates accumulator
|
||||
*/
|
||||
_process_ready(msg, updates) {
|
||||
const self = this._connection;
|
||||
|
||||
// Process "sub ready" messages. "sub ready" messages don't take effect
|
||||
// until all current server documents have been flushed to the local
|
||||
// database. We can use a write fence to implement this.
|
||||
msg.subs.forEach((subId) => {
|
||||
self._runWhenAllServerDocsAreFlushed(() => {
|
||||
const subRecord = self._subscriptions[subId];
|
||||
// Did we already unsubscribe?
|
||||
if (!subRecord) return;
|
||||
// Did we already receive a ready message? (Oops!)
|
||||
if (subRecord.ready) return;
|
||||
subRecord.ready = true;
|
||||
subRecord.readyCallback && subRecord.readyCallback();
|
||||
subRecord.readyDeps.changed();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @summary Process an 'updated' message from the server
|
||||
* @param {Object} msg The updated message
|
||||
* @param {Object} updates The updates accumulator
|
||||
*/
|
||||
_process_updated(msg, updates) {
|
||||
const self = this._connection;
|
||||
// Process "method done" messages.
|
||||
msg.methods.forEach((methodId) => {
|
||||
const docs = self._documentsWrittenByStub[methodId] || {};
|
||||
Object.values(docs).forEach((written) => {
|
||||
const serverDoc = self._getServerDoc(written.collection, written.id);
|
||||
if (!serverDoc) {
|
||||
throw new Error('Lost serverDoc for ' + JSON.stringify(written));
|
||||
}
|
||||
if (!serverDoc.writtenByStubs[methodId]) {
|
||||
throw new Error(
|
||||
'Doc ' +
|
||||
JSON.stringify(written) +
|
||||
' not written by method ' +
|
||||
methodId
|
||||
);
|
||||
}
|
||||
delete serverDoc.writtenByStubs[methodId];
|
||||
if (isEmpty(serverDoc.writtenByStubs)) {
|
||||
// All methods whose stubs wrote this method have completed! We can
|
||||
// now copy the saved document to the database (reverting the stub's
|
||||
// change if the server did not write to this object, or applying the
|
||||
// server's writes if it did).
|
||||
|
||||
// This is a fake ddp 'replace' message. It's just for talking
|
||||
// between livedata connections and minimongo. (We have to stringify
|
||||
// the ID because it's supposed to look like a wire message.)
|
||||
self._pushUpdate(updates, written.collection, {
|
||||
msg: 'replace',
|
||||
id: MongoID.idStringify(written.id),
|
||||
replace: serverDoc.document
|
||||
});
|
||||
// Call all flush callbacks.
|
||||
serverDoc.flushCallbacks.forEach((c) => {
|
||||
c();
|
||||
});
|
||||
|
||||
// Delete this completed serverDocument. Don't bother to GC empty
|
||||
// IdMaps inside self._serverDocuments, since there probably aren't
|
||||
// many collections and they'll be written repeatedly.
|
||||
self._serverDocuments[written.collection].remove(written.id);
|
||||
}
|
||||
});
|
||||
delete self._documentsWrittenByStub[methodId];
|
||||
|
||||
// We want to call the data-written callback, but we can't do so until all
|
||||
// currently buffered messages are flushed.
|
||||
const callbackInvoker = self._methodInvokers[methodId];
|
||||
if (!callbackInvoker) {
|
||||
throw new Error('No callback invoker for method ' + methodId);
|
||||
}
|
||||
|
||||
self._runWhenAllServerDocsAreFlushed(
|
||||
(...args) => callbackInvoker.dataVisible(...args)
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @summary Push an update to the buffer
|
||||
* @private
|
||||
* @param {Object} updates The updates accumulator
|
||||
* @param {String} collection The collection name
|
||||
* @param {Object} msg The update message
|
||||
*/
|
||||
_pushUpdate(updates, collection, msg) {
|
||||
if (!hasOwn.call(updates, collection)) {
|
||||
updates[collection] = [];
|
||||
}
|
||||
updates[collection].push(msg);
|
||||
}
|
||||
|
||||
/**
|
||||
* @summary Get a server document by collection and id
|
||||
* @private
|
||||
* @param {String} collection The collection name
|
||||
* @param {String} id The document id
|
||||
* @returns {Object|null} The server document or null
|
||||
*/
|
||||
_getServerDoc(collection, id) {
|
||||
const self = this._connection;
|
||||
if (!hasOwn.call(self._serverDocuments, collection)) {
|
||||
return null;
|
||||
}
|
||||
const serverDocsForCollection = self._serverDocuments[collection];
|
||||
return serverDocsForCollection.get(id) || null;
|
||||
}
|
||||
}
|
||||
@@ -5,20 +5,18 @@ import { EJSON } from 'meteor/ejson';
|
||||
import { Random } from 'meteor/random';
|
||||
import { MongoID } from 'meteor/mongo-id';
|
||||
import { DDP } from './namespace.js';
|
||||
import MethodInvoker from './MethodInvoker.js';
|
||||
import { MethodInvoker } from './method_invoker';
|
||||
import {
|
||||
hasOwn,
|
||||
slice,
|
||||
keys,
|
||||
isEmpty,
|
||||
last,
|
||||
} from "meteor/ddp-common/utils.js";
|
||||
|
||||
class MongoIDMap extends IdMap {
|
||||
constructor() {
|
||||
super(MongoID.idStringify, MongoID.idParse);
|
||||
}
|
||||
}
|
||||
} from "meteor/ddp-common/utils";
|
||||
import { ConnectionStreamHandlers } from './connection_stream_handlers';
|
||||
import { MongoIDMap } from './mongo_id_map';
|
||||
import { MessageProcessors } from './message_processors';
|
||||
import { DocumentProcessors } from './document_processors';
|
||||
|
||||
// @param url {String|Object} URL to Meteor app,
|
||||
// or an object as a test hook (see code)
|
||||
@@ -202,12 +200,6 @@ export class Connection {
|
||||
self._updatesForUnknownStores = {};
|
||||
// if we're blocking a migration, the retry func
|
||||
self._retryMigrate = null;
|
||||
|
||||
self.__flushBufferedWrites = Meteor.bindEnvironment(
|
||||
self._flushBufferedWrites,
|
||||
'flushing DDP buffered writes',
|
||||
self
|
||||
);
|
||||
// Collection name -> array of messages.
|
||||
self._bufferedWrites = {};
|
||||
// When current buffer of updates must be flushed at, in ms timestamp.
|
||||
@@ -249,34 +241,63 @@ export class Connection {
|
||||
});
|
||||
}
|
||||
|
||||
this._streamHandlers = new ConnectionStreamHandlers(this);
|
||||
|
||||
const onDisconnect = () => {
|
||||
if (self._heartbeat) {
|
||||
self._heartbeat.stop();
|
||||
self._heartbeat = null;
|
||||
if (this._heartbeat) {
|
||||
this._heartbeat.stop();
|
||||
this._heartbeat = null;
|
||||
}
|
||||
};
|
||||
|
||||
if (Meteor.isServer) {
|
||||
self._stream.on(
|
||||
this._stream.on(
|
||||
'message',
|
||||
Meteor.bindEnvironment(
|
||||
this.onMessage.bind(this),
|
||||
msg => this._streamHandlers.onMessage(msg),
|
||||
'handling DDP message'
|
||||
)
|
||||
);
|
||||
self._stream.on(
|
||||
this._stream.on(
|
||||
'reset',
|
||||
Meteor.bindEnvironment(this.onReset.bind(this), 'handling DDP reset')
|
||||
Meteor.bindEnvironment(
|
||||
() => this._streamHandlers.onReset(),
|
||||
'handling DDP reset'
|
||||
)
|
||||
);
|
||||
self._stream.on(
|
||||
this._stream.on(
|
||||
'disconnect',
|
||||
Meteor.bindEnvironment(onDisconnect, 'handling DDP disconnect')
|
||||
);
|
||||
} else {
|
||||
self._stream.on('message', this.onMessage.bind(this));
|
||||
self._stream.on('reset', this.onReset.bind(this));
|
||||
self._stream.on('disconnect', onDisconnect);
|
||||
this._stream.on('message', msg => this._streamHandlers.onMessage(msg));
|
||||
this._stream.on('reset', () => this._streamHandlers.onReset());
|
||||
this._stream.on('disconnect', onDisconnect);
|
||||
}
|
||||
|
||||
this._messageProcessors = new MessageProcessors(this);
|
||||
|
||||
// Expose message processor methods to maintain backward compatibility
|
||||
this._livedata_connected = (msg) => this._messageProcessors._livedata_connected(msg);
|
||||
this._livedata_data = (msg) => this._messageProcessors._livedata_data(msg);
|
||||
this._livedata_nosub = (msg) => this._messageProcessors._livedata_nosub(msg);
|
||||
this._livedata_result = (msg) => this._messageProcessors._livedata_result(msg);
|
||||
this._livedata_error = (msg) => this._messageProcessors._livedata_error(msg);
|
||||
|
||||
this._documentProcessors = new DocumentProcessors(this);
|
||||
|
||||
// Expose document processor methods to maintain backward compatibility
|
||||
this._process_added = (msg, updates) => this._documentProcessors._process_added(msg, updates);
|
||||
this._process_changed = (msg, updates) => this._documentProcessors._process_changed(msg, updates);
|
||||
this._process_removed = (msg, updates) => this._documentProcessors._process_removed(msg, updates);
|
||||
this._process_ready = (msg, updates) => this._documentProcessors._process_ready(msg, updates);
|
||||
this._process_updated = (msg, updates) => this._documentProcessors._process_updated(msg, updates);
|
||||
|
||||
// Also expose utility methods used by other parts of the system
|
||||
this._pushUpdate = (updates, collection, msg) =>
|
||||
this._documentProcessors._pushUpdate(updates, collection, msg);
|
||||
this._getServerDoc = (collection, id) =>
|
||||
this._documentProcessors._getServerDoc(collection, id);
|
||||
}
|
||||
|
||||
// 'name' is the name of the data on the wire that should go in the
|
||||
@@ -941,7 +962,7 @@ export class Connection {
|
||||
// documents.
|
||||
_saveOriginals() {
|
||||
if (! this._waitingForQuiescence()) {
|
||||
this._flushBufferedWritesClient();
|
||||
this._flushBufferedWrites();
|
||||
}
|
||||
|
||||
Object.values(this._stores).forEach((store) => {
|
||||
@@ -1099,121 +1120,6 @@ export class Connection {
|
||||
return Object.values(invokers).some((invoker) => !!invoker.sentMessage);
|
||||
}
|
||||
|
||||
async _livedata_connected(msg) {
|
||||
const self = this;
|
||||
|
||||
if (self._version !== 'pre1' && self._heartbeatInterval !== 0) {
|
||||
self._heartbeat = new DDPCommon.Heartbeat({
|
||||
heartbeatInterval: self._heartbeatInterval,
|
||||
heartbeatTimeout: self._heartbeatTimeout,
|
||||
onTimeout() {
|
||||
self._lostConnection(
|
||||
new DDP.ConnectionError('DDP heartbeat timed out')
|
||||
);
|
||||
},
|
||||
sendPing() {
|
||||
self._send({ msg: 'ping' });
|
||||
}
|
||||
});
|
||||
self._heartbeat.start();
|
||||
}
|
||||
|
||||
// If this is a reconnect, we'll have to reset all stores.
|
||||
if (self._lastSessionId) self._resetStores = true;
|
||||
|
||||
let reconnectedToPreviousSession;
|
||||
if (typeof msg.session === 'string') {
|
||||
reconnectedToPreviousSession = self._lastSessionId === msg.session;
|
||||
self._lastSessionId = msg.session;
|
||||
}
|
||||
|
||||
if (reconnectedToPreviousSession) {
|
||||
// Successful reconnection -- pick up where we left off. Note that right
|
||||
// now, this never happens: the server never connects us to a previous
|
||||
// session, because DDP doesn't provide enough data for the server to know
|
||||
// what messages the client has processed. We need to improve DDP to make
|
||||
// this possible, at which point we'll probably need more code here.
|
||||
return;
|
||||
}
|
||||
|
||||
// Server doesn't have our data any more. Re-sync a new session.
|
||||
|
||||
// Forget about messages we were buffering for unknown collections. They'll
|
||||
// be resent if still relevant.
|
||||
self._updatesForUnknownStores = Object.create(null);
|
||||
|
||||
if (self._resetStores) {
|
||||
// Forget about the effects of stubs. We'll be resetting all collections
|
||||
// anyway.
|
||||
self._documentsWrittenByStub = Object.create(null);
|
||||
self._serverDocuments = Object.create(null);
|
||||
}
|
||||
|
||||
// Clear _afterUpdateCallbacks.
|
||||
self._afterUpdateCallbacks = [];
|
||||
|
||||
// Mark all named subscriptions which are ready (ie, we already called the
|
||||
// ready callback) as needing to be revived.
|
||||
// XXX We should also block reconnect quiescence until unnamed subscriptions
|
||||
// (eg, autopublish) are done re-publishing to avoid flicker!
|
||||
self._subsBeingRevived = Object.create(null);
|
||||
Object.entries(self._subscriptions).forEach(([id, sub]) => {
|
||||
if (sub.ready) {
|
||||
self._subsBeingRevived[id] = true;
|
||||
}
|
||||
});
|
||||
|
||||
// Arrange for "half-finished" methods to have their callbacks run, and
|
||||
// track methods that were sent on this connection so that we don't
|
||||
// quiesce until they are all done.
|
||||
//
|
||||
// Start by clearing _methodsBlockingQuiescence: methods sent before
|
||||
// reconnect don't matter, and any "wait" methods sent on the new connection
|
||||
// that we drop here will be restored by the loop below.
|
||||
self._methodsBlockingQuiescence = Object.create(null);
|
||||
if (self._resetStores) {
|
||||
const invokers = self._methodInvokers;
|
||||
keys(invokers).forEach(id => {
|
||||
const invoker = invokers[id];
|
||||
if (invoker.gotResult()) {
|
||||
// This method already got its result, but it didn't call its callback
|
||||
// because its data didn't become visible. We did not resend the
|
||||
// method RPC. We'll call its callback when we get a full quiesce,
|
||||
// since that's as close as we'll get to "data must be visible".
|
||||
self._afterUpdateCallbacks.push(
|
||||
(...args) => invoker.dataVisible(...args)
|
||||
);
|
||||
} else if (invoker.sentMessage) {
|
||||
// This method has been sent on this connection (maybe as a resend
|
||||
// from the last connection, maybe from onReconnect, maybe just very
|
||||
// quickly before processing the connected message).
|
||||
//
|
||||
// We don't need to do anything special to ensure its callbacks get
|
||||
// called, but we'll count it as a method which is preventing
|
||||
// reconnect quiescence. (eg, it might be a login method that was run
|
||||
// from onReconnect, and we don't want to see flicker by seeing a
|
||||
// logged-out state.)
|
||||
self._methodsBlockingQuiescence[invoker.methodId] = true;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
self._messagesBufferedUntilQuiescence = [];
|
||||
|
||||
// If we're not waiting on any methods or subs, we can reset the stores and
|
||||
// call the callbacks immediately.
|
||||
if (! self._waitingForQuiescence()) {
|
||||
if (self._resetStores) {
|
||||
for (const store of Object.values(self._stores)) {
|
||||
await store.beginUpdate(0, true);
|
||||
await store.endUpdate();
|
||||
}
|
||||
self._resetStores = false;
|
||||
}
|
||||
self._runAfterUpdateCallbacks();
|
||||
}
|
||||
}
|
||||
|
||||
async _processOneDataMessage(msg, updates) {
|
||||
const messageType = msg.msg;
|
||||
|
||||
@@ -1235,87 +1141,6 @@ export class Connection {
|
||||
}
|
||||
}
|
||||
|
||||
async _livedata_data(msg) {
|
||||
const self = this;
|
||||
|
||||
if (self._waitingForQuiescence()) {
|
||||
self._messagesBufferedUntilQuiescence.push(msg);
|
||||
|
||||
if (msg.msg === 'nosub') {
|
||||
delete self._subsBeingRevived[msg.id];
|
||||
}
|
||||
|
||||
if (msg.subs) {
|
||||
msg.subs.forEach(subId => {
|
||||
delete self._subsBeingRevived[subId];
|
||||
});
|
||||
}
|
||||
|
||||
if (msg.methods) {
|
||||
msg.methods.forEach(methodId => {
|
||||
delete self._methodsBlockingQuiescence[methodId];
|
||||
});
|
||||
}
|
||||
|
||||
if (self._waitingForQuiescence()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// No methods or subs are blocking quiescence!
|
||||
// We'll now process and all of our buffered messages, reset all stores,
|
||||
// and apply them all at once.
|
||||
|
||||
const bufferedMessages = self._messagesBufferedUntilQuiescence;
|
||||
for (const bufferedMessage of Object.values(bufferedMessages)) {
|
||||
await self._processOneDataMessage(
|
||||
bufferedMessage,
|
||||
self._bufferedWrites
|
||||
);
|
||||
}
|
||||
|
||||
self._messagesBufferedUntilQuiescence = [];
|
||||
|
||||
} else {
|
||||
await self._processOneDataMessage(msg, self._bufferedWrites);
|
||||
}
|
||||
|
||||
// Immediately flush writes when:
|
||||
// 1. Buffering is disabled. Or;
|
||||
// 2. any non-(added/changed/removed) message arrives.
|
||||
const standardWrite =
|
||||
msg.msg === "added" ||
|
||||
msg.msg === "changed" ||
|
||||
msg.msg === "removed";
|
||||
|
||||
if (self._bufferedWritesInterval === 0 || ! standardWrite) {
|
||||
await self._flushBufferedWrites();
|
||||
return;
|
||||
}
|
||||
|
||||
if (self._bufferedWritesFlushAt === null) {
|
||||
self._bufferedWritesFlushAt =
|
||||
new Date().valueOf() + self._bufferedWritesMaxAge;
|
||||
} else if (self._bufferedWritesFlushAt < new Date().valueOf()) {
|
||||
await self._flushBufferedWrites();
|
||||
return;
|
||||
}
|
||||
|
||||
if (self._bufferedWritesFlushHandle) {
|
||||
clearTimeout(self._bufferedWritesFlushHandle);
|
||||
}
|
||||
self._bufferedWritesFlushHandle = setTimeout(() => {
|
||||
// __flushBufferedWrites is a promise, so with this we can wait the promise to finish
|
||||
// before doing something
|
||||
self._liveDataWritesPromise = self.__flushBufferedWrites();
|
||||
|
||||
if (Meteor._isPromise(self._liveDataWritesPromise)) {
|
||||
self._liveDataWritesPromise.finally(
|
||||
() => (self._liveDataWritesPromise = undefined)
|
||||
);
|
||||
}
|
||||
}, self._bufferedWritesInterval);
|
||||
}
|
||||
|
||||
_prepareBuffersToFlush() {
|
||||
const self = this;
|
||||
if (self._bufferedWritesFlushHandle) {
|
||||
@@ -1332,61 +1157,49 @@ export class Connection {
|
||||
return writes;
|
||||
}
|
||||
|
||||
async _flushBufferedWritesServer() {
|
||||
const self = this;
|
||||
const writes = self._prepareBuffersToFlush();
|
||||
await self._performWritesServer(writes);
|
||||
}
|
||||
_flushBufferedWritesClient() {
|
||||
const self = this;
|
||||
const writes = self._prepareBuffersToFlush();
|
||||
self._performWritesClient(writes);
|
||||
}
|
||||
_flushBufferedWrites() {
|
||||
const self = this;
|
||||
return Meteor.isClient
|
||||
? self._flushBufferedWritesClient()
|
||||
: self._flushBufferedWritesServer();
|
||||
}
|
||||
/**
|
||||
* Server-side store updates handled asynchronously
|
||||
* @private
|
||||
*/
|
||||
async _performWritesServer(updates) {
|
||||
const self = this;
|
||||
|
||||
if (self._resetStores || ! isEmpty(updates)) {
|
||||
// Begin a transactional update of each store.
|
||||
|
||||
for (const [storeName, store] of Object.entries(self._stores)) {
|
||||
if (self._resetStores || !isEmpty(updates)) {
|
||||
// Start all store updates - keeping original loop structure
|
||||
for (const store of Object.values(self._stores)) {
|
||||
await store.beginUpdate(
|
||||
hasOwn.call(updates, storeName)
|
||||
? updates[storeName].length
|
||||
: 0,
|
||||
updates[store._name]?.length || 0,
|
||||
self._resetStores
|
||||
);
|
||||
}
|
||||
|
||||
self._resetStores = false;
|
||||
|
||||
for (const [storeName, updateMessages] of Object.entries(updates)) {
|
||||
// Process each store's updates sequentially as before
|
||||
for (const [storeName, messages] of Object.entries(updates)) {
|
||||
const store = self._stores[storeName];
|
||||
if (store) {
|
||||
for (const updateMessage of updateMessages) {
|
||||
await store.update(updateMessage);
|
||||
// Batch each store's messages in modest chunks to prevent event loop blocking
|
||||
// while maintaining operation order
|
||||
const CHUNK_SIZE = 100;
|
||||
for (let i = 0; i < messages.length; i += CHUNK_SIZE) {
|
||||
const chunk = messages.slice(i, Math.min(i + CHUNK_SIZE, messages.length));
|
||||
|
||||
for (const msg of chunk) {
|
||||
await store.update(msg);
|
||||
}
|
||||
|
||||
await new Promise(resolve => process.nextTick(resolve));
|
||||
}
|
||||
} else {
|
||||
// Nobody's listening for this data. Queue it up until
|
||||
// someone wants it.
|
||||
// XXX memory use will grow without bound if you forget to
|
||||
// create a collection or just don't care about it... going
|
||||
// to have to do something about that.
|
||||
const updates = self._updatesForUnknownStores;
|
||||
|
||||
if (! hasOwn.call(updates, storeName)) {
|
||||
updates[storeName] = [];
|
||||
}
|
||||
|
||||
updates[storeName].push(...updateMessages);
|
||||
// Queue updates for uninitialized stores
|
||||
self._updatesForUnknownStores[storeName] =
|
||||
self._updatesForUnknownStores[storeName] || [];
|
||||
self._updatesForUnknownStores[storeName].push(...messages);
|
||||
}
|
||||
}
|
||||
// End update transaction.
|
||||
|
||||
// Complete all updates
|
||||
for (const store of Object.values(self._stores)) {
|
||||
await store.endUpdate();
|
||||
}
|
||||
@@ -1394,53 +1207,55 @@ export class Connection {
|
||||
|
||||
self._runAfterUpdateCallbacks();
|
||||
}
|
||||
|
||||
/**
|
||||
* Client-side store updates handled synchronously for optimistic UI
|
||||
* @private
|
||||
*/
|
||||
_performWritesClient(updates) {
|
||||
const self = this;
|
||||
|
||||
if (self._resetStores || ! isEmpty(updates)) {
|
||||
// Begin a transactional update of each store.
|
||||
|
||||
for (const [storeName, store] of Object.entries(self._stores)) {
|
||||
if (self._resetStores || !isEmpty(updates)) {
|
||||
// Synchronous store updates for client
|
||||
Object.values(self._stores).forEach(store => {
|
||||
store.beginUpdate(
|
||||
hasOwn.call(updates, storeName)
|
||||
? updates[storeName].length
|
||||
: 0,
|
||||
updates[store._name]?.length || 0,
|
||||
self._resetStores
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
self._resetStores = false;
|
||||
|
||||
for (const [storeName, updateMessages] of Object.entries(updates)) {
|
||||
Object.entries(updates).forEach(([storeName, messages]) => {
|
||||
const store = self._stores[storeName];
|
||||
if (store) {
|
||||
for (const updateMessage of updateMessages) {
|
||||
store.update(updateMessage);
|
||||
}
|
||||
messages.forEach(msg => store.update(msg));
|
||||
} else {
|
||||
// Nobody's listening for this data. Queue it up until
|
||||
// someone wants it.
|
||||
// XXX memory use will grow without bound if you forget to
|
||||
// create a collection or just don't care about it... going
|
||||
// to have to do something about that.
|
||||
const updates = self._updatesForUnknownStores;
|
||||
|
||||
if (! hasOwn.call(updates, storeName)) {
|
||||
updates[storeName] = [];
|
||||
}
|
||||
|
||||
updates[storeName].push(...updateMessages);
|
||||
self._updatesForUnknownStores[storeName] =
|
||||
self._updatesForUnknownStores[storeName] || [];
|
||||
self._updatesForUnknownStores[storeName].push(...messages);
|
||||
}
|
||||
}
|
||||
// End update transaction.
|
||||
for (const store of Object.values(self._stores)) {
|
||||
store.endUpdate();
|
||||
}
|
||||
});
|
||||
|
||||
Object.values(self._stores).forEach(store => store.endUpdate());
|
||||
}
|
||||
|
||||
self._runAfterUpdateCallbacks();
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes buffered writes either synchronously (client) or async (server)
|
||||
* @private
|
||||
*/
|
||||
async _flushBufferedWrites() {
|
||||
const self = this;
|
||||
const writes = self._prepareBuffersToFlush();
|
||||
|
||||
return Meteor.isClient
|
||||
? self._performWritesClient(writes)
|
||||
: self._performWritesServer(writes);
|
||||
}
|
||||
|
||||
// Call any callbacks deferred with _runWhenAllServerDocsAreFlushed whose
|
||||
// relevant docs have been flushed, as well as dataVisible callbacks at
|
||||
// reconnect-quiescence time.
|
||||
@@ -1453,160 +1268,6 @@ export class Connection {
|
||||
});
|
||||
}
|
||||
|
||||
_pushUpdate(updates, collection, msg) {
|
||||
if (! hasOwn.call(updates, collection)) {
|
||||
updates[collection] = [];
|
||||
}
|
||||
updates[collection].push(msg);
|
||||
}
|
||||
|
||||
_getServerDoc(collection, id) {
|
||||
const self = this;
|
||||
if (! hasOwn.call(self._serverDocuments, collection)) {
|
||||
return null;
|
||||
}
|
||||
const serverDocsForCollection = self._serverDocuments[collection];
|
||||
return serverDocsForCollection.get(id) || null;
|
||||
}
|
||||
|
||||
async _process_added(msg, updates) {
|
||||
const self = this;
|
||||
const id = MongoID.idParse(msg.id);
|
||||
const serverDoc = self._getServerDoc(msg.collection, id);
|
||||
if (serverDoc) {
|
||||
// Some outstanding stub wrote here.
|
||||
const isExisting = serverDoc.document !== undefined;
|
||||
|
||||
serverDoc.document = msg.fields || Object.create(null);
|
||||
serverDoc.document._id = id;
|
||||
|
||||
if (self._resetStores) {
|
||||
// During reconnect the server is sending adds for existing ids.
|
||||
// Always push an update so that document stays in the store after
|
||||
// reset. Use current version of the document for this update, so
|
||||
// that stub-written values are preserved.
|
||||
const currentDoc = await self._stores[msg.collection].getDoc(msg.id);
|
||||
if (currentDoc !== undefined) msg.fields = currentDoc;
|
||||
|
||||
self._pushUpdate(updates, msg.collection, msg);
|
||||
} else if (isExisting) {
|
||||
throw new Error('Server sent add for existing id: ' + msg.id);
|
||||
}
|
||||
} else {
|
||||
self._pushUpdate(updates, msg.collection, msg);
|
||||
}
|
||||
}
|
||||
|
||||
_process_changed(msg, updates) {
|
||||
const self = this;
|
||||
const serverDoc = self._getServerDoc(msg.collection, MongoID.idParse(msg.id));
|
||||
if (serverDoc) {
|
||||
if (serverDoc.document === undefined)
|
||||
throw new Error('Server sent changed for nonexisting id: ' + msg.id);
|
||||
DiffSequence.applyChanges(serverDoc.document, msg.fields);
|
||||
} else {
|
||||
self._pushUpdate(updates, msg.collection, msg);
|
||||
}
|
||||
}
|
||||
|
||||
_process_removed(msg, updates) {
|
||||
const self = this;
|
||||
const serverDoc = self._getServerDoc(msg.collection, MongoID.idParse(msg.id));
|
||||
if (serverDoc) {
|
||||
// Some outstanding stub wrote here.
|
||||
if (serverDoc.document === undefined)
|
||||
throw new Error('Server sent removed for nonexisting id:' + msg.id);
|
||||
serverDoc.document = undefined;
|
||||
} else {
|
||||
self._pushUpdate(updates, msg.collection, {
|
||||
msg: 'removed',
|
||||
collection: msg.collection,
|
||||
id: msg.id
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
_process_updated(msg, updates) {
|
||||
const self = this;
|
||||
// Process "method done" messages.
|
||||
|
||||
msg.methods.forEach((methodId) => {
|
||||
const docs = self._documentsWrittenByStub[methodId] || {};
|
||||
Object.values(docs).forEach((written) => {
|
||||
const serverDoc = self._getServerDoc(written.collection, written.id);
|
||||
if (! serverDoc) {
|
||||
throw new Error('Lost serverDoc for ' + JSON.stringify(written));
|
||||
}
|
||||
if (! serverDoc.writtenByStubs[methodId]) {
|
||||
throw new Error(
|
||||
'Doc ' +
|
||||
JSON.stringify(written) +
|
||||
' not written by method ' +
|
||||
methodId
|
||||
);
|
||||
}
|
||||
delete serverDoc.writtenByStubs[methodId];
|
||||
if (isEmpty(serverDoc.writtenByStubs)) {
|
||||
// All methods whose stubs wrote this method have completed! We can
|
||||
// now copy the saved document to the database (reverting the stub's
|
||||
// change if the server did not write to this object, or applying the
|
||||
// server's writes if it did).
|
||||
|
||||
// This is a fake ddp 'replace' message. It's just for talking
|
||||
// between livedata connections and minimongo. (We have to stringify
|
||||
// the ID because it's supposed to look like a wire message.)
|
||||
self._pushUpdate(updates, written.collection, {
|
||||
msg: 'replace',
|
||||
id: MongoID.idStringify(written.id),
|
||||
replace: serverDoc.document
|
||||
});
|
||||
// Call all flush callbacks.
|
||||
|
||||
serverDoc.flushCallbacks.forEach((c) => {
|
||||
c();
|
||||
});
|
||||
|
||||
// Delete this completed serverDocument. Don't bother to GC empty
|
||||
// IdMaps inside self._serverDocuments, since there probably aren't
|
||||
// many collections and they'll be written repeatedly.
|
||||
self._serverDocuments[written.collection].remove(written.id);
|
||||
}
|
||||
});
|
||||
delete self._documentsWrittenByStub[methodId];
|
||||
|
||||
// We want to call the data-written callback, but we can't do so until all
|
||||
// currently buffered messages are flushed.
|
||||
const callbackInvoker = self._methodInvokers[methodId];
|
||||
if (! callbackInvoker) {
|
||||
throw new Error('No callback invoker for method ' + methodId);
|
||||
}
|
||||
|
||||
self._runWhenAllServerDocsAreFlushed(
|
||||
(...args) => callbackInvoker.dataVisible(...args)
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
_process_ready(msg, updates) {
|
||||
const self = this;
|
||||
// Process "sub ready" messages. "sub ready" messages don't take effect
|
||||
// until all current server documents have been flushed to the local
|
||||
// database. We can use a write fence to implement this.
|
||||
|
||||
msg.subs.forEach((subId) => {
|
||||
self._runWhenAllServerDocsAreFlushed(() => {
|
||||
const subRecord = self._subscriptions[subId];
|
||||
// Did we already unsubscribe?
|
||||
if (!subRecord) return;
|
||||
// Did we already receive a ready message? (Oops!)
|
||||
if (subRecord.ready) return;
|
||||
subRecord.ready = true;
|
||||
subRecord.readyCallback && subRecord.readyCallback();
|
||||
subRecord.readyDeps.changed();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// Ensures that "f" will be called after all documents currently in
|
||||
// _serverDocuments have been written to the local cache. f will not be called
|
||||
// if the connection is lost before then!
|
||||
@@ -1646,93 +1307,6 @@ export class Connection {
|
||||
}
|
||||
}
|
||||
|
||||
async _livedata_nosub(msg) {
|
||||
const self = this;
|
||||
|
||||
// First pass it through _livedata_data, which only uses it to help get
|
||||
// towards quiescence.
|
||||
await self._livedata_data(msg);
|
||||
|
||||
// Do the rest of our processing immediately, with no
|
||||
// buffering-until-quiescence.
|
||||
|
||||
// we weren't subbed anyway, or we initiated the unsub.
|
||||
if (! hasOwn.call(self._subscriptions, msg.id)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// XXX COMPAT WITH 1.0.3.1 #errorCallback
|
||||
const errorCallback = self._subscriptions[msg.id].errorCallback;
|
||||
const stopCallback = self._subscriptions[msg.id].stopCallback;
|
||||
|
||||
self._subscriptions[msg.id].remove();
|
||||
|
||||
const meteorErrorFromMsg = msgArg => {
|
||||
return (
|
||||
msgArg &&
|
||||
msgArg.error &&
|
||||
new Meteor.Error(
|
||||
msgArg.error.error,
|
||||
msgArg.error.reason,
|
||||
msgArg.error.details
|
||||
)
|
||||
);
|
||||
};
|
||||
|
||||
// XXX COMPAT WITH 1.0.3.1 #errorCallback
|
||||
if (errorCallback && msg.error) {
|
||||
errorCallback(meteorErrorFromMsg(msg));
|
||||
}
|
||||
|
||||
if (stopCallback) {
|
||||
stopCallback(meteorErrorFromMsg(msg));
|
||||
}
|
||||
}
|
||||
|
||||
async _livedata_result(msg) {
|
||||
// id, result or error. error has error (code), reason, details
|
||||
|
||||
const self = this;
|
||||
|
||||
// Lets make sure there are no buffered writes before returning result.
|
||||
if (! isEmpty(self._bufferedWrites)) {
|
||||
await self._flushBufferedWrites();
|
||||
}
|
||||
|
||||
// find the outstanding request
|
||||
// should be O(1) in nearly all realistic use cases
|
||||
if (isEmpty(self._outstandingMethodBlocks)) {
|
||||
Meteor._debug('Received method result but no methods outstanding');
|
||||
return;
|
||||
}
|
||||
const currentMethodBlock = self._outstandingMethodBlocks[0].methods;
|
||||
let i;
|
||||
const m = currentMethodBlock.find((method, idx) => {
|
||||
const found = method.methodId === msg.id;
|
||||
if (found) i = idx;
|
||||
return found;
|
||||
});
|
||||
if (!m) {
|
||||
Meteor._debug("Can't match method response to original method call", msg);
|
||||
return;
|
||||
}
|
||||
|
||||
// Remove from current method block. This may leave the block empty, but we
|
||||
// don't move on to the next block until the callback has been delivered, in
|
||||
// _outstandingMethodFinished.
|
||||
currentMethodBlock.splice(i, 1);
|
||||
|
||||
if (hasOwn.call(msg, 'error')) {
|
||||
m.receiveResult(
|
||||
new Meteor.Error(msg.error.error, msg.error.reason, msg.error.details)
|
||||
);
|
||||
} else {
|
||||
// msg.result may be undefined if the method didn't return a
|
||||
// value
|
||||
m.receiveResult(undefined, msg.result);
|
||||
}
|
||||
}
|
||||
|
||||
_addOutstandingMethod(methodInvoker, options) {
|
||||
if (options?.wait) {
|
||||
// It's a wait method! Wait methods go in their own block.
|
||||
@@ -1801,11 +1375,6 @@ export class Connection {
|
||||
});
|
||||
}
|
||||
|
||||
_livedata_error(msg) {
|
||||
Meteor._debug('Received error from server: ', msg.reason);
|
||||
if (msg.offendingMessage) Meteor._debug('For: ', msg.offendingMessage);
|
||||
}
|
||||
|
||||
_sendOutstandingMethodBlocksMessages(oldOutstandingMethodBlocks) {
|
||||
const self = this;
|
||||
if (isEmpty(oldOutstandingMethodBlocks)) return;
|
||||
@@ -1870,148 +1439,4 @@ export class Connection {
|
||||
self._retryMigrate = null;
|
||||
}
|
||||
}
|
||||
|
||||
async onMessage(raw_msg) {
|
||||
let msg;
|
||||
try {
|
||||
msg = DDPCommon.parseDDP(raw_msg);
|
||||
} catch (e) {
|
||||
Meteor._debug('Exception while parsing DDP', e);
|
||||
return;
|
||||
}
|
||||
|
||||
// Any message counts as receiving a pong, as it demonstrates that
|
||||
// the server is still alive.
|
||||
if (this._heartbeat) {
|
||||
this._heartbeat.messageReceived();
|
||||
}
|
||||
|
||||
if (msg === null || !msg.msg) {
|
||||
if(!msg || !msg.testMessageOnConnect) {
|
||||
if (Object.keys(msg).length === 1 && msg.server_id) return;
|
||||
Meteor._debug('discarding invalid livedata message', msg);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (msg.msg === 'connected') {
|
||||
this._version = this._versionSuggestion;
|
||||
await this._livedata_connected(msg);
|
||||
this.options.onConnected();
|
||||
} else if (msg.msg === 'failed') {
|
||||
if (this._supportedDDPVersions.indexOf(msg.version) >= 0) {
|
||||
this._versionSuggestion = msg.version;
|
||||
this._stream.reconnect({ _force: true });
|
||||
} else {
|
||||
const description =
|
||||
'DDP version negotiation failed; server requested version ' +
|
||||
msg.version;
|
||||
this._stream.disconnect({ _permanent: true, _error: description });
|
||||
this.options.onDDPVersionNegotiationFailure(description);
|
||||
}
|
||||
} else if (msg.msg === 'ping' && this.options.respondToPings) {
|
||||
this._send({ msg: 'pong', id: msg.id });
|
||||
} else if (msg.msg === 'pong') {
|
||||
// noop, as we assume everything's a pong
|
||||
} else if (
|
||||
['added', 'changed', 'removed', 'ready', 'updated'].includes(msg.msg)
|
||||
) {
|
||||
await this._livedata_data(msg);
|
||||
} else if (msg.msg === 'nosub') {
|
||||
await this._livedata_nosub(msg);
|
||||
} else if (msg.msg === 'result') {
|
||||
await this._livedata_result(msg);
|
||||
} else if (msg.msg === 'error') {
|
||||
this._livedata_error(msg);
|
||||
} else {
|
||||
Meteor._debug('discarding unknown livedata message type', msg);
|
||||
}
|
||||
}
|
||||
|
||||
onReset() {
|
||||
// Send a connect message at the beginning of the stream.
|
||||
// NOTE: reset is called even on the first connection, so this is
|
||||
// the only place we send this message.
|
||||
const msg = { msg: 'connect' };
|
||||
if (this._lastSessionId) msg.session = this._lastSessionId;
|
||||
msg.version = this._versionSuggestion || this._supportedDDPVersions[0];
|
||||
this._versionSuggestion = msg.version;
|
||||
msg.support = this._supportedDDPVersions;
|
||||
this._send(msg);
|
||||
|
||||
// Mark non-retry calls as failed. This has to be done early as getting these methods out of the
|
||||
// current block is pretty important to making sure that quiescence is properly calculated, as
|
||||
// well as possibly moving on to another useful block.
|
||||
|
||||
// Only bother testing if there is an outstandingMethodBlock (there might not be, especially if
|
||||
// we are connecting for the first time.
|
||||
if (this._outstandingMethodBlocks.length > 0) {
|
||||
// If there is an outstanding method block, we only care about the first one as that is the
|
||||
// one that could have already sent messages with no response, that are not allowed to retry.
|
||||
const currentMethodBlock = this._outstandingMethodBlocks[0].methods;
|
||||
this._outstandingMethodBlocks[0].methods = currentMethodBlock.filter(
|
||||
methodInvoker => {
|
||||
// Methods with 'noRetry' option set are not allowed to re-send after
|
||||
// recovering dropped connection.
|
||||
if (methodInvoker.sentMessage && methodInvoker.noRetry) {
|
||||
// Make sure that the method is told that it failed.
|
||||
methodInvoker.receiveResult(
|
||||
new Meteor.Error(
|
||||
'invocation-failed',
|
||||
'Method invocation might have failed due to dropped connection. ' +
|
||||
'Failing because `noRetry` option was passed to Meteor.apply.'
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
// Only keep a method if it wasn't sent or it's allowed to retry.
|
||||
// This may leave the block empty, but we don't move on to the next
|
||||
// block until the callback has been delivered, in _outstandingMethodFinished.
|
||||
return !(methodInvoker.sentMessage && methodInvoker.noRetry);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
// Now, to minimize setup latency, go ahead and blast out all of
|
||||
// our pending methods ands subscriptions before we've even taken
|
||||
// the necessary RTT to know if we successfully reconnected. (1)
|
||||
// They're supposed to be idempotent, and where they are not,
|
||||
// they can block retry in apply; (2) even if we did reconnect,
|
||||
// we're not sure what messages might have gotten lost
|
||||
// (in either direction) since we were disconnected (TCP being
|
||||
// sloppy about that.)
|
||||
|
||||
// If the current block of methods all got their results (but didn't all get
|
||||
// their data visible), discard the empty block now.
|
||||
if (
|
||||
this._outstandingMethodBlocks.length > 0 &&
|
||||
this._outstandingMethodBlocks[0].methods.length === 0
|
||||
) {
|
||||
this._outstandingMethodBlocks.shift();
|
||||
}
|
||||
|
||||
// Mark all messages as unsent, they have not yet been sent on this
|
||||
// connection.
|
||||
keys(this._methodInvokers).forEach(id => {
|
||||
this._methodInvokers[id].sentMessage = false;
|
||||
});
|
||||
|
||||
// If an `onReconnect` handler is set, call it first. Go through
|
||||
// some hoops to ensure that methods that are called from within
|
||||
// `onReconnect` get executed _before_ ones that were originally
|
||||
// outstanding (since `onReconnect` is used to re-establish auth
|
||||
// certificates)
|
||||
this._callOnReconnectAndSendAppropriateOutstandingMethods();
|
||||
|
||||
// add new subscriptions at the end. this way they take effect after
|
||||
// the handlers and we don't see flicker.
|
||||
Object.entries(this._subscriptions).forEach(([id, sub]) => {
|
||||
this._sendQueued({
|
||||
msg: 'sub',
|
||||
id: id,
|
||||
name: sub.name,
|
||||
params: sub.params
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
336
packages/ddp-client/common/message_processors.js
Normal file
336
packages/ddp-client/common/message_processors.js
Normal file
@@ -0,0 +1,336 @@
|
||||
import { DDPCommon } from 'meteor/ddp-common';
|
||||
import { Meteor } from 'meteor/meteor';
|
||||
import { DDP } from './namespace.js';
|
||||
import { EJSON } from 'meteor/ejson';
|
||||
import { isEmpty, hasOwn } from "meteor/ddp-common/utils";
|
||||
|
||||
export class MessageProcessors {
|
||||
constructor(connection) {
|
||||
this._connection = connection;
|
||||
}
|
||||
|
||||
/**
|
||||
* @summary Process the connection message and set up the session
|
||||
* @param {Object} msg The connection message
|
||||
*/
|
||||
async _livedata_connected(msg) {
|
||||
const self = this._connection;
|
||||
|
||||
if (self._version !== 'pre1' && self._heartbeatInterval !== 0) {
|
||||
self._heartbeat = new DDPCommon.Heartbeat({
|
||||
heartbeatInterval: self._heartbeatInterval,
|
||||
heartbeatTimeout: self._heartbeatTimeout,
|
||||
onTimeout() {
|
||||
self._lostConnection(
|
||||
new DDP.ConnectionError('DDP heartbeat timed out')
|
||||
);
|
||||
},
|
||||
sendPing() {
|
||||
self._send({ msg: 'ping' });
|
||||
}
|
||||
});
|
||||
self._heartbeat.start();
|
||||
}
|
||||
|
||||
// If this is a reconnect, we'll have to reset all stores.
|
||||
if (self._lastSessionId) self._resetStores = true;
|
||||
|
||||
let reconnectedToPreviousSession;
|
||||
if (typeof msg.session === 'string') {
|
||||
reconnectedToPreviousSession = self._lastSessionId === msg.session;
|
||||
self._lastSessionId = msg.session;
|
||||
}
|
||||
|
||||
if (reconnectedToPreviousSession) {
|
||||
// Successful reconnection -- pick up where we left off.
|
||||
return;
|
||||
}
|
||||
|
||||
// Server doesn't have our data anymore. Re-sync a new session.
|
||||
|
||||
// Forget about messages we were buffering for unknown collections. They'll
|
||||
// be resent if still relevant.
|
||||
self._updatesForUnknownStores = Object.create(null);
|
||||
|
||||
if (self._resetStores) {
|
||||
// Forget about the effects of stubs. We'll be resetting all collections
|
||||
// anyway.
|
||||
self._documentsWrittenByStub = Object.create(null);
|
||||
self._serverDocuments = Object.create(null);
|
||||
}
|
||||
|
||||
// Clear _afterUpdateCallbacks.
|
||||
self._afterUpdateCallbacks = [];
|
||||
|
||||
// Mark all named subscriptions which are ready as needing to be revived.
|
||||
self._subsBeingRevived = Object.create(null);
|
||||
Object.entries(self._subscriptions).forEach(([id, sub]) => {
|
||||
if (sub.ready) {
|
||||
self._subsBeingRevived[id] = true;
|
||||
}
|
||||
});
|
||||
|
||||
// Arrange for "half-finished" methods to have their callbacks run, and
|
||||
// track methods that were sent on this connection so that we don't
|
||||
// quiesce until they are all done.
|
||||
//
|
||||
// Start by clearing _methodsBlockingQuiescence: methods sent before
|
||||
// reconnect don't matter, and any "wait" methods sent on the new connection
|
||||
// that we drop here will be restored by the loop below.
|
||||
self._methodsBlockingQuiescence = Object.create(null);
|
||||
if (self._resetStores) {
|
||||
const invokers = self._methodInvokers;
|
||||
Object.keys(invokers).forEach(id => {
|
||||
const invoker = invokers[id];
|
||||
if (invoker.gotResult()) {
|
||||
// This method already got its result, but it didn't call its callback
|
||||
// because its data didn't become visible. We did not resend the
|
||||
// method RPC. We'll call its callback when we get a full quiesce,
|
||||
// since that's as close as we'll get to "data must be visible".
|
||||
self._afterUpdateCallbacks.push(
|
||||
(...args) => invoker.dataVisible(...args)
|
||||
);
|
||||
} else if (invoker.sentMessage) {
|
||||
// This method has been sent on this connection (maybe as a resend
|
||||
// from the last connection, maybe from onReconnect, maybe just very
|
||||
// quickly before processing the connected message).
|
||||
//
|
||||
// We don't need to do anything special to ensure its callbacks get
|
||||
// called, but we'll count it as a method which is preventing
|
||||
// reconnect quiescence. (eg, it might be a login method that was run
|
||||
// from onReconnect, and we don't want to see flicker by seeing a
|
||||
// logged-out state.)
|
||||
self._methodsBlockingQuiescence[invoker.methodId] = true;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
self._messagesBufferedUntilQuiescence = [];
|
||||
|
||||
// If we're not waiting on any methods or subs, we can reset the stores and
|
||||
// call the callbacks immediately.
|
||||
if (!self._waitingForQuiescence()) {
|
||||
if (self._resetStores) {
|
||||
for (const store of Object.values(self._stores)) {
|
||||
await store.beginUpdate(0, true);
|
||||
await store.endUpdate();
|
||||
}
|
||||
self._resetStores = false;
|
||||
}
|
||||
self._runAfterUpdateCallbacks();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @summary Process various data messages from the server
|
||||
* @param {Object} msg The data message
|
||||
*/
|
||||
async _livedata_data(msg) {
|
||||
const self = this._connection;
|
||||
|
||||
if (self._waitingForQuiescence()) {
|
||||
self._messagesBufferedUntilQuiescence.push(msg);
|
||||
|
||||
if (msg.msg === 'nosub') {
|
||||
delete self._subsBeingRevived[msg.id];
|
||||
}
|
||||
|
||||
if (msg.subs) {
|
||||
msg.subs.forEach(subId => {
|
||||
delete self._subsBeingRevived[subId];
|
||||
});
|
||||
}
|
||||
|
||||
if (msg.methods) {
|
||||
msg.methods.forEach(methodId => {
|
||||
delete self._methodsBlockingQuiescence[methodId];
|
||||
});
|
||||
}
|
||||
|
||||
if (self._waitingForQuiescence()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// No methods or subs are blocking quiescence!
|
||||
// We'll now process and all of our buffered messages, reset all stores,
|
||||
// and apply them all at once.
|
||||
const bufferedMessages = self._messagesBufferedUntilQuiescence;
|
||||
for (const bufferedMessage of Object.values(bufferedMessages)) {
|
||||
await this._processOneDataMessage(
|
||||
bufferedMessage,
|
||||
self._bufferedWrites
|
||||
);
|
||||
}
|
||||
self._messagesBufferedUntilQuiescence = [];
|
||||
} else {
|
||||
await this._processOneDataMessage(msg, self._bufferedWrites);
|
||||
}
|
||||
|
||||
// Immediately flush writes when:
|
||||
// 1. Buffering is disabled. Or;
|
||||
// 2. any non-(added/changed/removed) message arrives.
|
||||
const standardWrite =
|
||||
msg.msg === "added" ||
|
||||
msg.msg === "changed" ||
|
||||
msg.msg === "removed";
|
||||
|
||||
if (self._bufferedWritesInterval === 0 || !standardWrite) {
|
||||
await self._flushBufferedWrites();
|
||||
return;
|
||||
}
|
||||
|
||||
if (self._bufferedWritesFlushAt === null) {
|
||||
self._bufferedWritesFlushAt =
|
||||
new Date().valueOf() + self._bufferedWritesMaxAge;
|
||||
} else if (self._bufferedWritesFlushAt < new Date().valueOf()) {
|
||||
await self._flushBufferedWrites();
|
||||
return;
|
||||
}
|
||||
|
||||
if (self._bufferedWritesFlushHandle) {
|
||||
clearTimeout(self._bufferedWritesFlushHandle);
|
||||
}
|
||||
self._bufferedWritesFlushHandle = setTimeout(() => {
|
||||
self._liveDataWritesPromise = self._flushBufferedWrites();
|
||||
if (Meteor._isPromise(self._liveDataWritesPromise)) {
|
||||
self._liveDataWritesPromise.finally(
|
||||
() => (self._liveDataWritesPromise = undefined)
|
||||
);
|
||||
}
|
||||
}, self._bufferedWritesInterval);
|
||||
}
|
||||
|
||||
/**
|
||||
* @summary Process individual data messages by type
|
||||
* @private
|
||||
*/
|
||||
async _processOneDataMessage(msg, updates) {
|
||||
const messageType = msg.msg;
|
||||
|
||||
switch (messageType) {
|
||||
case 'added':
|
||||
await this._connection._process_added(msg, updates);
|
||||
break;
|
||||
case 'changed':
|
||||
this._connection._process_changed(msg, updates);
|
||||
break;
|
||||
case 'removed':
|
||||
this._connection._process_removed(msg, updates);
|
||||
break;
|
||||
case 'ready':
|
||||
this._connection._process_ready(msg, updates);
|
||||
break;
|
||||
case 'updated':
|
||||
this._connection._process_updated(msg, updates);
|
||||
break;
|
||||
case 'nosub':
|
||||
// ignore this
|
||||
break;
|
||||
default:
|
||||
Meteor._debug('discarding unknown livedata data message type', msg);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @summary Handle method results arriving from the server
|
||||
* @param {Object} msg The method result message
|
||||
*/
|
||||
async _livedata_result(msg) {
|
||||
const self = this._connection;
|
||||
|
||||
// Lets make sure there are no buffered writes before returning result.
|
||||
if (!isEmpty(self._bufferedWrites)) {
|
||||
await self._flushBufferedWrites();
|
||||
}
|
||||
|
||||
// find the outstanding request
|
||||
// should be O(1) in nearly all realistic use cases
|
||||
if (isEmpty(self._outstandingMethodBlocks)) {
|
||||
Meteor._debug('Received method result but no methods outstanding');
|
||||
return;
|
||||
}
|
||||
const currentMethodBlock = self._outstandingMethodBlocks[0].methods;
|
||||
let i;
|
||||
const m = currentMethodBlock.find((method, idx) => {
|
||||
const found = method.methodId === msg.id;
|
||||
if (found) i = idx;
|
||||
return found;
|
||||
});
|
||||
if (!m) {
|
||||
Meteor._debug("Can't match method response to original method call", msg);
|
||||
return;
|
||||
}
|
||||
|
||||
// Remove from current method block. This may leave the block empty, but we
|
||||
// don't move on to the next block until the callback has been delivered, in
|
||||
// _outstandingMethodFinished.
|
||||
currentMethodBlock.splice(i, 1);
|
||||
|
||||
if (hasOwn.call(msg, 'error')) {
|
||||
m.receiveResult(
|
||||
new Meteor.Error(msg.error.error, msg.error.reason, msg.error.details)
|
||||
);
|
||||
} else {
|
||||
// msg.result may be undefined if the method didn't return a value
|
||||
m.receiveResult(undefined, msg.result);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @summary Handle "nosub" messages arriving from the server
|
||||
* @param {Object} msg The nosub message
|
||||
*/
|
||||
async _livedata_nosub(msg) {
|
||||
const self = this._connection;
|
||||
|
||||
// First pass it through _livedata_data, which only uses it to help get
|
||||
// towards quiescence.
|
||||
await this._livedata_data(msg);
|
||||
|
||||
// Do the rest of our processing immediately, with no
|
||||
// buffering-until-quiescence.
|
||||
|
||||
// we weren't subbed anyway, or we initiated the unsub.
|
||||
if (!hasOwn.call(self._subscriptions, msg.id)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// XXX COMPAT WITH 1.0.3.1 #errorCallback
|
||||
const errorCallback = self._subscriptions[msg.id].errorCallback;
|
||||
const stopCallback = self._subscriptions[msg.id].stopCallback;
|
||||
|
||||
self._subscriptions[msg.id].remove();
|
||||
|
||||
const meteorErrorFromMsg = msgArg => {
|
||||
return (
|
||||
msgArg &&
|
||||
msgArg.error &&
|
||||
new Meteor.Error(
|
||||
msgArg.error.error,
|
||||
msgArg.error.reason,
|
||||
msgArg.error.details
|
||||
)
|
||||
);
|
||||
};
|
||||
|
||||
// XXX COMPAT WITH 1.0.3.1 #errorCallback
|
||||
if (errorCallback && msg.error) {
|
||||
errorCallback(meteorErrorFromMsg(msg));
|
||||
}
|
||||
|
||||
if (stopCallback) {
|
||||
stopCallback(meteorErrorFromMsg(msg));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @summary Handle errors from the server
|
||||
* @param {Object} msg The error message
|
||||
*/
|
||||
_livedata_error(msg) {
|
||||
Meteor._debug('Received error from server: ', msg.reason);
|
||||
if (msg.offendingMessage) Meteor._debug('For: ', msg.offendingMessage);
|
||||
}
|
||||
|
||||
// Document change message processors will be defined in a separate class
|
||||
}
|
||||
@@ -3,7 +3,7 @@
|
||||
// _methodInvokers map; it removes itself once the method is fully finished and
|
||||
// the callback is invoked. This occurs when it has both received a result,
|
||||
// and the data written by it is fully visible.
|
||||
export default class MethodInvoker {
|
||||
export class MethodInvoker {
|
||||
constructor(options) {
|
||||
// Public (within this file) fields.
|
||||
this.methodId = options.methodId;
|
||||
7
packages/ddp-client/common/mongo_id_map.js
Normal file
7
packages/ddp-client/common/mongo_id_map.js
Normal file
@@ -0,0 +1,7 @@
|
||||
import { MongoID } from 'meteor/mongo-id';
|
||||
|
||||
export class MongoIDMap extends IdMap {
|
||||
constructor() {
|
||||
super(MongoID.idStringify, MongoID.idParse);
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
Package.describe({
|
||||
summary: "Meteor's latency-compensated distributed data client",
|
||||
version: "3.0.2",
|
||||
version: "3.1.0",
|
||||
documentation: null,
|
||||
});
|
||||
|
||||
@@ -67,4 +67,5 @@ Package.onTest((api) => {
|
||||
api.addFiles("test/async_stubs/client.js", "client");
|
||||
api.addFiles("test/async_stubs/server_setup.js", "server");
|
||||
api.addFiles("test/livedata_callAsync_tests.js");
|
||||
api.addFiles("test/allow_deny_setup.js");
|
||||
});
|
||||
|
||||
17
packages/ddp-client/test/allow_deny_setup.js
Normal file
17
packages/ddp-client/test/allow_deny_setup.js
Normal file
@@ -0,0 +1,17 @@
|
||||
export const FlickerCollectionName = `allow_deny_flicker`;
|
||||
export const FlickerCollection = new Mongo.Collection(FlickerCollectionName);
|
||||
|
||||
if (Meteor.isServer) {
|
||||
FlickerCollection.allow({
|
||||
insert: () => true,
|
||||
update: () => true,
|
||||
remove: () => true,
|
||||
insertAsync: () => true,
|
||||
updateAsync: () => true,
|
||||
removeAsync: () => true,
|
||||
});
|
||||
|
||||
Meteor.publish(`pub-${FlickerCollectionName}`, function() {
|
||||
return FlickerCollection.find();
|
||||
});
|
||||
}
|
||||
@@ -334,38 +334,41 @@ if (Meteor.isServer) {
|
||||
One = new Mongo.Collection('collectionOne');
|
||||
Two = new Mongo.Collection('collectionTwo');
|
||||
|
||||
Meteor.startup(async () => {
|
||||
if (Meteor.isServer) {
|
||||
await One.removeAsync({});
|
||||
await One.insertAsync({ name: 'value1' });
|
||||
await One.insertAsync({ name: 'value2' });
|
||||
async function populateDatabase() {
|
||||
await One.removeAsync({});
|
||||
await One.insertAsync({ name: 'value1' });
|
||||
await One.insertAsync({ name: 'value2' });
|
||||
|
||||
await Two.removeAsync({});
|
||||
await Two.insertAsync({ name: 'value3' });
|
||||
await Two.insertAsync({ name: 'value4' });
|
||||
await Two.insertAsync({ 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';
|
||||
});
|
||||
}
|
||||
});
|
||||
if (Meteor.isServer) {
|
||||
Meteor.publish('multiPublish', async function (options) {
|
||||
// See below to see what options are accepted.
|
||||
check(options, Object);
|
||||
|
||||
await populateDatabase();
|
||||
|
||||
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,5 +1,7 @@
|
||||
import { DDP } from '../common/namespace.js';
|
||||
import { Meteor } from 'meteor/meteor';
|
||||
import { Connection } from '../common/livedata_connection.js';
|
||||
import { DDP } from '../common/namespace.js';
|
||||
import { FlickerCollection, FlickerCollectionName } from './allow_deny_setup.js';
|
||||
|
||||
const callWhenSubReady = async (subName, handle, cb = () => {}) => {
|
||||
let control = 0;
|
||||
@@ -1193,6 +1195,168 @@ testAsyncMulti('livedata - methods with nested stubs', [
|
||||
},
|
||||
]);
|
||||
|
||||
const collName = `test-collection`;
|
||||
const coll = new Mongo.Collection(collName);
|
||||
|
||||
if (Meteor.isServer) {
|
||||
Meteor.publish(`pub-${collName}`, function () {
|
||||
return coll.find();
|
||||
});
|
||||
}
|
||||
|
||||
Meteor.methods({
|
||||
[`insert-${collName}`]: async function() {
|
||||
return await coll.insertAsync({ value: 1 });
|
||||
},
|
||||
[`update-${collName}`]: async function(id) {
|
||||
return await coll.updateAsync(id, { $set: { value: 2 } });
|
||||
},
|
||||
[`remove-${collName}`]: async function(id) {
|
||||
return await coll.removeAsync(id);
|
||||
}
|
||||
});
|
||||
|
||||
if (Meteor.isClient) {
|
||||
Tinytest.addAsync('livedata - method updated message with subscriptions', async function (test) {
|
||||
let messages = [];
|
||||
|
||||
const onMessage = message => messages.push(EJSON.parse(message));
|
||||
|
||||
Meteor.connection._stream.on('message', onMessage);
|
||||
|
||||
const sub = Meteor.subscribe(`pub-${collName}`);
|
||||
|
||||
await new Promise(resolve => {
|
||||
const id = setInterval(() => {
|
||||
if (sub.ready()) {
|
||||
clearInterval(id);
|
||||
resolve();
|
||||
}
|
||||
}, 10);
|
||||
});
|
||||
|
||||
let insertId;
|
||||
let resultId
|
||||
|
||||
try {
|
||||
for (let i = 0; i < 250; i++) {
|
||||
messages = [];
|
||||
|
||||
insertId = await Meteor.callAsync(`insert-${collName}`);
|
||||
|
||||
const hasResult = messages.some(msg => msg.msg === 'result');
|
||||
|
||||
resultId = messages.find(msg => msg.msg === 'result').id;
|
||||
|
||||
const hasAdded = messages.some(msg => msg.msg === 'added');
|
||||
const hasUpdated = messages.some(msg =>
|
||||
msg.msg === 'updated' && msg.methods?.includes(resultId)
|
||||
);
|
||||
|
||||
test.isTrue(hasResult, `Iteration ${i}: Should receive RESULT message for insert`);
|
||||
test.isTrue(hasAdded, `Iteration ${i}: Should receive ADDED message for insert`);
|
||||
test.isTrue(hasUpdated, `Iteration ${i}: Should receive UPDATED message for insert`);
|
||||
|
||||
messages = [];
|
||||
|
||||
await Meteor.callAsync(`update-${collName}`, insertId);
|
||||
|
||||
const hasUpdateResult = messages.some(msg => msg.msg === 'result');
|
||||
|
||||
resultId = messages.find(msg => msg.msg === 'result').id;
|
||||
|
||||
const hasChanged = messages.some(msg => msg.msg === 'changed');
|
||||
const hasUpdateUpdated = messages.some(msg =>
|
||||
msg.msg === 'updated' && msg.methods?.includes(resultId)
|
||||
);
|
||||
|
||||
test.isTrue(hasUpdateResult, `Iteration ${i}: Should receive RESULT message for update`);
|
||||
test.isTrue(hasChanged, `Iteration ${i}: Should receive CHANGED message`);
|
||||
test.isTrue(hasUpdateUpdated, `Iteration ${i}: Should receive UPDATED message for update`);
|
||||
|
||||
messages = [];
|
||||
|
||||
await Meteor.callAsync(`remove-${collName}`, insertId);
|
||||
|
||||
const hasRemoveResult = messages.some(msg => msg.msg === 'result');
|
||||
|
||||
resultId = messages.find(msg => msg.msg === 'result').id;
|
||||
|
||||
const hasRemoved = messages.some(msg => msg.msg === 'removed');
|
||||
const hasRemoveUpdated = messages.some(msg =>
|
||||
msg.msg === 'updated' && msg.methods?.includes(resultId)
|
||||
);
|
||||
|
||||
test.isTrue(hasRemoveResult, `Iteration ${i}: Should receive RESULT message for remove`);
|
||||
test.isTrue(hasRemoved, `Iteration ${i}: Should receive REMOVED message`);
|
||||
test.isTrue(hasRemoveUpdated, `Iteration ${i}: Should receive UPDATED message for remove`);
|
||||
}
|
||||
} finally {
|
||||
sub.stop();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (Meteor.isClient) {
|
||||
testAsyncMulti('livedata - allow/deny - no flicker with isomorphic calls', [
|
||||
async function(test, expect) {
|
||||
const docId = await FlickerCollection.insertAsync({
|
||||
value: ['initial'],
|
||||
test: test.runId()
|
||||
});
|
||||
|
||||
let changeCount = 0;
|
||||
const messages = [];
|
||||
|
||||
const handle = await FlickerCollection.find({ _id: docId }).observeChanges({
|
||||
added(id, fields) {
|
||||
messages.push(['added', id, fields]);
|
||||
},
|
||||
changed(id, fields) {
|
||||
changeCount++;
|
||||
messages.push(['changed', id, fields]);
|
||||
|
||||
if (changeCount > 1) {
|
||||
test.fail('Multiple changes detected - flicker occurred');
|
||||
}
|
||||
|
||||
test.equal(fields.value.length, 2);
|
||||
test.isTrue(fields.value.includes('updated'));
|
||||
}
|
||||
});
|
||||
|
||||
const sub = Meteor.subscribe(`pub-${FlickerCollectionName}`);
|
||||
|
||||
await new Promise(resolve => {
|
||||
const checkReady = setInterval(() => {
|
||||
console.log('sub.ready()', sub.ready());
|
||||
if (sub.ready()) {
|
||||
clearInterval(checkReady);
|
||||
resolve();
|
||||
}
|
||||
}, 10);
|
||||
});
|
||||
|
||||
await FlickerCollection.updateAsync(docId, {
|
||||
$addToSet: {
|
||||
value: 'updated'
|
||||
}
|
||||
});
|
||||
|
||||
await Meteor._sleepForMs(200);
|
||||
|
||||
handle.stop();
|
||||
sub.stop();
|
||||
|
||||
test.equal(changeCount, 1, 'Expected exactly one change notification');
|
||||
|
||||
test.equal(messages.length, 2);
|
||||
test.equal(messages[0][0], 'added');
|
||||
test.equal(messages[1][0], 'changed');
|
||||
}
|
||||
]);
|
||||
}
|
||||
|
||||
// TODO [FIBERS] - check if this still makes sense to have
|
||||
|
||||
// Tinytest.addAsync('livedata - isAsync call', async function (test) {
|
||||
|
||||
65
packages/ddp-server/.npm/package/npm-shrinkwrap.json
generated
65
packages/ddp-server/.npm/package/npm-shrinkwrap.json
generated
@@ -1,65 +0,0 @@
|
||||
{
|
||||
"lockfileVersion": 4,
|
||||
"dependencies": {
|
||||
"faye-websocket": {
|
||||
"version": "0.11.4",
|
||||
"resolved": "https://registry.npmjs.org/faye-websocket/-/faye-websocket-0.11.4.tgz",
|
||||
"integrity": "sha512-CzbClwlXAuiRQAlUyfqPgvPoNKTckTPGfwZV4ZdAhVcP2lh9KUxJg2b5GkE7XbjKQ3YJnQ9z6D9ntLAlB+tP8g=="
|
||||
},
|
||||
"http-parser-js": {
|
||||
"version": "0.5.8",
|
||||
"resolved": "https://registry.npmjs.org/http-parser-js/-/http-parser-js-0.5.8.tgz",
|
||||
"integrity": "sha512-SGeBX54F94Wgu5RH3X5jsDtf4eHyRogWX1XGT3b4HuW3tQPM4AaBzoUji/4AAJNXCEOWZ5O0DgZmJw1947gD5Q=="
|
||||
},
|
||||
"lodash.isempty": {
|
||||
"version": "4.4.0",
|
||||
"resolved": "https://registry.npmjs.org/lodash.isempty/-/lodash.isempty-4.4.0.tgz",
|
||||
"integrity": "sha512-oKMuF3xEeqDltrGMfDxAPGIVMSSRv8tbRSODbrs4KGsRRLEhrW8N8Rd4DRgB2+621hY8A8XwwrTVhXWpxFvMzg=="
|
||||
},
|
||||
"lodash.isobject": {
|
||||
"version": "3.0.2",
|
||||
"resolved": "https://registry.npmjs.org/lodash.isobject/-/lodash.isobject-3.0.2.tgz",
|
||||
"integrity": "sha512-3/Qptq2vr7WeJbB4KHUSKlq8Pl7ASXi3UG6CMbBm8WRtXi8+GHm7mKaU3urfpSEzWe2wCIChs6/sdocUsTKJiA=="
|
||||
},
|
||||
"lodash.isstring": {
|
||||
"version": "4.0.1",
|
||||
"resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz",
|
||||
"integrity": "sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw=="
|
||||
},
|
||||
"lodash.once": {
|
||||
"version": "4.1.1",
|
||||
"resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz",
|
||||
"integrity": "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg=="
|
||||
},
|
||||
"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.24",
|
||||
"resolved": "https://registry.npmjs.org/sockjs/-/sockjs-0.3.24.tgz",
|
||||
"integrity": "sha512-GJgLTZ7vYb/JtPSSZ10hsOYIvEYsjbNU+zPdIHcUaWVNUEPivzxku31865sSSud0Da0W4lEeOPlmw93zLQchuQ=="
|
||||
},
|
||||
"uuid": {
|
||||
"version": "8.3.2",
|
||||
"resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz",
|
||||
"integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg=="
|
||||
},
|
||||
"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=="
|
||||
}
|
||||
}
|
||||
}
|
||||
40
packages/ddp-server/dummy_document_view.ts
Normal file
40
packages/ddp-server/dummy_document_view.ts
Normal file
@@ -0,0 +1,40 @@
|
||||
interface ChangeCollector {
|
||||
[key: string]: any;
|
||||
}
|
||||
|
||||
interface DataEntry {
|
||||
subscriptionHandle: string;
|
||||
value: any;
|
||||
}
|
||||
|
||||
export class DummyDocumentView {
|
||||
private existsIn: Set<string>;
|
||||
private dataByKey: Map<string, DataEntry[]>;
|
||||
|
||||
constructor() {
|
||||
this.existsIn = new Set<string>(); // set of subscriptionHandle
|
||||
this.dataByKey = new Map<string, DataEntry[]>(); // key-> [ {subscriptionHandle, value} by precedence]
|
||||
}
|
||||
|
||||
getFields(): Record<string, never> {
|
||||
return {};
|
||||
}
|
||||
|
||||
clearField(
|
||||
subscriptionHandle: string,
|
||||
key: string,
|
||||
changeCollector: ChangeCollector
|
||||
): void {
|
||||
changeCollector[key] = undefined;
|
||||
}
|
||||
|
||||
changeField(
|
||||
subscriptionHandle: string,
|
||||
key: string,
|
||||
value: any,
|
||||
changeCollector: ChangeCollector,
|
||||
isAdd?: boolean
|
||||
): void {
|
||||
changeCollector[key] = value;
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,8 @@
|
||||
import isEmpty from 'lodash.isempty';
|
||||
import isString from 'lodash.isstring';
|
||||
import isObject from 'lodash.isobject';
|
||||
import isString from 'lodash.isstring';
|
||||
import { SessionCollectionView } from './session_collection_view';
|
||||
import { SessionDocumentView } from './session_document_view';
|
||||
|
||||
DDPServer = {};
|
||||
|
||||
@@ -55,33 +57,7 @@ DDPServer.publicationStrategies = publicationStrategies;
|
||||
// Session and Subscription are file scope. For now, until we freeze
|
||||
// the interface, Server is package scope (in the future it should be
|
||||
// exported).
|
||||
var DummyDocumentView = function () {
|
||||
var self = this;
|
||||
self.existsIn = new Set(); // set of subscriptionHandle
|
||||
self.dataByKey = new Map(); // key-> [ {subscriptionHandle, value} by precedence]
|
||||
};
|
||||
|
||||
Object.assign(DummyDocumentView.prototype, {
|
||||
getFields: function () {
|
||||
return {}
|
||||
},
|
||||
|
||||
clearField: function (subscriptionHandle, key, changeCollector) {
|
||||
changeCollector[key] = undefined
|
||||
},
|
||||
|
||||
changeField: function (subscriptionHandle, key, value,
|
||||
changeCollector, isAdd) {
|
||||
changeCollector[key] = value
|
||||
}
|
||||
});
|
||||
|
||||
// Represents a single document in a SessionCollectionView
|
||||
var SessionDocumentView = function () {
|
||||
var self = this;
|
||||
self.existsIn = new Set(); // set of subscriptionHandle
|
||||
self.dataByKey = new Map(); // key-> [ {subscriptionHandle, value} by precedence]
|
||||
};
|
||||
|
||||
DDPServer._SessionDocumentView = SessionDocumentView;
|
||||
|
||||
@@ -94,210 +70,9 @@ DDPServer._getCurrentFence = function () {
|
||||
return currentInvocation ? currentInvocation.fence : undefined;
|
||||
};
|
||||
|
||||
Object.assign(SessionDocumentView.prototype, {
|
||||
|
||||
getFields: function () {
|
||||
var self = this;
|
||||
var ret = {};
|
||||
self.dataByKey.forEach(function (precedenceList, key) {
|
||||
ret[key] = precedenceList[0].value;
|
||||
});
|
||||
return ret;
|
||||
},
|
||||
|
||||
clearField: function (subscriptionHandle, key, changeCollector) {
|
||||
var self = this;
|
||||
// Publish API ignores _id if present in fields
|
||||
if (key === "_id")
|
||||
return;
|
||||
var precedenceList = self.dataByKey.get(key);
|
||||
|
||||
// It's okay to clear fields that didn't exist. No need to throw
|
||||
// an error.
|
||||
if (!precedenceList)
|
||||
return;
|
||||
|
||||
var removedValue = undefined;
|
||||
for (var i = 0; i < precedenceList.length; i++) {
|
||||
var precedence = precedenceList[i];
|
||||
if (precedence.subscriptionHandle === subscriptionHandle) {
|
||||
// The view's value can only change if this subscription is the one that
|
||||
// used to have precedence.
|
||||
if (i === 0)
|
||||
removedValue = precedence.value;
|
||||
precedenceList.splice(i, 1);
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (precedenceList.length === 0) {
|
||||
self.dataByKey.delete(key);
|
||||
changeCollector[key] = undefined;
|
||||
} else if (removedValue !== undefined &&
|
||||
!EJSON.equals(removedValue, precedenceList[0].value)) {
|
||||
changeCollector[key] = precedenceList[0].value;
|
||||
}
|
||||
},
|
||||
|
||||
changeField: function (subscriptionHandle, key, value,
|
||||
changeCollector, isAdd) {
|
||||
var self = this;
|
||||
// Publish API ignores _id if present in fields
|
||||
if (key === "_id")
|
||||
return;
|
||||
|
||||
// Don't share state with the data passed in by the user.
|
||||
value = EJSON.clone(value);
|
||||
|
||||
if (!self.dataByKey.has(key)) {
|
||||
self.dataByKey.set(key, [{subscriptionHandle: subscriptionHandle,
|
||||
value: value}]);
|
||||
changeCollector[key] = value;
|
||||
return;
|
||||
}
|
||||
var precedenceList = self.dataByKey.get(key);
|
||||
var elt;
|
||||
if (!isAdd) {
|
||||
elt = precedenceList.find(function (precedence) {
|
||||
return precedence.subscriptionHandle === subscriptionHandle;
|
||||
});
|
||||
}
|
||||
|
||||
if (elt) {
|
||||
if (elt === precedenceList[0] && !EJSON.equals(value, elt.value)) {
|
||||
// this subscription is changing the value of this field.
|
||||
changeCollector[key] = value;
|
||||
}
|
||||
elt.value = value;
|
||||
} else {
|
||||
// this subscription is newly caring about this field
|
||||
precedenceList.push({subscriptionHandle: subscriptionHandle, value: value});
|
||||
}
|
||||
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* Represents a client's view of a single collection
|
||||
* @param {String} collectionName Name of the collection it represents
|
||||
* @param {Object.<String, Function>} sessionCallbacks The callbacks for added, changed, removed
|
||||
* @class SessionCollectionView
|
||||
*/
|
||||
var SessionCollectionView = function (collectionName, sessionCallbacks) {
|
||||
var self = this;
|
||||
self.collectionName = collectionName;
|
||||
self.documents = new Map();
|
||||
self.callbacks = sessionCallbacks;
|
||||
};
|
||||
|
||||
DDPServer._SessionCollectionView = SessionCollectionView;
|
||||
|
||||
|
||||
Object.assign(SessionCollectionView.prototype, {
|
||||
|
||||
isEmpty: function () {
|
||||
var self = this;
|
||||
return self.documents.size === 0;
|
||||
},
|
||||
|
||||
diff: function (previous) {
|
||||
var self = this;
|
||||
DiffSequence.diffMaps(previous.documents, self.documents, {
|
||||
both: self.diffDocument.bind(self),
|
||||
|
||||
rightOnly: function (id, nowDV) {
|
||||
self.callbacks.added(self.collectionName, id, nowDV.getFields());
|
||||
},
|
||||
|
||||
leftOnly: function (id, prevDV) {
|
||||
self.callbacks.removed(self.collectionName, id);
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
diffDocument: function (id, prevDV, nowDV) {
|
||||
var self = this;
|
||||
var fields = {};
|
||||
DiffSequence.diffObjects(prevDV.getFields(), nowDV.getFields(), {
|
||||
both: function (key, prev, now) {
|
||||
if (!EJSON.equals(prev, now))
|
||||
fields[key] = now;
|
||||
},
|
||||
rightOnly: function (key, now) {
|
||||
fields[key] = now;
|
||||
},
|
||||
leftOnly: function(key, prev) {
|
||||
fields[key] = undefined;
|
||||
}
|
||||
});
|
||||
self.callbacks.changed(self.collectionName, id, fields);
|
||||
},
|
||||
|
||||
added: function (subscriptionHandle, id, fields) {
|
||||
var self = this;
|
||||
var docView = self.documents.get(id);
|
||||
var added = false;
|
||||
if (!docView) {
|
||||
added = true;
|
||||
if (Meteor.server.getPublicationStrategy(this.collectionName).useDummyDocumentView) {
|
||||
docView = new DummyDocumentView();
|
||||
} else {
|
||||
docView = new SessionDocumentView();
|
||||
}
|
||||
|
||||
self.documents.set(id, docView);
|
||||
}
|
||||
docView.existsIn.add(subscriptionHandle);
|
||||
var changeCollector = {};
|
||||
Object.entries(fields).forEach(function ([key, value]) {
|
||||
docView.changeField(
|
||||
subscriptionHandle, key, value, changeCollector, true);
|
||||
});
|
||||
if (added)
|
||||
self.callbacks.added(self.collectionName, id, changeCollector);
|
||||
else
|
||||
self.callbacks.changed(self.collectionName, id, changeCollector);
|
||||
},
|
||||
|
||||
changed: function (subscriptionHandle, id, changed) {
|
||||
var self = this;
|
||||
var changedResult = {};
|
||||
var docView = self.documents.get(id);
|
||||
if (!docView)
|
||||
throw new Error("Could not find element with id " + id + " to change");
|
||||
Object.entries(changed).forEach(function ([key, value]) {
|
||||
if (value === undefined)
|
||||
docView.clearField(subscriptionHandle, key, changedResult);
|
||||
else
|
||||
docView.changeField(subscriptionHandle, key, value, changedResult);
|
||||
});
|
||||
self.callbacks.changed(self.collectionName, id, changedResult);
|
||||
},
|
||||
|
||||
removed: function (subscriptionHandle, id) {
|
||||
var self = this;
|
||||
var docView = self.documents.get(id);
|
||||
if (!docView) {
|
||||
var err = new Error("Removed nonexistent document " + id);
|
||||
throw err;
|
||||
}
|
||||
docView.existsIn.delete(subscriptionHandle);
|
||||
if (docView.existsIn.size === 0) {
|
||||
// it is gone from everyone
|
||||
self.callbacks.removed(self.collectionName, id);
|
||||
self.documents.delete(id);
|
||||
} else {
|
||||
var changed = {};
|
||||
// remove this subscription from every precedence list
|
||||
// and record the changes
|
||||
docView.dataByKey.forEach(function (precedenceList, key) {
|
||||
docView.clearField(subscriptionHandle, key, changed);
|
||||
});
|
||||
|
||||
self.callbacks.changed(self.collectionName, id, changed);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
/******************************************************************************/
|
||||
/* Session */
|
||||
/******************************************************************************/
|
||||
@@ -636,7 +411,7 @@ Object.assign(Session.prototype, {
|
||||
if (!blocked)
|
||||
return; // idempotent
|
||||
blocked = false;
|
||||
processNext();
|
||||
setImmediate(processNext);
|
||||
};
|
||||
|
||||
self.server.onMessageHook.each(function (callback) {
|
||||
@@ -1576,33 +1351,33 @@ Object.assign(Server.prototype, {
|
||||
},
|
||||
|
||||
/**
|
||||
* @summary Set publication strategy for the given publication. Publications strategies are available from `DDPServer.publicationStrategies`. You call this method from `Meteor.server`, like `Meteor.server.setPublicationStrategy()`
|
||||
* @summary Set publication strategy for the given collection. Publications strategies are available from `DDPServer.publicationStrategies`. You call this method from `Meteor.server`, like `Meteor.server.setPublicationStrategy()`
|
||||
* @locus Server
|
||||
* @alias setPublicationStrategy
|
||||
* @param publicationName {String}
|
||||
* @param collectionName {String}
|
||||
* @param strategy {{useCollectionView: boolean, doAccountingForCollection: boolean}}
|
||||
* @memberOf Meteor.server
|
||||
* @importFromPackage meteor
|
||||
*/
|
||||
setPublicationStrategy(publicationName, strategy) {
|
||||
setPublicationStrategy(collectionName, strategy) {
|
||||
if (!Object.values(publicationStrategies).includes(strategy)) {
|
||||
throw new Error(`Invalid merge strategy: ${strategy}
|
||||
for collection ${publicationName}`);
|
||||
for collection ${collectionName}`);
|
||||
}
|
||||
this._publicationStrategies[publicationName] = strategy;
|
||||
this._publicationStrategies[collectionName] = strategy;
|
||||
},
|
||||
|
||||
/**
|
||||
* @summary Gets the publication strategy for the requested publication. You call this method from `Meteor.server`, like `Meteor.server.getPublicationStrategy()`
|
||||
* @summary Gets the publication strategy for the requested collection. You call this method from `Meteor.server`, like `Meteor.server.getPublicationStrategy()`
|
||||
* @locus Server
|
||||
* @alias getPublicationStrategy
|
||||
* @param publicationName {String}
|
||||
* @param collectionName {String}
|
||||
* @memberOf Meteor.server
|
||||
* @importFromPackage meteor
|
||||
* @return {{useCollectionView: boolean, doAccountingForCollection: boolean}}
|
||||
*/
|
||||
getPublicationStrategy(publicationName) {
|
||||
return this._publicationStrategies[publicationName]
|
||||
getPublicationStrategy(collectionName) {
|
||||
return this._publicationStrategies[collectionName]
|
||||
|| this.options.defaultPublicationStrategy;
|
||||
},
|
||||
|
||||
@@ -1794,7 +1569,6 @@ Object.assign(Server.prototype, {
|
||||
const options = args[0]?.hasOwnProperty('returnStubValue')
|
||||
? args.shift()
|
||||
: {};
|
||||
DDP._CurrentMethodInvocation._set();
|
||||
DDP._CurrentMethodInvocation._setCallAsyncMethodRunning(true);
|
||||
const promise = new Promise((resolve, reject) => {
|
||||
DDP._CurrentCallAsyncInvocation._set({ name, hasCallAsyncParent: true });
|
||||
|
||||
@@ -493,6 +493,96 @@ Tinytest.addAsync('livedata server - publish cursor is properly awaited', async
|
||||
cleanup()
|
||||
});
|
||||
|
||||
Tinytest.addAsync('livedata server - stopping a handle should preserve its context on callbacks', async function (test) {
|
||||
const { conn, messages, cleanup } = await captureConnectionMessages(test);
|
||||
|
||||
const coll = new Mongo.Collection('items', {
|
||||
defineMutationMethods: false,
|
||||
});
|
||||
|
||||
for (let i = 0; i < 10; i++) {
|
||||
await coll.removeAsync({ _id: `item_${i}` })
|
||||
await coll.insertAsync({ _id: `item_${i}`, title: `Item #${i}` });
|
||||
}
|
||||
|
||||
const publicationName = `publication_${Random.id()}`
|
||||
|
||||
delete Meteor.server.publish_handlers[publicationName];
|
||||
|
||||
Meteor.publish(publicationName, async function () {
|
||||
const user = {
|
||||
_id: 'user_id',
|
||||
customer: 'customer_id',
|
||||
}
|
||||
|
||||
if (user) {
|
||||
let count = 0;
|
||||
|
||||
let initializing = true;
|
||||
const handle = await coll.find({}).observeChangesAsync({
|
||||
added: () => {
|
||||
count += 1;
|
||||
if (!initializing) this.changed('issueUnreadCount', user._id, {count});
|
||||
},
|
||||
removed: () => {
|
||||
count -= 1;
|
||||
this.changed('issueUnreadCount', user._id, {count});
|
||||
}
|
||||
});
|
||||
|
||||
initializing = false;
|
||||
|
||||
this.added('issueUnreadCount', user._id, {count});
|
||||
|
||||
// Should be the same as `this.onStop(() => handle.stop())`
|
||||
this.onStop(handle.stop);
|
||||
|
||||
this.onStop(() => {
|
||||
// If stop is called and breaks for some reason, this will be false
|
||||
test.isTrue(handle._stopped)
|
||||
})
|
||||
|
||||
this.ready();
|
||||
}
|
||||
});
|
||||
|
||||
// Create multiple competing subscriptions
|
||||
const sub1 = conn.subscribe(publicationName);
|
||||
const sub2 = conn.subscribe(publicationName);
|
||||
const sub3 = conn.subscribe(publicationName);
|
||||
|
||||
// Make changes that will affect all subs
|
||||
await coll.insertAsync({ _id: 'item_10', title: 'Item #10' });
|
||||
|
||||
// Stop middle subscription during changes
|
||||
sub2.stop();
|
||||
|
||||
await coll.insertAsync({ _id: 'item_11', title: 'Item #11' });
|
||||
|
||||
// Create new subscription while changes happening
|
||||
const sub4 = conn.subscribe(publicationName);
|
||||
|
||||
await coll.removeAsync({ _id: 'item_10' });
|
||||
|
||||
sub1.stop();
|
||||
|
||||
await coll.insertAsync({ _id: 'item_12', title: 'Item #12' });
|
||||
|
||||
// Final subscription during teardown of others
|
||||
const sub5 = conn.subscribe(publicationName);
|
||||
|
||||
sub3.stop();
|
||||
sub4.stop();
|
||||
|
||||
await sleep(50);
|
||||
|
||||
sub5.stop();
|
||||
|
||||
await sleep(50);
|
||||
|
||||
cleanup();
|
||||
});
|
||||
|
||||
function getTestConnections(test) {
|
||||
return new Promise((resolve, reject) => {
|
||||
makeTestConnection(test, (clientConn, serverConn) => {
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
Package.describe({
|
||||
summary: "Meteor's latency-compensated distributed data server",
|
||||
version: "3.0.2",
|
||||
version: "3.1.0",
|
||||
documentation: null,
|
||||
});
|
||||
|
||||
Npm.depends({
|
||||
"permessage-deflate": "0.1.7",
|
||||
"permessage-deflate2": "0.1.8",
|
||||
sockjs: "0.3.24",
|
||||
"lodash.once": "4.1.1",
|
||||
"lodash.isempty": "4.4.0",
|
||||
@@ -23,6 +23,7 @@ Package.onUse(function (api) {
|
||||
"mongo-id",
|
||||
"diff-sequence",
|
||||
"ecmascript",
|
||||
"typescript",
|
||||
],
|
||||
"server"
|
||||
);
|
||||
|
||||
140
packages/ddp-server/session_collection_view.ts
Normal file
140
packages/ddp-server/session_collection_view.ts
Normal file
@@ -0,0 +1,140 @@
|
||||
import { DummyDocumentView } from "./dummy_document_view";
|
||||
import { SessionDocumentView } from "./session_document_view";
|
||||
|
||||
interface SessionCallbacks {
|
||||
added: (collectionName: string, id: string, fields: Record<string, any>) => void;
|
||||
changed: (collectionName: string, id: string, fields: Record<string, any>) => void;
|
||||
removed: (collectionName: string, id: string) => void;
|
||||
}
|
||||
|
||||
type DocumentView = SessionDocumentView | DummyDocumentView;
|
||||
|
||||
export class SessionCollectionView {
|
||||
private readonly collectionName: string;
|
||||
private readonly documents: Map<string, DocumentView>;
|
||||
private readonly callbacks: SessionCallbacks;
|
||||
|
||||
/**
|
||||
* Represents a client's view of a single collection
|
||||
* @param collectionName - Name of the collection it represents
|
||||
* @param sessionCallbacks - The callbacks for added, changed, removed
|
||||
*/
|
||||
constructor(collectionName: string, sessionCallbacks: SessionCallbacks) {
|
||||
this.collectionName = collectionName;
|
||||
this.documents = new Map();
|
||||
this.callbacks = sessionCallbacks;
|
||||
}
|
||||
|
||||
public isEmpty(): boolean {
|
||||
return this.documents.size === 0;
|
||||
}
|
||||
|
||||
public diff(previous: SessionCollectionView): void {
|
||||
DiffSequence.diffMaps(previous.documents, this.documents, {
|
||||
both: this.diffDocument.bind(this),
|
||||
rightOnly: (id: string, nowDV: DocumentView) => {
|
||||
this.callbacks.added(this.collectionName, id, nowDV.getFields());
|
||||
},
|
||||
leftOnly: (id: string, prevDV: DocumentView) => {
|
||||
this.callbacks.removed(this.collectionName, id);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private diffDocument(id: string, prevDV: DocumentView, nowDV: DocumentView): void {
|
||||
const fields: Record<string, any> = {};
|
||||
|
||||
DiffSequence.diffObjects(prevDV.getFields(), nowDV.getFields(), {
|
||||
both: (key: string, prev: any, now: any) => {
|
||||
if (!EJSON.equals(prev, now)) {
|
||||
fields[key] = now;
|
||||
}
|
||||
},
|
||||
rightOnly: (key: string, now: any) => {
|
||||
fields[key] = now;
|
||||
},
|
||||
leftOnly: (key: string, prev: any) => {
|
||||
fields[key] = undefined;
|
||||
}
|
||||
});
|
||||
|
||||
this.callbacks.changed(this.collectionName, id, fields);
|
||||
}
|
||||
|
||||
public added(subscriptionHandle: string, id: string, fields: Record<string, any>): void {
|
||||
let docView: DocumentView | undefined = this.documents.get(id);
|
||||
let added = false;
|
||||
|
||||
if (!docView) {
|
||||
added = true;
|
||||
if (Meteor.server.getPublicationStrategy(this.collectionName).useDummyDocumentView) {
|
||||
docView = new DummyDocumentView();
|
||||
} else {
|
||||
docView = new SessionDocumentView();
|
||||
}
|
||||
this.documents.set(id, docView);
|
||||
}
|
||||
|
||||
docView.existsIn.add(subscriptionHandle);
|
||||
const changeCollector: Record<string, any> = {};
|
||||
|
||||
Object.entries(fields).forEach(([key, value]) => {
|
||||
docView!.changeField(
|
||||
subscriptionHandle,
|
||||
key,
|
||||
value,
|
||||
changeCollector,
|
||||
true
|
||||
);
|
||||
});
|
||||
|
||||
if (added) {
|
||||
this.callbacks.added(this.collectionName, id, changeCollector);
|
||||
} else {
|
||||
this.callbacks.changed(this.collectionName, id, changeCollector);
|
||||
}
|
||||
}
|
||||
|
||||
public changed(subscriptionHandle: string, id: string, changed: Record<string, any>): void {
|
||||
const changedResult: Record<string, any> = {};
|
||||
const docView = this.documents.get(id);
|
||||
|
||||
if (!docView) {
|
||||
throw new Error(`Could not find element with id ${id} to change`);
|
||||
}
|
||||
|
||||
Object.entries(changed).forEach(([key, value]) => {
|
||||
if (value === undefined) {
|
||||
docView.clearField(subscriptionHandle, key, changedResult);
|
||||
} else {
|
||||
docView.changeField(subscriptionHandle, key, value, changedResult);
|
||||
}
|
||||
});
|
||||
|
||||
this.callbacks.changed(this.collectionName, id, changedResult);
|
||||
}
|
||||
|
||||
public removed(subscriptionHandle: string, id: string): void {
|
||||
const docView = this.documents.get(id);
|
||||
|
||||
if (!docView) {
|
||||
throw new Error(`Removed nonexistent document ${id}`);
|
||||
}
|
||||
|
||||
docView.existsIn.delete(subscriptionHandle);
|
||||
|
||||
if (docView.existsIn.size === 0) {
|
||||
// it is gone from everyone
|
||||
this.callbacks.removed(this.collectionName, id);
|
||||
this.documents.delete(id);
|
||||
} else {
|
||||
const changed: Record<string, any> = {};
|
||||
// remove this subscription from every precedence list
|
||||
// and record the changes
|
||||
docView.dataByKey.forEach((precedenceList, key) => {
|
||||
docView.clearField(subscriptionHandle, key, changed);
|
||||
});
|
||||
this.callbacks.changed(this.collectionName, id, changed);
|
||||
}
|
||||
}
|
||||
}
|
||||
106
packages/ddp-server/session_document_view.ts
Normal file
106
packages/ddp-server/session_document_view.ts
Normal file
@@ -0,0 +1,106 @@
|
||||
interface PrecedenceItem {
|
||||
subscriptionHandle: string;
|
||||
value: any;
|
||||
}
|
||||
|
||||
interface ChangeCollector {
|
||||
[key: string]: any;
|
||||
}
|
||||
|
||||
export class SessionDocumentView {
|
||||
private existsIn: Set<string>;
|
||||
private dataByKey: Map<string, PrecedenceItem[]>;
|
||||
|
||||
constructor() {
|
||||
this.existsIn = new Set(); // set of subscriptionHandle
|
||||
// Memory Growth
|
||||
this.dataByKey = new Map(); // key-> [ {subscriptionHandle, value} by precedence]
|
||||
}
|
||||
|
||||
getFields(): Record<string, any> {
|
||||
const ret: Record<string, any> = {};
|
||||
this.dataByKey.forEach((precedenceList, key) => {
|
||||
ret[key] = precedenceList[0].value;
|
||||
});
|
||||
return ret;
|
||||
}
|
||||
|
||||
clearField(
|
||||
subscriptionHandle: string,
|
||||
key: string,
|
||||
changeCollector: ChangeCollector
|
||||
): void {
|
||||
// Publish API ignores _id if present in fields
|
||||
if (key === "_id") return;
|
||||
|
||||
const precedenceList = this.dataByKey.get(key);
|
||||
// It's okay to clear fields that didn't exist. No need to throw
|
||||
// an error.
|
||||
if (!precedenceList) return;
|
||||
|
||||
let removedValue: any = undefined;
|
||||
|
||||
for (let i = 0; i < precedenceList.length; i++) {
|
||||
const precedence = precedenceList[i];
|
||||
if (precedence.subscriptionHandle === subscriptionHandle) {
|
||||
// The view's value can only change if this subscription is the one that
|
||||
// used to have precedence.
|
||||
if (i === 0) removedValue = precedence.value;
|
||||
precedenceList.splice(i, 1);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (precedenceList.length === 0) {
|
||||
this.dataByKey.delete(key);
|
||||
changeCollector[key] = undefined;
|
||||
} else if (
|
||||
removedValue !== undefined &&
|
||||
!EJSON.equals(removedValue, precedenceList[0].value)
|
||||
) {
|
||||
changeCollector[key] = precedenceList[0].value;
|
||||
}
|
||||
}
|
||||
|
||||
changeField(
|
||||
subscriptionHandle: string,
|
||||
key: string,
|
||||
value: any,
|
||||
changeCollector: ChangeCollector,
|
||||
isAdd: boolean = false
|
||||
): void {
|
||||
// Publish API ignores _id if present in fields
|
||||
if (key === "_id") return;
|
||||
|
||||
// Don't share state with the data passed in by the user.
|
||||
value = EJSON.clone(value);
|
||||
|
||||
if (!this.dataByKey.has(key)) {
|
||||
this.dataByKey.set(key, [
|
||||
{ subscriptionHandle: subscriptionHandle, value: value },
|
||||
]);
|
||||
changeCollector[key] = value;
|
||||
return;
|
||||
}
|
||||
|
||||
const precedenceList = this.dataByKey.get(key)!;
|
||||
let elt: PrecedenceItem | undefined;
|
||||
|
||||
if (!isAdd) {
|
||||
elt = precedenceList.find(
|
||||
(precedence) => precedence.subscriptionHandle === subscriptionHandle
|
||||
);
|
||||
}
|
||||
|
||||
if (elt) {
|
||||
if (elt === precedenceList[0] && !EJSON.equals(value, elt.value)) {
|
||||
// this subscription is changing the value of this field.
|
||||
changeCollector[key] = value;
|
||||
}
|
||||
elt.value = value;
|
||||
} else {
|
||||
// this subscription is newly caring about this field
|
||||
precedenceList.push({ subscriptionHandle: subscriptionHandle, value: value });
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
import once from 'lodash.once';
|
||||
import zlib from 'node:zlib';
|
||||
|
||||
// By default, we use the permessage-deflate extension with default
|
||||
// configuration. If $SERVER_WEBSOCKET_COMPRESSION is set, then it must be valid
|
||||
@@ -14,12 +15,18 @@ import once from 'lodash.once';
|
||||
var websocketExtensions = once(function () {
|
||||
var extensions = [];
|
||||
|
||||
var websocketCompressionConfig = process.env.SERVER_WEBSOCKET_COMPRESSION
|
||||
? JSON.parse(process.env.SERVER_WEBSOCKET_COMPRESSION) : {};
|
||||
var websocketCompressionConfig = process.env.SERVER_WEBSOCKET_COMPRESSION ?
|
||||
JSON.parse(process.env.SERVER_WEBSOCKET_COMPRESSION) : {};
|
||||
|
||||
if (websocketCompressionConfig) {
|
||||
extensions.push(Npm.require('permessage-deflate').configure(
|
||||
websocketCompressionConfig
|
||||
));
|
||||
extensions.push(Npm.require('permessage-deflate2').configure({
|
||||
threshold: 1024,
|
||||
level: zlib.constants.Z_BEST_SPEED,
|
||||
memLevel: zlib.constants.Z_MIN_MEMLEVEL,
|
||||
noContextTakeover: true,
|
||||
maxWindowBits: zlib.constants.Z_MIN_WINDOWBITS,
|
||||
...(websocketCompressionConfig || {})
|
||||
}));
|
||||
}
|
||||
|
||||
return extensions;
|
||||
|
||||
@@ -1,7 +1,3 @@
|
||||
// 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;
|
||||
@@ -12,58 +8,49 @@ DDPServer._WriteFence = class {
|
||||
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.retired) {
|
||||
return { committed: () => {} };
|
||||
}
|
||||
|
||||
if (this.fired)
|
||||
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();
|
||||
};
|
||||
|
||||
return {
|
||||
committed: _committedFn,
|
||||
committed: async () => {
|
||||
if (committed) {
|
||||
throw new Error("committed called twice on the same write");
|
||||
}
|
||||
committed = true;
|
||||
this.outstanding_writes--;
|
||||
await this._maybeFire();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// Arm the fence. Once the fence is armed, and there are no more
|
||||
// uncommitted writes, it will activate.
|
||||
arm() {
|
||||
|
||||
if (this === DDPServer._getCurrentFence())
|
||||
if (this === DDPServer._getCurrentFence()) {
|
||||
throw Error("Can't arm the current fence");
|
||||
}
|
||||
this.armed = true;
|
||||
return 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");
|
||||
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");
|
||||
if (this.fired) {
|
||||
throw new Error("fence has already activated -- too late to add a callback");
|
||||
}
|
||||
this.completion_callbacks.push(func);
|
||||
}
|
||||
|
||||
@@ -72,56 +59,54 @@ DDPServer._WriteFence = class {
|
||||
const returnValue = new Promise(r => resolver = r);
|
||||
this.onAllCommitted(resolver);
|
||||
await this.arm();
|
||||
|
||||
return returnValue;
|
||||
}
|
||||
// Convenience function. Arms the fence, then blocks until it fires.
|
||||
async armAndWait() {
|
||||
|
||||
armAndWait() {
|
||||
return this._armAndWait();
|
||||
}
|
||||
|
||||
async _maybeFire() {
|
||||
if (this.fired)
|
||||
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.armed || this.outstanding_writes > 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!this.outstanding_writes) {
|
||||
this.fired = true;
|
||||
const callbacks = this.completion_callbacks || [];
|
||||
this.completion_callbacks = [];
|
||||
while (callbacks.length > 0) {
|
||||
const cb = callbacks.shift();
|
||||
await invokeCallback(cb);
|
||||
}
|
||||
const invokeCallback = async (func) => {
|
||||
try {
|
||||
await func(this);
|
||||
} catch (err) {
|
||||
Meteor._debug("exception in write fence callback:", err);
|
||||
}
|
||||
};
|
||||
|
||||
this.outstanding_writes++;
|
||||
|
||||
// Process all before_fire callbacks in parallel
|
||||
const beforeCallbacks = [...this.before_fire_callbacks];
|
||||
this.before_fire_callbacks = [];
|
||||
await Promise.all(beforeCallbacks.map(cb => invokeCallback(cb)));
|
||||
|
||||
this.outstanding_writes--;
|
||||
|
||||
if (this.outstanding_writes === 0) {
|
||||
this.fired = true;
|
||||
// Process all completion callbacks in parallel
|
||||
const callbacks = [...this.completion_callbacks];
|
||||
this.completion_callbacks = [];
|
||||
await Promise.all(callbacks.map(cb => invokeCallback(cb)));
|
||||
}
|
||||
}
|
||||
|
||||
// Deactivate this fence so that adding more writes has no effect.
|
||||
// The fence must have already fired.
|
||||
retire() {
|
||||
if (!this.fired)
|
||||
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;
|
||||
DDPServer._CurrentWriteFence = new Meteor.EnvironmentVariable;
|
||||
@@ -1,20 +0,0 @@
|
||||
{
|
||||
"lockfileVersion": 4,
|
||||
"dependencies": {
|
||||
"basic-auth": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/basic-auth/-/basic-auth-2.0.1.tgz",
|
||||
"integrity": "sha512-NF+epuEdnUYVlGuhaxbbq+dvJttwLnGY+YixlXlME5KpQ5W3CnXA5cVTneY3SPbPDRkcjMbifrwmFYcClgOZeg=="
|
||||
},
|
||||
"express-basic-auth": {
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmjs.org/express-basic-auth/-/express-basic-auth-1.2.1.tgz",
|
||||
"integrity": "sha512-L6YQ1wQ/mNjVLAmK3AG1RK6VkokA1BIY6wmiH304Xtt/cLTps40EusZsU1Uop+v9lTDPxdtzbFmdXfFO3KEnwA=="
|
||||
},
|
||||
"safe-buffer": {
|
||||
"version": "5.1.2",
|
||||
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
|
||||
"integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,57 +1,58 @@
|
||||
allow-deny@2.0.0-alpha300.18
|
||||
babel-compiler@7.11.0-alpha300.18
|
||||
babel-runtime@1.5.2-alpha300.18
|
||||
base64@1.0.13-alpha300.18
|
||||
binary-heap@1.0.12-alpha300.18
|
||||
blaze@3.0.0-alpha300.16
|
||||
boilerplate-generator@2.0.0-alpha300.18
|
||||
callback-hook@1.6.0-alpha300.18
|
||||
check@1.3.3-alpha300.18
|
||||
core-runtime@1.0.0-alpha300.18
|
||||
ddp@1.4.2-alpha300.18
|
||||
ddp-client@3.0.0-alpha300.18
|
||||
ddp-common@1.4.1-alpha300.18
|
||||
ddp-server@3.0.0-alpha300.18
|
||||
diff-sequence@1.1.3-alpha300.18
|
||||
dynamic-import@0.7.4-alpha300.18
|
||||
ecmascript@0.16.8-alpha300.18
|
||||
ecmascript-runtime@0.8.2-alpha300.18
|
||||
ecmascript-runtime-client@0.12.2-alpha300.18
|
||||
ecmascript-runtime-server@0.11.1-alpha300.18
|
||||
ejson@1.1.4-alpha300.18
|
||||
facts-base@1.0.2-alpha300.18
|
||||
fetch@0.1.4-alpha300.18
|
||||
geojson-utils@1.0.12-alpha300.18
|
||||
htmljs@2.0.0-alpha300.16
|
||||
http@3.0.0-alpha300.18
|
||||
id-map@1.2.0-alpha300.18
|
||||
inter-process-messaging@0.1.2-alpha300.18
|
||||
local-test:http@3.0.0-alpha300.18
|
||||
logging@1.3.3-alpha300.18
|
||||
meteor@2.0.0-alpha300.18
|
||||
minimongo@2.0.0-alpha300.18
|
||||
modern-browsers@0.1.10-alpha300.18
|
||||
modules@0.19.1-alpha300.18
|
||||
modules-runtime@0.13.2-alpha300.18
|
||||
mongo@2.0.0-alpha300.18
|
||||
mongo-decimal@0.1.4-alpha300.18
|
||||
mongo-dev-server@1.1.1-alpha300.18
|
||||
mongo-id@1.0.9-alpha300.18
|
||||
npm-mongo@4.16.1-alpha300.18
|
||||
observe-sequence@2.0.0-alpha300.16
|
||||
ordered-dict@1.2.0-alpha300.18
|
||||
promise@1.0.0-alpha300.18
|
||||
random@1.2.2-alpha300.18
|
||||
react-fast-refresh@0.2.8-alpha300.18
|
||||
reactive-var@1.0.13-alpha300.18
|
||||
reload@1.3.2-alpha300.18
|
||||
retry@1.1.1-alpha300.18
|
||||
routepolicy@1.1.2-alpha300.18
|
||||
socket-stream-client@0.5.2-alpha300.18
|
||||
test-helpers@2.0.0-alpha300.18
|
||||
tinytest@2.0.0-alpha300.18
|
||||
tracker@1.3.3-alpha300.18
|
||||
underscore@1.0.14-alpha300.18
|
||||
url@1.3.2
|
||||
webapp@2.0.0-alpha300.18
|
||||
webapp-hashing@1.1.2-alpha300.18
|
||||
allow-deny@2.0.0
|
||||
babel-compiler@7.11.1
|
||||
babel-runtime@1.5.2
|
||||
base64@1.0.13
|
||||
binary-heap@1.0.12
|
||||
blaze@3.0.0
|
||||
boilerplate-generator@2.0.0
|
||||
callback-hook@1.6.0
|
||||
check@1.4.4
|
||||
core-runtime@1.0.0
|
||||
ddp@1.4.2
|
||||
ddp-client@3.0.2
|
||||
ddp-common@1.4.4
|
||||
ddp-server@3.0.2
|
||||
diff-sequence@1.1.3
|
||||
dynamic-import@0.7.4
|
||||
ecmascript@0.16.9
|
||||
ecmascript-runtime@0.8.3
|
||||
ecmascript-runtime-client@0.12.2
|
||||
ecmascript-runtime-server@0.11.1
|
||||
ejson@1.1.4
|
||||
facts-base@1.0.2
|
||||
fetch@0.1.5
|
||||
geojson-utils@1.0.12
|
||||
htmljs@2.0.1
|
||||
http@3.0.0
|
||||
id-map@1.2.0
|
||||
inter-process-messaging@0.1.2
|
||||
local-test:http@3.0.0
|
||||
logging@1.3.5
|
||||
meteor@2.0.1
|
||||
minimongo@2.0.1
|
||||
modern-browsers@0.1.11
|
||||
modules@0.20.2
|
||||
modules-runtime@0.13.2
|
||||
mongo@2.0.2
|
||||
mongo-decimal@0.1.4
|
||||
mongo-dev-server@1.1.1
|
||||
mongo-id@1.0.9
|
||||
npm-mongo@4.17.4
|
||||
observe-sequence@2.0.0
|
||||
ordered-dict@1.2.0
|
||||
promise@1.0.0
|
||||
random@1.2.2
|
||||
react-fast-refresh@0.2.9
|
||||
reactive-var@1.0.13
|
||||
reload@1.3.2
|
||||
retry@1.1.1
|
||||
routepolicy@1.1.2
|
||||
socket-stream-client@0.5.3
|
||||
test-helpers@2.0.1
|
||||
tinytest@1.3.0
|
||||
tracker@1.3.4
|
||||
typescript@5.4.3
|
||||
underscore@1.6.4
|
||||
url@1.3.4
|
||||
webapp@2.0.3
|
||||
webapp-hashing@1.1.2
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
Package.describe({
|
||||
summary: "Make HTTP calls to remote servers",
|
||||
version: '3.0.0-beta300.7',
|
||||
version: '3.0.0',
|
||||
deprecated: 'Please use the fetch package'
|
||||
});
|
||||
|
||||
|
||||
@@ -1,154 +0,0 @@
|
||||
{
|
||||
"lockfileVersion": 4,
|
||||
"dependencies": {
|
||||
"balanced-match": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
|
||||
"integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="
|
||||
},
|
||||
"brace-expansion": {
|
||||
"version": "1.1.11",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
|
||||
"integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA=="
|
||||
},
|
||||
"cli": {
|
||||
"version": "0.6.6",
|
||||
"resolved": "https://registry.npmjs.org/cli/-/cli-0.6.6.tgz",
|
||||
"integrity": "sha512-4H6IzYk78R+VBeJ3fH3VQejcQRkGPR+kMjA9n30srEN+YVMPJLHfoQDtLquIzcLnfrlUrVA8qSQRB9fdgWpUBw=="
|
||||
},
|
||||
"concat-map": {
|
||||
"version": "0.0.1",
|
||||
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
|
||||
"integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg=="
|
||||
},
|
||||
"console-browserify": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/console-browserify/-/console-browserify-1.1.0.tgz",
|
||||
"integrity": "sha512-duS7VP5pvfsNLDvL1O4VOEbw37AI3A4ZUQYemvDlnpGrNu9tprR7BYWpDYwC0Xia0Zxz5ZupdiIrUp0GH1aXfg=="
|
||||
},
|
||||
"core-util-is": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz",
|
||||
"integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ=="
|
||||
},
|
||||
"date-now": {
|
||||
"version": "0.1.4",
|
||||
"resolved": "https://registry.npmjs.org/date-now/-/date-now-0.1.4.tgz",
|
||||
"integrity": "sha512-AsElvov3LoNB7tf5k37H2jYSB+ZZPMT5sG2QjJCcdlV5chIv6htBUBUui2IKRjgtKAKtCBN7Zbwa+MtwLjSeNw=="
|
||||
},
|
||||
"dom-serializer": {
|
||||
"version": "0.2.2",
|
||||
"resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-0.2.2.tgz",
|
||||
"integrity": "sha512-2/xPb3ORsQ42nHYiSunXkDjPLBaEj/xTwUO4B7XCZQTRk7EBtTOPaygh10YAAh2OI1Qrp6NWfpAhzswj0ydt9g==",
|
||||
"dependencies": {
|
||||
"domelementtype": {
|
||||
"version": "2.3.0",
|
||||
"resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz",
|
||||
"integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw=="
|
||||
},
|
||||
"entities": {
|
||||
"version": "2.2.0",
|
||||
"resolved": "https://registry.npmjs.org/entities/-/entities-2.2.0.tgz",
|
||||
"integrity": "sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A=="
|
||||
}
|
||||
}
|
||||
},
|
||||
"domelementtype": {
|
||||
"version": "1.3.1",
|
||||
"resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-1.3.1.tgz",
|
||||
"integrity": "sha512-BSKB+TSpMpFI/HOxCNr1O8aMOTZ8hT3pM3GQ0w/mWRmkhEDSFJkkyzz4XQsBV44BChwGkrDfMyjVD0eA2aFV3w=="
|
||||
},
|
||||
"domhandler": {
|
||||
"version": "2.3.0",
|
||||
"resolved": "https://registry.npmjs.org/domhandler/-/domhandler-2.3.0.tgz",
|
||||
"integrity": "sha512-q9bUwjfp7Eif8jWxxxPSykdRZAb6GkguBGSgvvCrhI9wB71W2K/Kvv4E61CF/mcCfnVJDeDWx/Vb/uAqbDj6UQ=="
|
||||
},
|
||||
"domutils": {
|
||||
"version": "1.5.1",
|
||||
"resolved": "https://registry.npmjs.org/domutils/-/domutils-1.5.1.tgz",
|
||||
"integrity": "sha512-gSu5Oi/I+3wDENBsOWBiRK1eoGxcywYSqg3rR960/+EfY0CF4EX1VPkgHOZ3WiS/Jg2DtliF6BhWcHlfpYUcGw=="
|
||||
},
|
||||
"entities": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/entities/-/entities-1.0.0.tgz",
|
||||
"integrity": "sha512-LbLqfXgJMmy81t+7c14mnulFHJ170cM6E+0vMXR9k/ZiZwgX8i5pNgjTCX3SO4VeUsFLV+8InixoretwU+MjBQ=="
|
||||
},
|
||||
"exit": {
|
||||
"version": "0.1.2",
|
||||
"resolved": "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz",
|
||||
"integrity": "sha512-Zk/eNKV2zbjpKzrsQ+n1G6poVbErQxJ0LBOJXaKZ1EViLzH+hrLu9cdXI4zw9dBQJslwBEpbQ2P1oS7nDxs6jQ=="
|
||||
},
|
||||
"glob": {
|
||||
"version": "3.2.11",
|
||||
"resolved": "https://registry.npmjs.org/glob/-/glob-3.2.11.tgz",
|
||||
"integrity": "sha512-hVb0zwEZwC1FXSKRPFTeOtN7AArJcJlI6ULGLtrstaswKNlrTJqAA+1lYlSUop4vjA423xlBzqfVS3iWGlqJ+g==",
|
||||
"dependencies": {
|
||||
"minimatch": {
|
||||
"version": "0.3.0",
|
||||
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-0.3.0.tgz",
|
||||
"integrity": "sha512-WFX1jI1AaxNTZVOHLBVazwTWKaQjoykSzCBNXB72vDTCzopQGtyP91tKdFK5cv1+qMwPyiTu1HqUriqplI8pcA=="
|
||||
}
|
||||
}
|
||||
},
|
||||
"htmlparser2": {
|
||||
"version": "3.8.3",
|
||||
"resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-3.8.3.tgz",
|
||||
"integrity": "sha512-hBxEg3CYXe+rPIua8ETe7tmG3XDn9B0edOE/e9wH2nLczxzgdu0m0aNHY+5wFZiviLWLdANPJTssa92dMcXQ5Q=="
|
||||
},
|
||||
"inherits": {
|
||||
"version": "2.0.4",
|
||||
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
|
||||
"integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="
|
||||
},
|
||||
"isarray": {
|
||||
"version": "0.0.1",
|
||||
"resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz",
|
||||
"integrity": "sha512-D2S+3GLxWH+uhrNEcoh/fnmYeP8E8/zHl644d/jdA0g2uyXvy3sb0qxotE+ne0LtccHknQzWwZEzhak7oJ0COQ=="
|
||||
},
|
||||
"jshint": {
|
||||
"version": "2.7.0",
|
||||
"resolved": "https://registry.npmjs.org/jshint/-/jshint-2.7.0.tgz",
|
||||
"integrity": "sha512-omn1ROF3q3//EWz+XkKMT1P7pHnJE8wqcpJ8AUk13nNFugVzzDeGJW8S4dtGXW6hYB5dlSy90zVbRojolLMSwA=="
|
||||
},
|
||||
"lodash": {
|
||||
"version": "3.6.0",
|
||||
"resolved": "https://registry.npmjs.org/lodash/-/lodash-3.6.0.tgz",
|
||||
"integrity": "sha512-fysFKsJtaOtRGZT/b3Xx03iyEmO0zjU+d1HBH5NcEaUjtg7XO0wDY5I7IJFfr2rguJt0Rve2V32426Za3zYyRw=="
|
||||
},
|
||||
"lru-cache": {
|
||||
"version": "2.7.3",
|
||||
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-2.7.3.tgz",
|
||||
"integrity": "sha512-WpibWJ60c3AgAz8a2iYErDrcT2C7OmKnsWhIcHOjkUHFjkXncJhtLxNSqUmxRxRunpb5I8Vprd7aNSd2NtksJQ=="
|
||||
},
|
||||
"minimatch": {
|
||||
"version": "2.0.10",
|
||||
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-2.0.10.tgz",
|
||||
"integrity": "sha512-jQo6o1qSVLEWaw3l+bwYA2X0uLuK2KjNh2wjgO7Q/9UJnXr1Q3yQKR8BI0/Bt/rPg75e6SMW4hW/6cBHVTZUjA=="
|
||||
},
|
||||
"readable-stream": {
|
||||
"version": "1.1.14",
|
||||
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz",
|
||||
"integrity": "sha512-+MeVjFf4L44XUkhM1eYbD8fyEsxcV81pqMSR5gblfcLCHfZvbrqy4/qYHE+/R5HoBUT11WV5O08Cr1n3YXkWVQ=="
|
||||
},
|
||||
"shelljs": {
|
||||
"version": "0.3.0",
|
||||
"resolved": "https://registry.npmjs.org/shelljs/-/shelljs-0.3.0.tgz",
|
||||
"integrity": "sha512-Ny0KN4dyT8ZSCE0frtcbAJGoM/HTArpyPkeli1/00aYfm0sbD/Gk/4x7N2DP9QKGpBsiQH7n6rpm1L79RtviEQ=="
|
||||
},
|
||||
"sigmund": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/sigmund/-/sigmund-1.0.1.tgz",
|
||||
"integrity": "sha512-fCvEXfh6NWpm+YSuY2bpXb/VIihqWA6hLsgboC+0nl71Q7N7o2eaCW8mJa/NLvQhs6jpd3VZV4UiUQlV6+lc8g=="
|
||||
},
|
||||
"string_decoder": {
|
||||
"version": "0.10.31",
|
||||
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz",
|
||||
"integrity": "sha512-ev2QzSzWPYmy9GuqfIVildA4OdcGLeFZQrq5ys6RtiuF+RQQiZWr8TZNyAcuVXyQRYfEO+MsoB/1BuQVhOJuoQ=="
|
||||
},
|
||||
"strip-json-comments": {
|
||||
"version": "1.0.4",
|
||||
"resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-1.0.4.tgz",
|
||||
"integrity": "sha512-AOPG8EBc5wAikaG1/7uFCNFJwnKOuQwFTpYBdTW6OvWHeZBQBrAA/amefHGrEiOnCPcLFZK6FUPtWVKpQVIRgg=="
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,177 +0,0 @@
|
||||
{
|
||||
"lockfileVersion": 4,
|
||||
"dependencies": {
|
||||
"amdefine": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/amdefine/-/amdefine-1.0.1.tgz",
|
||||
"integrity": "sha512-S2Hw0TtNkMJhIabBwIojKL9YHO5T0n5eNqWJ7Lrlel/zDbftQpxpapi8tZs3X1HWa+u+QeydGmzzNU0m09+Rcg=="
|
||||
},
|
||||
"autoprefixer": {
|
||||
"version": "6.3.7",
|
||||
"resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-6.3.7.tgz",
|
||||
"integrity": "sha512-xnArQBxKETltXW1R/ZrmlaslmU5vF4huqAw0iARn1VXXc8TztdtWQJ9myUe/ywZbG7tvErKQ7hZORBf7G8fArQ=="
|
||||
},
|
||||
"autoprefixer-stylus": {
|
||||
"version": "0.9.4",
|
||||
"resolved": "https://registry.npmjs.org/autoprefixer-stylus/-/autoprefixer-stylus-0.9.4.tgz",
|
||||
"integrity": "sha512-LTcjRdT4sRvfA6FkhRS6HuEtRm/GNFDr3+egHSr4/7oyKUjYJpZCCakpO4hhi/l3NahnJihokz0iaGb1boA4rw=="
|
||||
},
|
||||
"balanced-match": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
|
||||
"integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="
|
||||
},
|
||||
"brace-expansion": {
|
||||
"version": "1.1.11",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
|
||||
"integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA=="
|
||||
},
|
||||
"browserslist": {
|
||||
"version": "1.3.6",
|
||||
"resolved": "https://registry.npmjs.org/browserslist/-/browserslist-1.3.6.tgz",
|
||||
"integrity": "sha512-fKSWtyNQTclfi1A+s2KU91/r1mfANG1ZibxTdCwJGfV1J9UwcV22plFOm0wkaq4WzqW87zxiAkyp2Ho1Wn1NnA=="
|
||||
},
|
||||
"caniuse-db": {
|
||||
"version": "1.0.30001640",
|
||||
"resolved": "https://registry.npmjs.org/caniuse-db/-/caniuse-db-1.0.30001640.tgz",
|
||||
"integrity": "sha512-K8/5iWoH/NULlqJz/iaopQJraQCHGcFGvs8dmTpAH7GyvoQu2Xq8ht3jq2c+wNck4bgQu/PHu2GN2mJfUj9qtw=="
|
||||
},
|
||||
"concat-map": {
|
||||
"version": "0.0.1",
|
||||
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
|
||||
"integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg=="
|
||||
},
|
||||
"css-parse": {
|
||||
"version": "1.7.0",
|
||||
"resolved": "https://registry.npmjs.org/css-parse/-/css-parse-1.7.0.tgz",
|
||||
"integrity": "sha512-OI38lO4JQQX2GSisTqwiSFxiWNmLajXdW4tCCxAuiwGKjusHALQadSHBSxGlU8lrFp47IkLuU2AfSYz31qpETQ=="
|
||||
},
|
||||
"debug": {
|
||||
"version": "4.3.5",
|
||||
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.5.tgz",
|
||||
"integrity": "sha512-pt0bNEmneDIvdL1Xsd9oDQ/wrQRkXDT4AUWlNZNPKvW5x/jyO9VFXkJUP07vQ2upmw5PlaITaPKc31jK13V+jg=="
|
||||
},
|
||||
"fs.realpath": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
|
||||
"integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw=="
|
||||
},
|
||||
"glob": {
|
||||
"version": "7.0.6",
|
||||
"resolved": "https://registry.npmjs.org/glob/-/glob-7.0.6.tgz",
|
||||
"integrity": "sha512-f8c0rE8JiCxpa52kWPAOa3ZaYEnzofDzCQLCn3Vdk0Z5OVLq3BsRFJI4S4ykpeVW6QMGBUkMeUpoEgWnMTnw5Q=="
|
||||
},
|
||||
"has-flag": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-1.0.0.tgz",
|
||||
"integrity": "sha512-DyYHfIYwAJmjAjSSPKANxI8bFY9YtFrgkAfinBojQ8YJTOuOuav64tMUJv584SES4xl74PmuaevIyaLESHdTAA=="
|
||||
},
|
||||
"inflight": {
|
||||
"version": "1.0.6",
|
||||
"resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
|
||||
"integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA=="
|
||||
},
|
||||
"inherits": {
|
||||
"version": "2.0.4",
|
||||
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
|
||||
"integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="
|
||||
},
|
||||
"js-base64": {
|
||||
"version": "2.6.4",
|
||||
"resolved": "https://registry.npmjs.org/js-base64/-/js-base64-2.6.4.tgz",
|
||||
"integrity": "sha512-pZe//GGmwJndub7ZghVHz7vjb2LgC1m8B07Au3eYqeqv9emhESByMXxaEgkUkEqJe87oBbSniGYoQNIBklc7IQ=="
|
||||
},
|
||||
"minimatch": {
|
||||
"version": "3.1.2",
|
||||
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
|
||||
"integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw=="
|
||||
},
|
||||
"minimist": {
|
||||
"version": "1.2.8",
|
||||
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz",
|
||||
"integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA=="
|
||||
},
|
||||
"mkdirp": {
|
||||
"version": "0.5.6",
|
||||
"resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz",
|
||||
"integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw=="
|
||||
},
|
||||
"ms": {
|
||||
"version": "2.1.2",
|
||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
|
||||
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
|
||||
},
|
||||
"multi-stage-sourcemap": {
|
||||
"version": "0.2.1",
|
||||
"resolved": "https://registry.npmjs.org/multi-stage-sourcemap/-/multi-stage-sourcemap-0.2.1.tgz",
|
||||
"integrity": "sha512-umaOM+8BZByZIB/ciD3dQLzTv50rEkkGJV78ta/tIVc/J/rfGZY5y1R+fBD3oTaolx41mK8rRcyGtYbDXlzx8Q=="
|
||||
},
|
||||
"nib": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/nib/-/nib-1.1.2.tgz",
|
||||
"integrity": "sha512-xBpZ9XU0vLOxp0GBTuUHt6Kcl37ZpC/rXPpKcK4LYOUnSmqp2CXkcNiJxf9bgNZeivYR6bxsaZkNHZ9deEMupQ=="
|
||||
},
|
||||
"normalize-range": {
|
||||
"version": "0.1.2",
|
||||
"resolved": "https://registry.npmjs.org/normalize-range/-/normalize-range-0.1.2.tgz",
|
||||
"integrity": "sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA=="
|
||||
},
|
||||
"num2fraction": {
|
||||
"version": "1.2.2",
|
||||
"resolved": "https://registry.npmjs.org/num2fraction/-/num2fraction-1.2.2.tgz",
|
||||
"integrity": "sha512-Y1wZESM7VUThYY+4W+X4ySH2maqcA+p7UR+w8VWNWVAd6lwuXXWz/w/Cz43J/dI2I+PS6wD5N+bJUF+gjWvIqg=="
|
||||
},
|
||||
"once": {
|
||||
"version": "1.4.0",
|
||||
"resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
|
||||
"integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w=="
|
||||
},
|
||||
"path-is-absolute": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
|
||||
"integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg=="
|
||||
},
|
||||
"postcss": {
|
||||
"version": "5.0.21",
|
||||
"resolved": "https://registry.npmjs.org/postcss/-/postcss-5.0.21.tgz",
|
||||
"integrity": "sha512-/UdnZhOe5WC0Kvts13bNLPREqhaU0ntLQ1v29S5ofLx38zP+WhM0sjhVzrPrIQwKwXhtf8byfH+BROc3t2YQRg==",
|
||||
"dependencies": {
|
||||
"source-map": {
|
||||
"version": "0.5.7",
|
||||
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz",
|
||||
"integrity": "sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ=="
|
||||
}
|
||||
}
|
||||
},
|
||||
"postcss-value-parser": {
|
||||
"version": "3.3.1",
|
||||
"resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz",
|
||||
"integrity": "sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ=="
|
||||
},
|
||||
"sax": {
|
||||
"version": "0.5.8",
|
||||
"resolved": "https://registry.npmjs.org/sax/-/sax-0.5.8.tgz",
|
||||
"integrity": "sha512-c0YL9VcSfcdH3F1Qij9qpYJFpKFKMXNOkLWFssBL3RuF7ZS8oZhllR2rWlCRjDTJsfq3R6wbSsaRU6o0rkEdNw=="
|
||||
},
|
||||
"source-map": {
|
||||
"version": "0.1.43",
|
||||
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.1.43.tgz",
|
||||
"integrity": "sha512-VtCvB9SIQhk3aF6h+N85EaqIaBFIAfZ9Cu+NJHHVvc8BbEcnvDcFw6sqQ2dQrT6SlOrZq3tIvyD9+EGq/lJryQ=="
|
||||
},
|
||||
"stylus": {
|
||||
"version": "0.54.5",
|
||||
"resolved": "https://github.com/meteor/stylus/tarball/bb47a357d132ca843718c63998eb37b90013a449",
|
||||
"integrity": "sha512-j6fvtoNfjx/TEIlIOZ53OqbP6uDdF5HsQidsRfvp0IfW0D5PCtV8IeHVQa4jjbhF9PbjOXX/rrt5lP4CGpgtfw=="
|
||||
},
|
||||
"supports-color": {
|
||||
"version": "3.2.3",
|
||||
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-3.2.3.tgz",
|
||||
"integrity": "sha512-Jds2VIYDrlp5ui7t8abHN2bjAu4LV/q4N2KivFPpGH0lrka0BMq/33AmECUXlKPcHigkNaqfXRENFju+rlcy+A=="
|
||||
},
|
||||
"wrappy": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
|
||||
"integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ=="
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3,6 +3,7 @@ Package.describe({
|
||||
// Tinytest depends on underscore
|
||||
summary: "Tests for the underscore package",
|
||||
version: '1.0.10',
|
||||
deprecated: true
|
||||
});
|
||||
|
||||
Package.onTest(function (api) {
|
||||
@@ -2,6 +2,7 @@
|
||||
Package.describe({
|
||||
summary: "Collection of small helpers: _.map, _.each, ...",
|
||||
version: '1.6.4',
|
||||
deprecated: true
|
||||
});
|
||||
|
||||
Npm.depends({
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user