├── demo
├── action.json
├── index.js
└── local.js
├── lib
├── directline
│ ├── model
│ │ └── AttachmentTypes.json
│ ├── DirectLineClientStorage.js
│ ├── DirectLineManager.js
│ └── DirectLineWrapper.js
├── actions-on-google
│ ├── model
│ │ ├── Attachment.js
│ │ ├── Instructions.js
│ │ └── Activity.js
│ ├── InstructionBuilder.js
│ └── MessageBuilder.js
├── queue
│ └── GoogleOutboundQueue.js
└── fulfilment.js
├── test
├── directline
│ ├── DirectLineClientStorageTest.js
│ └── DirectLineWrapperTest.js
├── actions-on-google
│ ├── MessageBuilderTest.js
│ └── MessageBuilder2Test.js
└── queue
│ └── GoogleOutboundQueueTest.js
├── package.json
├── index.js
├── LICENSE
├── .gitignore
├── .circleci
└── config.yml
├── README.md
└── yarn.lock
/demo/action.json:
--------------------------------------------------------------------------------
1 | {"actions":[{"description":"Default Welcome Intent","name":"MAIN","fulfillment":{"conversationName":"MAIN_CONVERSATION"},"intent":{"name":"actions.intent.MAIN","trigger":{"queryPatterns":["talk to Bot Framework"]}}}],"conversations":{"MAIN_CONVERSATION":{"name":"MAIN_CONVERSATION","url":"https://41aa9e69.ngrok.io"}}}
--------------------------------------------------------------------------------
/lib/directline/model/AttachmentTypes.json:
--------------------------------------------------------------------------------
1 | {
2 | "AnimationCard": "application/vnd.microsoft.card.animation",
3 | "AudioCard": "application/vnd.microsoft.card.audio",
4 | "HeroCard": "application/vnd.microsoft.card.hero",
5 | "ReceiptCard": "application/vnd.microsoft.com.card.receipt",
6 | "SigninCard": "application/vnd.microsoft.card.signin",
7 | "ThumbnailCard": "application/vnd.microsoft.card.thumbnail",
8 | "VideoCard": "application/vnd.microsoft.card.video"
9 | }
--------------------------------------------------------------------------------
/lib/directline/DirectLineClientStorage.js:
--------------------------------------------------------------------------------
1 | class DirectLineClientStorage {
2 | constructor() {
3 | this._clients = new Map();
4 | }
5 | getClient(conversationId) {
6 | return this._clients.get(conversationId);
7 | }
8 | storeClient(conversationId, directLineWrapper) {
9 | return this._clients.set(conversationId, directLineWrapper);
10 | }
11 | removeClient(conversationId) {
12 | return this._clients.delete(conversationId);
13 | }
14 | }
15 | module.exports = new DirectLineClientStorage();
--------------------------------------------------------------------------------
/demo/index.js:
--------------------------------------------------------------------------------
1 | //=========================================================
2 | // Import NPM modules
3 | //=========================================================
4 | const express = require("express");
5 | const actionsOnGoogleAdapter = require("../")(process.env.DIRECT_LINE_SECRET);
6 |
7 | const PORT = process.env.PORT || 3000;
8 | const app = express();
9 |
10 | app.use(actionsOnGoogleAdapter.router);
11 |
12 | actionsOnGoogleAdapter.onUserSignedInHandlerProvider.registerHandler((user) => {
13 | //Get additional user details from your API once account is linked and access token is available
14 | // return fetch('https://your-api/user/' + user.userId, {
15 | // headers: {
16 | // Authorization: `Bearer ${user.accessToken}`
17 | // }
18 | // }).then(res => res.json())
19 | return Promise.resolve({username: 'John87', ...user})
20 | });
21 |
22 | app.listen(PORT, () => console.log(`ActionsOnGoogle demo listening on port ${PORT}!`));
--------------------------------------------------------------------------------
/test/directline/DirectLineClientStorageTest.js:
--------------------------------------------------------------------------------
1 | const directLineClientStorage = require("../../lib/directline/DirectLineClientStorage");
2 | const assert = require('assert');
3 |
4 | describe('DirectLineClientStorage', () => {
5 | it('should return 123 when the value 123 is stored against key "test"', () => {
6 | directLineClientStorage.storeClient("test", 123);
7 | assert.equal(directLineClientStorage.getClient("test"), 123);
8 | });
9 | it('should be able to store an object against a given key', () => {
10 | let objectToBeStored = {
11 | number: new Number()
12 | };
13 | directLineClientStorage.storeClient("object-lives-here", objectToBeStored);
14 | assert.equal(directLineClientStorage.getClient("object-lives-here"), objectToBeStored);
15 | });
16 | it('should return undefined when no value is stored for key', () => {
17 | assert.equal(directLineClientStorage.getClient("should-be-undefined"));
18 | });
19 | });
20 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "bot-framework-actions-on-google",
3 | "description": "This NPM Module adds a Microsoft Bot Framework integration to Actions on Google - turning it into an input channel.",
4 | "version": "2.3.1",
5 | "main": "index.js",
6 | "repository": "git@github.com:Capgemini-AIE/bot-framework-actions-on-google.git",
7 | "author": "Dan Cotton",
8 | "license": "MIT",
9 | "dependencies": {
10 | "actions-on-google": "^1.7.0",
11 | "body-parser": "^1.18.2",
12 | "botframework-directlinejs": "^0.9.14",
13 | "express": "^4.16.2",
14 | "node-fetch": "^1.7.3",
15 | "xhr2": "^0.1.4"
16 | },
17 | "devDependencies": {
18 | "dotenv": "^4.0.0",
19 | "mocha": "^4.1.0",
20 | "mocha-circleci-reporter": "^0.0.2",
21 | "ngrok": "^2.2.24",
22 | "npm-cli-login": "^0.0.10",
23 | "sinon": "^4.1.3"
24 | },
25 | "scripts": {
26 | "start": "pushd demo; node local; popd;",
27 | "test": "mocha --reporter mocha-circleci-reporter test/**/*.js",
28 | "login-cli": "npm-cli-login"
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/index.js:
--------------------------------------------------------------------------------
1 | //=========================================================
2 | // Import NPM modules
3 | //=========================================================
4 | const express = require("express");
5 | const bodyParser = require("body-parser");
6 | const fulfill = require("./lib/fulfilment");
7 |
8 | class OnUserSignedInHandlerProvider {
9 | constructor() {
10 | this.handler = (user) => {
11 | return Promise.resolve(user)
12 | }
13 | }
14 |
15 | registerHandler(handler) {
16 | this.handler = handler
17 | }
18 |
19 | getHandler() {
20 | return this.handler
21 | }
22 | }
23 |
24 |
25 | module.exports = (directlineSecret, conversationTimeout, shouldGetUsersNameFromGoogle = true) => {
26 | const onUserSignedInHandlerProvider = new OnUserSignedInHandlerProvider();
27 | const router = express.Router();
28 | router.use(bodyParser.json());
29 | router.use(fulfill(directlineSecret, onUserSignedInHandlerProvider, conversationTimeout, shouldGetUsersNameFromGoogle));
30 |
31 | return {
32 | onUserSignedInHandlerProvider,
33 | router
34 | };
35 | };
--------------------------------------------------------------------------------
/demo/local.js:
--------------------------------------------------------------------------------
1 | require("dotenv").config();
2 | const { exec } = require('child_process');
3 | const ngrok = require('ngrok');
4 | const fs = require('fs');
5 |
6 | const actionMeta = Object.assign({}, require("./action.json"));
7 | const app = require("./index.js");
8 |
9 | ngrok.connect(process.env.PORT || 3000, (err, url) => {
10 | console.log("[NGROK] Obtained URL: " + url);
11 | // Write to actions metadata file
12 | actionMeta.conversations.MAIN_CONVERSATION.url = url;
13 | fs.writeFile("./action.json", JSON.stringify(actionMeta), (err) => {
14 | if (err) {
15 | return console.error(err);
16 | }
17 | console.log("[FS] Rewrote actions.json to update URL");
18 | // Update GACTIONS for local dev.
19 | exec(`gactions update --action_package action.json --project ${process.env.GACTIONS_PROJECT_ID}`, (err, stdout, stderr) => {
20 | if (err) {
21 | // Failed
22 | return console.error(err);
23 | }
24 | console.log("[GACTIONS] Successfully executed update via gactions CLI");
25 | // Done. Leave app running.
26 | });
27 | });
28 | });
29 |
--------------------------------------------------------------------------------
/lib/actions-on-google/model/Attachment.js:
--------------------------------------------------------------------------------
1 | const AttachmentTypes = require("../../directline/model/AttachmentTypes.json");
2 | const Instructions = require("./Instructions");
3 |
4 | module.exports = class Activity {
5 |
6 | constructor(attachment) {
7 | switch (attachment.contentType) {
8 | case AttachmentTypes.AudioCard:
9 | this.getGoogleActionsInstructions = () => this.getAudioCardInstructions(attachment);
10 | break;
11 | case AttachmentTypes.SigninCard:
12 | this.getGoogleActionsInstructions = () => this.getSignInCardInstructions(attachment);
13 | break;
14 | default:
15 | this.getGoogleActionsInstructions = () => this.getEmptyInstructions();
16 | break;
17 | }
18 | }
19 |
20 | getEmptyInstructions() {
21 | return new Instructions();
22 | }
23 |
24 | getAudioCardInstructions(attachment) {
25 | return new Instructions().appendText(`${attachment.content.media.map(item => ``).join(" ")}`);
26 | }
27 |
28 | getSignInCardInstructions() {
29 | return new Instructions().mergeSignInRequired(true);
30 | }
31 |
32 | }
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2018 Capgemini AIE
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/lib/actions-on-google/InstructionBuilder.js:
--------------------------------------------------------------------------------
1 | const Activity = require("./model/Activity");
2 | const Instructions = require("./model/Instructions");
3 |
4 | module.exports = class MessageBuilder {
5 |
6 | constructInstructions(activities) {
7 | const startingInstructions = new Instructions();
8 | const instructions = activities.reduce((accumulatedInstructions, activity) => {
9 | const instructions = new Activity(activity).getGoogleActionsInstructions();
10 | return accumulatedInstructions.mergeInstructions(instructions, true);
11 | }, startingInstructions);
12 | return instructions.getInstructions()
13 | }
14 |
15 | actOnInstructions(instructions, app, state) {
16 | console.log(`User: ${JSON.stringify(app.getUser())}`);
17 | if (instructions.signInRequired) {
18 | console.log(`Asking for sign in`);
19 | app.askForSignIn();
20 | } else {
21 | if(instructions.shouldEndSession)
22 | {
23 | app.tell(`${instructions.text}`, state)
24 | }
25 | else
26 | {
27 | app.ask(`${instructions.text}`, state)
28 | }
29 | }
30 | }
31 | }
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log
4 | npm-debug.log*
5 | yarn-debug.log*
6 | yarn-error.log*
7 |
8 | # Runtime data
9 | pids
10 | *.pid
11 | *.seed
12 | *.pid.lock
13 |
14 | # Test Results
15 | test-results.xml
16 |
17 | # env
18 | .env
19 |
20 | # Creds
21 | creds.data
22 |
23 | # Directory for instrumented libs generated by jscoverage/JSCover
24 | lib-cov
25 |
26 | # Coverage directory used by tools like istanbul
27 | coverage
28 |
29 | # nyc test coverage
30 | .nyc_output
31 |
32 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
33 | .grunt
34 |
35 | # Bower dependency directory (https://bower.io/)
36 | bower_components
37 |
38 | # node-waf configuration
39 | .lock-wscript
40 |
41 | # Compiled binary addons (http://nodejs.org/api/addons.html)
42 | build/Release
43 |
44 | # Dependency directories
45 | node_modules/
46 | jspm_packages/
47 |
48 | # Typescript v1 declaration files
49 | typings/
50 |
51 | # Optional npm cache directory
52 | .npm
53 |
54 | # Optional eslint cache
55 | .eslintcache
56 |
57 | # Optional REPL history
58 | .node_repl_history
59 |
60 | # Output of 'npm pack'
61 | *.tgz
62 |
63 | # Yarn Integrity file
64 | .yarn-integrity
65 |
66 | # dotenv environment variables file
67 | .env
68 |
69 | .idea
70 |
71 |
--------------------------------------------------------------------------------
/lib/queue/GoogleOutboundQueue.js:
--------------------------------------------------------------------------------
1 | class GoogleOutboundQueue {
2 | constructor() {
3 | this._queue = new Map();
4 | this._interval = setInterval(this.queueWorker.bind(this), process.env.QUEUE_GROUPING_TIMEOUT || 800)
5 | }
6 | queueWorker() {
7 | Array.from(this._queue.keys())
8 | .map(key => ({
9 | key,
10 | value: Object.assign({}, this._queue.get(key))
11 | }))
12 | .map((pair) => {
13 | let { key, value } = pair;
14 | if (value.count > 0 && value.count === value.messages.length) {
15 | let activities = value.messages;
16 | console.log("Sending message back for conv id: " + key, activities);
17 | value.messages[value.count - 1].ask(activities);
18 | value.messages = [];
19 | value.count = 0;
20 | } else {
21 | // console.log(`[QUEUE] ${value.count} !== ${value.messages.length} for ${key}`);
22 | value.count = value.messages.length;
23 | }
24 | this._queue.set(key, value);
25 | })
26 | }
27 | enqueueMessage(conversationId, activity, ask) {
28 | let entry = this._queue.get(conversationId) || {};
29 | entry.messages = [].concat(entry.messages || [], Object.assign({}, activity, {
30 | ask
31 | }));
32 | return this._queue.set(conversationId, entry);
33 | }
34 | }
35 | module.exports = new GoogleOutboundQueue();
--------------------------------------------------------------------------------
/lib/actions-on-google/model/Instructions.js:
--------------------------------------------------------------------------------
1 | module.exports = class Instructions {
2 |
3 | constructor() {
4 | this.signInRequired = false;
5 | this.text = '';
6 | this.shouldEndSession = false;
7 | }
8 |
9 | appendText(newText, requiresBreak) {
10 | if (this.text && requiresBreak) {
11 | this.text = `${this.text} . ${newText}`
12 | } else {
13 | if (this.text) {
14 | this.text = `${this.text} ${newText}`
15 | } else {
16 | this.text = newText
17 | }
18 | }
19 | return this;
20 | }
21 |
22 | mergeSignInRequired(signInRequired) {
23 | if (!this.signInRequired) {
24 | this.signInRequired = signInRequired
25 | }
26 | return this;
27 | }
28 |
29 | mergeShouldEndSession(shouldEndSession) {
30 | if (!this.shouldEndSession) {
31 | this.shouldEndSession = shouldEndSession
32 | }
33 | return this;
34 | }
35 |
36 | mergeInstructions(instructions, insertSpeechBreaks) {
37 | this.appendText(instructions.getInstructions().text, insertSpeechBreaks);
38 | this.mergeSignInRequired(instructions.getInstructions().signInRequired);
39 | this.mergeShouldEndSession(instructions.getInstructions().shouldEndSession);
40 | return this;
41 | }
42 |
43 | getInstructions() {
44 | return {
45 | text: this.text,
46 | signInRequired: this.signInRequired,
47 | shouldEndSession: this.shouldEndSession
48 | }
49 | }
50 |
51 | }
--------------------------------------------------------------------------------
/lib/actions-on-google/model/Activity.js:
--------------------------------------------------------------------------------
1 | const Attachment = require('./Attachment')
2 | const Instructions = require('./Instructions')
3 |
4 | module.exports = class Activity {
5 |
6 | constructor(data) {
7 | this.activityData = data;
8 | }
9 |
10 | getGoogleActionsInstructions() {
11 | const instructions = new Instructions();
12 | instructions.appendText(this.activityData.text);
13 |
14 | const defaultLeaveSessionOpen = (process.env.DEFAULT_LEAVE_SESSION_OPEN)
15 | ? (process.env.DEFAULT_LEAVE_SESSION_OPEN == 'true')
16 | : true;
17 |
18 | const expectingInput = ((this.activityData.inputHint) && (this.activityData.inputHint == 'expectingInput')) ? true : false;
19 | const ignoringInput = ((this.activityData.inputHint) && (this.activityData.inputHint == 'ignoringInput')) ? true : false;
20 |
21 | let shouldLeaveSessionOpen = defaultLeaveSessionOpen;
22 |
23 | if(expectingInput) {
24 | shouldLeaveSessionOpen = true;
25 | } else if(ignoringInput) {
26 | shouldLeaveSessionOpen = false;
27 | }
28 |
29 | instructions.mergeShouldEndSession(!shouldLeaveSessionOpen);
30 |
31 | if (this.activityData.attachments && this.activityData.attachments.length > 0) {
32 | return this.activityData.attachments.reduce((accumulatedInstructions, attachment) => {
33 | const instructions = new Attachment(attachment).getGoogleActionsInstructions();
34 | return accumulatedInstructions.mergeInstructions(instructions, false)
35 | }, instructions);
36 | } else {
37 | return instructions;
38 | }
39 | }
40 | };
--------------------------------------------------------------------------------
/lib/directline/DirectLineManager.js:
--------------------------------------------------------------------------------
1 | global.XMLHttpRequest = require("xhr2");
2 |
3 | const { DirectLine, ConnectionStatus } = require("botframework-directlinejs");
4 | const fetch = require("node-fetch");
5 | const DirectLineWrapper = require("./DirectLineWrapper");
6 |
7 | const DIRECTLINE_ENDPOINT = "https://directline.botframework.com";
8 |
9 | class DirectLineManager {
10 | constructor(directlineSecret) {
11 | this._secret = directlineSecret;
12 | }
13 | createConversation() {
14 | return this.getToken(this._secret)
15 | .then(token => new Promise((resolve, reject) => {
16 | console.log("New client with given token: ");
17 | let wrapper = new DirectLineWrapper(new DirectLine({
18 | token: token,
19 | webSocket: true
20 | }));
21 | wrapper.client.connectionStatus$
22 | .subscribe(connectionStatus => {
23 | switch(connectionStatus) {
24 | case ConnectionStatus.Online:
25 | console.log("Client ONLINE. ");
26 | resolve(wrapper);
27 | break;
28 | default:
29 | console.log("Conn Status: " + connectionStatus + "" + ConnectionStatus[connectionStatus]);
30 | break;
31 | }
32 | });
33 | }));
34 | }
35 | getToken(directlineSecret) {
36 | return fetch(`${DIRECTLINE_ENDPOINT}/v3/directline/tokens/generate`, {
37 | method: "POST",
38 | headers: {
39 | Authorization: `Bearer ${directlineSecret}`
40 | }
41 | })
42 | .then(response => response.json())
43 | .then(json => console.log(json) || json.token)
44 | }
45 | }
46 |
47 | module.exports = DirectLineManager;
--------------------------------------------------------------------------------
/.circleci/config.yml:
--------------------------------------------------------------------------------
1 | # Javascript Node CircleCI 2.0 configuration file
2 | #
3 | # Check https://circleci.com/docs/2.0/language-javascript/ for more details
4 | #
5 | version: 2
6 | jobs:
7 | test:
8 | docker:
9 | - image: circleci/node:latest
10 |
11 | working_directory: ~/repo
12 |
13 | steps:
14 | - checkout
15 |
16 | # Download and cache dependencies
17 | - restore_cache:
18 | keys:
19 | - v1-dependencies-{{ checksum "package.json" }}
20 | # fallback to using the latest cache if no exact match is found
21 | - v1-dependencies-
22 |
23 | - save_cache:
24 | paths:
25 | - node_modules
26 | key: v1-dependencies-{{ checksum "package.json" }}
27 |
28 | - run: yarn install
29 | - run: mkdir junit
30 | - run:
31 | command: yarn test
32 | environment:
33 | MOCHA_FILE: junit/test-results.xml
34 | when: always
35 | - store_test_results:
36 | path: ~/repo/junit
37 | - store_artifacts:
38 | path: ~/repo/junit
39 | release:
40 | docker:
41 | - image: circleci/node:latest
42 | working_directory: ~/repo
43 | steps:
44 | - checkout
45 | - run: yarn
46 | - run: npm run login-cli
47 | - run: npm publish
48 |
49 | workflows:
50 | version: 2
51 | test_and_release:
52 | jobs:
53 | - test:
54 | filters:
55 | tags:
56 | only: /.*/
57 | - release:
58 | requires:
59 | - test
60 | filters:
61 | tags:
62 | only: /v[0-9]+(\.[0-9]+)*/
63 | branches:
64 | ignore: /.*/
--------------------------------------------------------------------------------
/lib/directline/DirectLineWrapper.js:
--------------------------------------------------------------------------------
1 | module.exports = class DirectLineWrapper {
2 | constructor(directlineClient, user) {
3 | this._messagingCallbacks = [];
4 | this._user = user;
5 | this.client = directlineClient;
6 | this.client.activity$
7 | .filter(activity => activity.type === 'message')
8 | .subscribe(this._activityHandler.bind(this));
9 | }
10 | _activityHandler(activity) {
11 | this._messagingCallbacks.map(callback => callback(activity));
12 | }
13 | addEventListener(callback) {
14 | this._messagingCallbacks.push(callback);
15 | }
16 | get conversationId() {
17 | return this.client.conversationId;
18 | }
19 | endConversation(userId) {
20 | this.client.postActivity({
21 | type: "endOfConversation",
22 | from: {
23 | id: userId,
24 | }
25 | })
26 | .subscribe(
27 | id => (id) => console.log("Conversation end request sent: " + id),
28 | error => {
29 | console.error("Error posting request to end conversation", error);
30 | }
31 | );
32 | }
33 | removeEventListener(callback) {
34 | this._messagingCallbacks.splice(this._messagingCallbacks.indexOf(callback), 1);
35 | }
36 | sendMessage(user, message, userId) {
37 | // Patch if user isn't provided
38 | user = user || {};
39 | const activity = {
40 | from: {
41 | id: userId,
42 | name: user.displayName,
43 | summary: user
44 | },
45 | type: "message",
46 | text: message
47 | };
48 | console.log("[BOT-FRAMEWORK] Sending activity: ", activity);
49 | this.client
50 | .postActivity(activity)
51 | .subscribe(
52 | id => (id) => console.log("Message sent with ID: " + id),
53 | error => {
54 | console.error("Error posting activity", error);
55 | }
56 | );
57 | }
58 | }
--------------------------------------------------------------------------------
/test/actions-on-google/MessageBuilderTest.js:
--------------------------------------------------------------------------------
1 | const MessageBuilder = require("../../lib/actions-on-google/MessageBuilder");
2 | const assert = require('assert');
3 |
4 | const instance = new MessageBuilder();
5 |
6 | xdescribe('MessageBuilder', () => {
7 |
8 | it('should concat activities consisting of purely plain text', () => {
9 | const PLAIN_TEXT = [
10 | {
11 | text: "This is a test..."
12 | },
13 | {
14 | text: "Of some text."
15 | },
16 | {
17 | text: "Being concatenated."
18 | }
19 | ];
20 | let processed = instance.constructMessage(PLAIN_TEXT);
21 | assert.equal(processed.text, `${PLAIN_TEXT[0].text} . ${PLAIN_TEXT[1].text} . ${PLAIN_TEXT[2].text}`);
22 | });
23 |
24 | it('should concat activities consisting of single audio cards', () => {
25 | const AUDIO_CARD_ACTIVITIES = [
26 | {
27 | text: "This is a test...",
28 | attachments: [{
29 | data: {
30 | contentType: "application/vnd.microsoft.card.audio",
31 | content: {
32 | media: [{
33 | url: "http://test.audio-source.com/audio.mp3"
34 | }]
35 | }
36 | }
37 | }]
38 | },
39 | {
40 | text: "Of some text."
41 | },
42 | {
43 | text: "Being concatenated.",
44 | attachments: [{
45 | data: {
46 | contentType: "application/vnd.microsoft.card.audio",
47 | content: {
48 | media: [{
49 | url: "http://test.audio-source.com/audio-2.mp3"
50 | }]
51 | }
52 | }
53 | }]
54 | }
55 | ];
56 | let processed = instance.constructMessage(AUDIO_CARD_ACTIVITIES);
57 | assert.equal(processed.text, `${AUDIO_CARD_ACTIVITIES[0].text} . ${AUDIO_CARD_ACTIVITIES[1].text} . ${AUDIO_CARD_ACTIVITIES[2].text} `);
58 | });
59 |
60 | });
--------------------------------------------------------------------------------
/lib/actions-on-google/MessageBuilder.js:
--------------------------------------------------------------------------------
1 | const AttachmentTypes = require("../directline/model/AttachmentTypes.json");
2 |
3 | module.exports = class MessageBuilder {
4 |
5 | constructor() {
6 | this._handleAttachments = this._handleAttachments.bind(this);
7 | }
8 |
9 | actOnMessages(activities, app, state) {
10 | let message = this._handleActivities(activities, app);
11 | console.log('Message constructed from activities', message);
12 | if (message.signInRequired) {
13 | console.log('Signin required');
14 | app.askForSignIn();
15 | } else {
16 | message.text = `${message.text}`;
17 | // Ask the user.
18 | app.ask(message.text, state);
19 | }
20 | }
21 |
22 | _handleActivities(activities) {
23 | console.log(`Handling activities ${JSON.stringify(activities)}`);
24 | let cardText = activity => activity.cards ? activity.cards.map(card => card.resolvedText).join(" ") : "";
25 |
26 | return Object.assign({}, activities.reduce(this._flattenActivityCards), {
27 | text: activities
28 | .map(this._handleAttachments)
29 | .map(activity => activity.text + cardText(activity))
30 | .join(" . ")
31 | });
32 | }
33 |
34 | _flattenActivityCards(result, activity) {
35 | return Object.assign({}, result, {
36 | cards: [].concat(result.cards, activity.cards)
37 | });
38 | }
39 |
40 | _handleAttachments(activity) {
41 | if (activity.attachments) {
42 | let cards = activity.attachments
43 | .map(attachment => {
44 | return attachment.data || attachment;
45 | })
46 | .map((attachmentData) => {
47 | switch (attachmentData.contentType) {
48 | case AttachmentTypes.AudioCard:
49 | return this._renderAudioCard(activity, attachmentData);
50 | break;
51 | case AttachmentTypes.SigninCard:
52 | return this._renderSigninCard(attachmentData);
53 | break;
54 | default:
55 | return attachmentData;
56 | break;
57 | }
58 | });
59 | return Object.assign({}, activity, {
60 | cards
61 | });
62 | } else {
63 | return activity;
64 | }
65 | }
66 |
67 | _renderAudioCard(activity, attachment) {
68 | let text = activity.text;
69 | let content = attachment.content;
70 | return Object.assign({}, attachment, {
71 | resolvedText: ` ${content.media.map(item => ``).join(" ")} `
72 | });
73 | }
74 |
75 | _renderSigninCard(attachment) {
76 | return Object.assign({}, attachment, {
77 | signInRequired: true
78 | });
79 | }
80 | }
--------------------------------------------------------------------------------
/test/actions-on-google/MessageBuilder2Test.js:
--------------------------------------------------------------------------------
1 | const InstructionBuilder = require("../../lib/actions-on-google/InstructionBuilder");
2 | const assert = require('assert');
3 |
4 | const instance = new InstructionBuilder();
5 |
6 | describe('MessageBuilder', () => {
7 |
8 | it('should concat activities consisting of purely plain text', () => {
9 | const PLAIN_TEXT = [
10 | {
11 | text: "This is a test..."
12 | },
13 | {
14 | text: "Of some text."
15 | },
16 | {
17 | text: "Being concatenated."
18 | }
19 | ];
20 | let processed = instance.constructInstructions(PLAIN_TEXT);
21 | assert.equal(processed.text, `${PLAIN_TEXT[0].text} . ${PLAIN_TEXT[1].text} . ${PLAIN_TEXT[2].text}`);
22 | });
23 |
24 | it('should concat activities consisting of single audio cards', () => {
25 | const AUDIO_CARD_ACTIVITIES = [
26 | {
27 | text: "This is a test...",
28 | attachments: [{
29 | contentType: "application/vnd.microsoft.card.audio",
30 | content: {
31 | media: [{
32 | url: "http://test.audio-source.com/audio.mp3"
33 | }]
34 | }
35 | }]
36 | },
37 | {
38 | text: "Of some text."
39 | },
40 | {
41 | text: "Being concatenated.",
42 | attachments: [{
43 | contentType: "application/vnd.microsoft.card.audio",
44 | content: {
45 | media: [{
46 | url: "http://test.audio-source.com/audio-2.mp3"
47 | }]
48 | }
49 | }]
50 | }
51 | ];
52 | let processed = instance.constructInstructions(AUDIO_CARD_ACTIVITIES);
53 | assert.equal(processed.text, `${AUDIO_CARD_ACTIVITIES[0].text} . ${AUDIO_CARD_ACTIVITIES[1].text} . ${AUDIO_CARD_ACTIVITIES[2].text} `);
54 | });
55 |
56 | it('should concat activities consisting of single audio cards', () => {
57 | const AUDIO_CARD_ACTIVITIES = [
58 | {
59 | attachments: [{
60 | contentType: "application/vnd.microsoft.card.signin",
61 | content: {
62 | text: "BotFramework Sign-in Card",
63 | buttons: [{
64 | type: "signin",
65 | title: "Sign-in",
66 | value: "http://esurelogin-hosting-mobilehub-769067525.s3-website.eu-west-2.amazonaws.com/"
67 | }]
68 | }
69 | }],
70 | }
71 | ];
72 | let processed = instance.constructInstructions(AUDIO_CARD_ACTIVITIES);
73 | assert.equal(processed.signInRequired, true);
74 | });
75 |
76 | });
--------------------------------------------------------------------------------
/test/queue/GoogleOutboundQueueTest.js:
--------------------------------------------------------------------------------
1 | process.env.QUEUE_GROUPING_TIMEOUT = 250;
2 | const outboundMessageQueue = require("../../lib/queue/GoogleOutboundQueue");
3 | const assert = require('assert');
4 | const sinon = require('sinon');
5 |
6 |
7 | const mockMessage = {
8 | text: "This is my message",
9 | ask: sinon.spy()
10 | };
11 | const mockMessage2 = {
12 | text: "This is my second message",
13 | ask: sinon.spy()
14 | };
15 |
16 | describe('GoogleOutboundQueue', () => {
17 | it('should construct an empty map', () => {
18 | assert(outboundMessageQueue._queue instanceof Map);
19 | assert.equal(Array.from(outboundMessageQueue._queue.keys()).length, 0);
20 | // Clear interval for test purposes
21 | clearInterval(outboundMessageQueue._interval);
22 | });
23 | it('should enqueue message correctly when no other messages queued for that conversation.', () => {
24 | // Pre assertions.
25 | assert(outboundMessageQueue._queue instanceof Map);
26 | assert.equal(Array.from(outboundMessageQueue._queue.keys()).length, 0);
27 |
28 | outboundMessageQueue.enqueueMessage("TEST_CONV_ID", mockMessage, mockMessage.ask);
29 |
30 | // Post assertions
31 | assert.equal(Array.from(outboundMessageQueue._queue.keys()).length, 1);
32 | assert.deepEqual(outboundMessageQueue._queue.get("TEST_CONV_ID"), {
33 | messages: [
34 | mockMessage
35 | ]
36 | });
37 | });
38 | it('should enqueue subsequent messages correctly.', () => {
39 |
40 | // Pre assertions.
41 | assert(outboundMessageQueue._queue instanceof Map);
42 | assert.equal(Array.from(outboundMessageQueue._queue.keys()).length, 1);
43 |
44 | assert.deepEqual(outboundMessageQueue._queue.get("TEST_CONV_ID"), {
45 | messages: [
46 | mockMessage
47 | ]
48 | });
49 |
50 | outboundMessageQueue.enqueueMessage("TEST_CONV_ID", mockMessage2, mockMessage2.ask);
51 |
52 | // Post assertions
53 | assert.equal(Array.from(outboundMessageQueue._queue.keys()).length, 1);
54 | assert.deepEqual(outboundMessageQueue._queue.get("TEST_CONV_ID"), {
55 | messages: [
56 | mockMessage,
57 | mockMessage2
58 | ]
59 | });
60 | });
61 |
62 | it('should call most recent ask() once with full message.', () => {
63 | // Pre assertions.
64 | assert.equal(Array.from(outboundMessageQueue._queue.keys()).length, 1);
65 | assert.deepEqual(outboundMessageQueue._queue.get("TEST_CONV_ID"), {
66 | messages: [
67 | mockMessage,
68 | mockMessage2
69 | ]
70 | });
71 |
72 | // Build count();
73 | outboundMessageQueue.queueWorker();
74 | assert(mockMessage.ask.notCalled);
75 | assert(mockMessage2.ask.notCalled);
76 |
77 | // Call Ask
78 | outboundMessageQueue.queueWorker();
79 |
80 | // Post assertions
81 | assert(mockMessage.ask.notCalled);
82 | assert(mockMessage2.ask.calledOnce);
83 | });
84 | });
--------------------------------------------------------------------------------
/lib/fulfilment.js:
--------------------------------------------------------------------------------
1 | const ActionsSdkApp = require('actions-on-google').ActionsSdkApp;
2 | const DirectLineManager = require("./directline/DirectLineManager");
3 | const directLineClients = require("./directline/DirectLineClientStorage");
4 | const outboundQueue = require("./queue/GoogleOutboundQueue");
5 | const InstructionBuilder = require("./actions-on-google/InstructionBuilder");
6 |
7 | const EXIT_TRIGGERS = ["bye", "exit", "quit"];
8 | const EXIT_MESSAGE = "Ok, bye!";
9 | let manager;
10 | const queuedMessages = new Map();
11 | const assistantMessage = new InstructionBuilder();
12 |
13 | const sendMessageToBot = (app, bot, input, userId, state, user) => {
14 | // Add event listener for responses
15 | let rand = Math.random();
16 | let responder = event => {
17 | if (event.from.id !== app.getConversationId()) {
18 | console.log("[BOT-FRAMEWORK] Got response from bot:" + rand, event);
19 | outboundQueue.enqueueMessage(bot.conversationId, event, (messages) => {
20 | console.log("[ASKING] Sending messages: ", messages);
21 | const instructions = assistantMessage.constructInstructions(messages);
22 | state.lastInteraction = new Date();
23 | assistantMessage.actOnInstructions(instructions, app, state);
24 | console.log("Acting on Instructions");
25 | bot.removeEventListener(responder);
26 | });
27 | } else if (!app.getConversationId()) {
28 | console.error("No conversation ID provided, cannot send message back.");
29 | }
30 | };
31 | bot.addEventListener(responder);
32 | if (!user) {
33 | user = app.getUserName();
34 | }
35 | bot.sendMessage(user, input, userId);
36 | };
37 |
38 | const getDirectLineClient = (state, conversationTimeout, conversationId) => {
39 | // Check for expired conversation if timeout has been provided
40 | if (conversationTimeout && state.lastInteraction) {
41 | let bot = directLineClients.getClient(state.directlineConversationId);
42 | // Get time delta from state's last interaction
43 | const delta = new Date() - new Date(state.lastInteraction);
44 | // If the delta is large enough to be >= the provided timeout
45 | if (delta >= conversationTimeout) {
46 | // End the conversation
47 | bot.endConversation(conversationId);
48 | // Make bot null
49 | bot = null;
50 | // Remove the directline client from the store
51 | directLineClients.removeClient(state.directlineConversationId);
52 | // Remove conversationId from the state
53 | delete state.directlineConversationId;
54 | delete state.lastInteraction;
55 | }
56 | }
57 | // Get an instance of the directline client
58 | let bot = directLineClients.getClient(state.directlineConversationId);
59 | console.log("STATE:", state);
60 | if (!bot) {
61 | // Create a conversation
62 | return manager.createConversation();
63 | } else {
64 | return Promise.resolve(bot);
65 | }
66 | }
67 |
68 | let continueConversation = function (input, app, state, conversationTimeout, shouldGetUsersNameFromGoogle, user) {
69 | const conversationId = app.getConversationId();
70 | // If user is trying to exit.
71 | if (EXIT_TRIGGERS.includes(input.toLowerCase())) {
72 | console.log("[EXIT] User requested exit");
73 | app.tell(EXIT_MESSAGE);
74 | } else {
75 | getDirectLineClient(state, conversationTimeout, conversationId)
76 | .then(bot => {
77 | // Update state to contain conversation ID
78 | state.directlineConversationId = bot.conversationId;
79 | // Store in client storage
80 | directLineClients.storeClient(bot.conversationId, bot);
81 | // Get user name
82 | let userName = app.getUserName();
83 | // If we can't get the user's name....
84 | if (shouldGetUsersNameFromGoogle && (!user || !app.getUserName())) {
85 | const permission = app.SupportedPermissions.NAME;
86 | // Request permissions
87 | app.askForPermission('To know who you are', permission, state);
88 | // Add a message to the queue, to be sent on permission granted.
89 | queuedMessages.set(state.directlineConversationId, (newApp) => sendMessageToBot(newApp, bot, input, conversationId, state));
90 | } else {
91 | // Else - if we can - Send message to directline
92 | sendMessageToBot(app, bot, input, conversationId, state, user || app.getUserName());
93 | }
94 | });
95 | }
96 | };
97 |
98 | const fulfill = (onUserSignedInHandlerProvider, conversationTimeout, shouldGetUsersNameFromGoogle) => (app) => {
99 | let state = Object.assign({}, app.getDialogState());
100 | let intent = app.getIntent();
101 |
102 | let input = app.getRawInput();
103 |
104 | if (intent === app.StandardIntents.PERMISSION) {
105 | console.log("Executing queued message");
106 | queuedMessages.get(state.directlineConversationId)(app);
107 | return queuedMessages.delete(state.directlineConversationId);
108 | } else if (intent === app.StandardIntents.SIGN_IN) {
109 | const user = app.getUser();
110 | if (app.getSignInStatus() === app.SignInStatus.OK) {
111 | console.log('User has signed in: ', user);
112 | //Get id token and populate user with it
113 | const onUserSignedInHandler = onUserSignedInHandlerProvider.getHandler();
114 | onUserSignedInHandler(user).then((res) => {
115 | input = 'Hi';
116 | continueConversation(input, app, state, conversationTimeout, shouldGetUsersNameFromGoogle, res);
117 | });
118 | } else {
119 | app.tell('You need to sign-in before using the app. Use Google assistant on the Google Home app on your phone to sign in.');
120 | }
121 | } else {
122 | continueConversation(input, app, state, conversationTimeout, shouldGetUsersNameFromGoogle);
123 | }
124 | };
125 |
126 | module.exports = (directlineSecret, onUserSignedInHandlerProvider, conversationTimeout, shouldGetUsersNameFromGoogle) => {
127 | manager = new DirectLineManager(directlineSecret);
128 | return (req, res) => {
129 | console.log("Inbound request to: " + req.url)
130 | const app = new ActionsSdkApp({
131 | request: req,
132 | response: res
133 | });
134 |
135 | app.handleRequest(fulfill(onUserSignedInHandlerProvider, conversationTimeout, shouldGetUsersNameFromGoogle));
136 | }
137 | };
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # bot-framework-actions-on-google
2 |
3 |
4 | This NPM Module adds a Microsoft Bot Framework integration to Actions on Google - turning it into an input channel.
5 |
6 | [](https://circleci.com/gh/Capgemini-AIE/bot-framework-actions-on-google)
7 |
8 | ## Quickstart
9 |
10 | If you know how to create an Actions on Google project with the Actions SDK. Follow the usual process of creating and populating an actions.json file.
11 |
12 | This module then acts as a replacement for your fulfilment, simply use the module to construct an express router to be used as the fulfilment URL. You will need to provide a directline secret.
13 |
14 | ```javascript
15 | const actionsOnGoogleAdapter = require("bot-framework-actions-on-google");
16 | const express = require("express");
17 |
18 | const app = express();
19 |
20 | // Construct and use router.
21 | app.use(actionsOnGoogleAdapter());
22 | ```
23 |
24 | ## Full Guide
25 |
26 | ### Setting up actions.json
27 | ```json
28 | {
29 | "actions": [{
30 | "description": "",
31 | "name": "MAIN",
32 | "fulfillment": {
33 | "conversationName": "MAIN_CONVERSATION"
34 | },
35 | "intent": {
36 | "name": "actions.intent.MAIN",
37 | "trigger": {
38 | "queryPatterns": ["talk to "]
39 | }
40 | }
41 | }],
42 | "conversations": {
43 | "MAIN_CONVERSATION": {
44 | "name": "MAIN_CONVERSATION",
45 | "url": ""
46 | }
47 | }
48 | }
49 | ```
50 |
51 | ### Providing Google with your actions.json file
52 |
53 | Now that you've got an actions.json file - you need to update Google on the configuration of your action.
54 |
55 | `gactions update --action_package actions.json --project `
56 |
57 | ### Using the module
58 |
59 | #### Router
60 |
61 | This module exposes a single function, which returns an object which has an Express Router and a way to register a handler for when a user links their account.
62 |
63 | `actionsOnGoogleAdapter();`
64 |
65 | Simply require() the module, and pass it your Microsoft Bot Framework Directline secret.
66 |
67 | Then pass the resulting router to ExpressJS's `app.use()` middleware registration function.
68 |
69 | ```javascript
70 | //=========================================================
71 | // Import NPM modules
72 | //=========================================================
73 | const express = require("express");
74 | const actionsOnGoogleAdapter = require("bot-framework-actions-on-google");
75 |
76 | const app = express();
77 |
78 | app.use(actionsOnGoogleAdapter().router);
79 |
80 | const PORT = process.env.PORT || 3000;
81 | app.listen(PORT, () => console.log(`ActionsOnGoogle demo listening on port ${PORT}!`));
82 | ```
83 |
84 | Now that this is complete. Deploy the express server to the URL you configured in the actions.json file - and your bot should be accessible through Actions on Google/Google Assistant.
85 |
86 | #### Sign In Handler
87 |
88 | Use the sign in handler to get any additional information about your user or anything else once you have an access token.
89 |
90 | The access token is available on the user object passed into the handler.
91 |
92 | ```javascript
93 | actionsOnGoogleAdapter.onUserSignedInHandlerProvider.registerHandler((user) => {
94 | // Get additional user details from your API once account is linked and access token is available
95 | return fetch('https://your-api/user/' + user.userId, {
96 | headers: {
97 | Authorization: `Bearer ${user.accessToken}`
98 | }
99 | }).then(res => res.json())
100 | .then(res => ({...res, ...user}))
101 | });
102 | ```
103 |
104 | ## Features
105 |
106 | ### Controlling the session (should we wait for another message from the user?)
107 |
108 | By default this bridge will leave the session open after returning a response to Google and will expect further input from the user, which will allow them to continue their conversation with your action. If you wish to end the session by default following each message, there an environment setting 'DEFAULT_LEAVE_SESSION_OPEN' which you can set to "false" to achieve this.
109 |
110 | This bridge also supports InputHint properties on Bot Framework activities when using the SayAsync method (as opposed to PostAsync), which determine if the bot should wait for further input from the user in a voice scenario.
111 |
112 | The bridge looks for an inputHint on the incoming activity from the bot, specifically looking for either the 'ExpectingInput' hint, which will cause the bridge to leave the conversation open, or 'IgnoringInput', which will end the conversation. So, for example, if you have sessions being left open by default you could use the 'IgnoringInput' InputHint to end the session. Conversely, if you end the session by default, you can send the 'ExpectingInput' InputHint on your bot activity to indicate that we should wait for the user to say something else.
113 |
114 | Below is an example of using the above InputHint features in a C# bot. In this example we send a message from the bot to the bridge and also indicate that we are expecting a further message from the user.
115 |
116 | ```cs
117 | var messageText = "Thanks! Can I help you with something else?";
118 | var messageOptions = new MessageOptions
119 | {
120 | InputHint = InputHints.ExpectingInput
121 | };
122 | await context.SayAsync(messageText, speakText, options: messageOptions);
123 | ```
124 |
125 | ### Audio Cards
126 |
127 | This bridge supports Microsoft Bot Framework Audio Cards:
128 | (https://docs.botframework.com/en-us/node/builder/chat-reference/classes/_botbuilder_d_.audiocard.html)
129 |
130 | It will simply concatenate the audio file onto the text provided in the response.
131 |
132 |
133 | ### Sign In Cards
134 |
135 | This bridge supports Microsoft Bot Framework Signin Cards:
136 | (https://docs.botframework.com/en-us/node/builder/chat-reference/classes/_botbuilder_d_.signincard.html)
137 |
138 | If a sign in card is sent back, other cards are ignored and the user will be asked to sign in.
139 |
140 | ## Known Issues
141 |
142 | ### Multiple Responses
143 |
144 | Actions on Google doesn't play too nicely with multiple messages being sent, ideally keep your responses in one message block.
145 |
--------------------------------------------------------------------------------
/test/directline/DirectLineWrapperTest.js:
--------------------------------------------------------------------------------
1 | const DirectLineWrapper = require("../../lib/directline/DirectLineWrapper");
2 | const assert = require('assert');
3 | const sinon = require('sinon');
4 |
5 | const mockClient = {
6 | conversationId: "AB123-00",
7 | isDirectLineClient: true,
8 | activity$: {
9 | filter: () => ({
10 | subscribe: () => {
11 |
12 | }
13 | })
14 | }
15 | };
16 |
17 | const mockUser = {
18 | id: 123912456,
19 | isChatbotUser: true
20 | };
21 |
22 | describe('DirectLineWrapper', () => {
23 | it('should initialise correctly, storing the client provided on construction', () => {
24 | let wrapper = new DirectLineWrapper(mockClient, mockUser);
25 | assert.equal(wrapper.client, mockClient);
26 | assert.equal(wrapper._user, mockUser);
27 | assert.deepEqual(wrapper._messagingCallbacks, []);
28 | });
29 |
30 | it('should correctly subscribe to an event', () => {
31 | let subscribeCompatibleMock = Object.assign({}, mockClient, {
32 | activity$: {
33 | filter: (filterFunction) => {
34 | assert.equal(filterFunction({
35 | type: "message"
36 | }), true)
37 | assert.equal(filterFunction({
38 | type: "conversation"
39 | }), false)
40 | return ({
41 | subscribe: (callback) => {
42 | assert.equal(callback instanceof Function, true);
43 | }
44 | })
45 | }
46 | }
47 | })
48 | let wrapper = new DirectLineWrapper(subscribeCompatibleMock, mockUser);
49 | assert.equal(mockClient.conversationId, wrapper.conversationId);
50 | });
51 |
52 | it('should call registered event listener on activity', () => {
53 | // Setup mocks
54 | let subscriptionFunction;
55 | let subscribeCompatibleMock = Object.assign({}, mockClient, {
56 | activity$: {
57 | filter: (filterFunction) => {
58 | assert.equal(filterFunction({
59 | type: "message"
60 | }), true)
61 | assert.equal(filterFunction({
62 | type: "conversation"
63 | }), false)
64 | return ({
65 | subscribe: (callback) => {
66 | assert.equal(callback instanceof Function, true);
67 | subscriptionFunction = callback;
68 | }
69 | })
70 | }
71 | }
72 | });
73 | const mockActivity = {
74 | id: 12341,
75 | isActivity: true
76 | };
77 | const eventHandler = sinon.spy();
78 | // Construct wrapper
79 | let wrapper = new DirectLineWrapper(subscribeCompatibleMock, mockUser);
80 | // Call event registration
81 | wrapper.addEventListener(eventHandler);
82 | // Trigger activity
83 | subscriptionFunction(mockActivity);
84 | // Assert
85 | assert(eventHandler.calledOnce);
86 | assert(eventHandler.calledWith(mockActivity));
87 | });
88 |
89 | it('should call multiple registered event listeners on activity', () => {
90 | // Setup mocks
91 | let subscriptionFunction;
92 | let subscribeCompatibleMock = Object.assign({}, mockClient, {
93 | activity$: {
94 | filter: (filterFunction) => {
95 | assert.equal(filterFunction({
96 | type: "message"
97 | }), true)
98 | assert.equal(filterFunction({
99 | type: "conversation"
100 | }), false)
101 | return ({
102 | subscribe: (callback) => {
103 | assert.equal(callback instanceof Function, true);
104 | subscriptionFunction = callback;
105 | }
106 | })
107 | }
108 | }
109 | });
110 | const mockActivity = {
111 | id: 12341,
112 | isActivity: true
113 | };
114 | const eventHandlerOne = sinon.spy();
115 | // Construct wrapper
116 | let wrapper = new DirectLineWrapper(subscribeCompatibleMock, mockUser);
117 | // Call event registration
118 | wrapper.addEventListener(eventHandlerOne);
119 | // Trigger activity
120 | subscriptionFunction(mockActivity);
121 | // Assert
122 | assert(eventHandlerOne.calledOnce);
123 | assert(eventHandlerOne.calledWith(mockActivity));
124 |
125 | /**
126 | * Second call
127 | */
128 | const eventHandlerTwo = sinon.spy();
129 | // Call event registration
130 | wrapper.addEventListener(eventHandlerTwo);
131 | // Trigger activity
132 | subscriptionFunction(mockActivity);
133 | // Handler 1
134 | assert(eventHandlerOne.callCount, 2);
135 | assert(eventHandlerOne.called);
136 | assert(eventHandlerOne.calledWith(mockActivity));
137 | // Handler 2
138 | assert(eventHandlerTwo.calledOnce);
139 | assert(eventHandlerTwo.calledWith(mockActivity));
140 |
141 | });
142 |
143 | it('should return the correct conversation ID when getter is called', () => {
144 | let wrapper = new DirectLineWrapper(mockClient, mockUser);
145 | assert.equal(mockClient.conversationId, wrapper.conversationId);
146 | });
147 |
148 | it('should be able to remove registered event listener', () => {
149 | // Setup mocks
150 | let subscriptionFunction;
151 | let subscribeCompatibleMock = Object.assign({}, mockClient, {
152 | activity$: {
153 | filter: (filterFunction) => {
154 | assert.equal(filterFunction({
155 | type: "message"
156 | }), true)
157 | assert.equal(filterFunction({
158 | type: "conversation"
159 | }), false)
160 | return ({
161 | subscribe: (callback) => {
162 | assert.equal(callback instanceof Function, true);
163 | subscriptionFunction = callback;
164 | }
165 | })
166 | }
167 | }
168 | });
169 | const mockActivity = {
170 | id: 12341,
171 | isActivity: true
172 | };
173 | const eventHandler = sinon.spy();
174 | // Construct wrapper
175 | let wrapper = new DirectLineWrapper(subscribeCompatibleMock, mockUser);
176 | // Call event registration
177 | wrapper.addEventListener(eventHandler);
178 | // Trigger activity
179 | subscriptionFunction(mockActivity);
180 | // Remove event listener
181 | wrapper.removeEventListener(eventHandler);
182 | // Trigger activity again
183 | subscriptionFunction(mockActivity);
184 | // Assert
185 | assert(eventHandler.calledOnce);
186 | assert(eventHandler.calledWith(mockActivity));
187 | });
188 |
189 |
190 | it('should send a well constructed event when sendMessage is called', () => {
191 | // Mocks
192 | const user = {
193 | displayName: "Dan Cotton"
194 | };
195 | const userId = "AA-B123-00A";
196 | const message = "This is a test message";
197 | const activityMock = {
198 | from: {
199 | id: userId,
200 | name: user.displayName,
201 | summary: user
202 | },
203 | type: "message",
204 | text: message
205 | };
206 | const postActivityMockClient = Object.assign({}, mockClient, {
207 | postActivity: () => ({
208 | subscribe: () => {}
209 | })
210 | });
211 | let spy = sinon.spy(postActivityMockClient, "postActivity");
212 | // Construct Wrapper, perform send message
213 | let wrapper = new DirectLineWrapper(postActivityMockClient, mockUser);
214 | wrapper.sendMessage(user, message, userId);
215 | assert(spy.calledOnce);
216 | assert(spy.calledWith(activityMock));
217 | });
218 |
219 | });
220 |
--------------------------------------------------------------------------------
/yarn.lock:
--------------------------------------------------------------------------------
1 | # THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
2 | # yarn lockfile v1
3 |
4 |
5 | "@types/node@^8.0.19":
6 | version "8.5.5"
7 | resolved "https://registry.yarnpkg.com/@types/node/-/node-8.5.5.tgz#6f9e8164ae1a55a9beb1d2571cfb7acf9d720c61"
8 |
9 | abbrev@1:
10 | version "1.1.1"
11 | resolved "https://registry.yarnpkg.com/abbrev/-/abbrev-1.1.1.tgz#f8f2c887ad10bf67f634f005b6987fed3179aac8"
12 |
13 | accepts@~1.3.4:
14 | version "1.3.4"
15 | resolved "https://registry.yarnpkg.com/accepts/-/accepts-1.3.4.tgz#86246758c7dd6d21a6474ff084a4740ec05eb21f"
16 | dependencies:
17 | mime-types "~2.1.16"
18 | negotiator "0.6.1"
19 |
20 | actions-on-google@^1.7.0:
21 | version "1.7.0"
22 | resolved "https://registry.yarnpkg.com/actions-on-google/-/actions-on-google-1.7.0.tgz#4bc7209bd696755affb56c61fa4e7da7896075b9"
23 | dependencies:
24 | debug "^2.2.0"
25 | google-auth-library "^0.10.0"
26 | lodash.camelcase "^4.3.0"
27 | lodash.snakecase "^4.1.1"
28 |
29 | ajv@^5.1.0:
30 | version "5.5.2"
31 | resolved "https://registry.yarnpkg.com/ajv/-/ajv-5.5.2.tgz#73b5eeca3fab653e3d3f9422b341ad42205dc965"
32 | dependencies:
33 | co "^4.6.0"
34 | fast-deep-equal "^1.0.0"
35 | fast-json-stable-stringify "^2.0.0"
36 | json-schema-traverse "^0.3.0"
37 |
38 | ansi@^0.3.0, ansi@~0.3.1:
39 | version "0.3.1"
40 | resolved "https://registry.yarnpkg.com/ansi/-/ansi-0.3.1.tgz#0c42d4fb17160d5a9af1e484bace1c66922c1b21"
41 |
42 | are-we-there-yet@~1.1.2:
43 | version "1.1.4"
44 | resolved "https://registry.yarnpkg.com/are-we-there-yet/-/are-we-there-yet-1.1.4.tgz#bb5dca382bb94f05e15194373d16fd3ba1ca110d"
45 | dependencies:
46 | delegates "^1.0.0"
47 | readable-stream "^2.0.6"
48 |
49 | array-flatten@1.1.1:
50 | version "1.1.1"
51 | resolved "https://registry.yarnpkg.com/array-flatten/-/array-flatten-1.1.1.tgz#9a5f699051b1e7073328f2a008968b64ea2955d2"
52 |
53 | asn1@~0.2.3:
54 | version "0.2.3"
55 | resolved "https://registry.yarnpkg.com/asn1/-/asn1-0.2.3.tgz#dac8787713c9966849fc8180777ebe9c1ddf3b86"
56 |
57 | assert-plus@1.0.0, assert-plus@^1.0.0:
58 | version "1.0.0"
59 | resolved "https://registry.yarnpkg.com/assert-plus/-/assert-plus-1.0.0.tgz#f12e0f3c5d77b0b1cdd9146942e4e96c1e4dd525"
60 |
61 | async@^2.3.0:
62 | version "2.6.0"
63 | resolved "https://registry.yarnpkg.com/async/-/async-2.6.0.tgz#61a29abb6fcc026fea77e56d1c6ec53a795951f4"
64 | dependencies:
65 | lodash "^4.14.0"
66 |
67 | asynckit@^0.4.0:
68 | version "0.4.0"
69 | resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79"
70 |
71 | aws-sign2@~0.7.0:
72 | version "0.7.0"
73 | resolved "https://registry.yarnpkg.com/aws-sign2/-/aws-sign2-0.7.0.tgz#b46e890934a9591f2d2f6f86d7e6a9f1b3fe76a8"
74 |
75 | aws4@^1.6.0:
76 | version "1.6.0"
77 | resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.6.0.tgz#83ef5ca860b2b32e4a0deedee8c771b9db57471e"
78 |
79 | balanced-match@^1.0.0:
80 | version "1.0.0"
81 | resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.0.tgz#89b4d199ab2bee49de164ea02b89ce462d71b767"
82 |
83 | base64url@2.0.0, base64url@^2.0.0:
84 | version "2.0.0"
85 | resolved "https://registry.yarnpkg.com/base64url/-/base64url-2.0.0.tgz#eac16e03ea1438eff9423d69baa36262ed1f70bb"
86 |
87 | bcrypt-pbkdf@^1.0.0:
88 | version "1.0.1"
89 | resolved "https://registry.yarnpkg.com/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.1.tgz#63bc5dcb61331b92bc05fd528953c33462a06f8d"
90 | dependencies:
91 | tweetnacl "^0.14.3"
92 |
93 | binary@^0.3.0:
94 | version "0.3.0"
95 | resolved "https://registry.yarnpkg.com/binary/-/binary-0.3.0.tgz#9f60553bc5ce8c3386f3b553cff47462adecaa79"
96 | dependencies:
97 | buffers "~0.1.1"
98 | chainsaw "~0.1.0"
99 |
100 | body-parser@1.18.2, body-parser@^1.18.2:
101 | version "1.18.2"
102 | resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.18.2.tgz#87678a19d84b47d859b83199bd59bce222b10454"
103 | dependencies:
104 | bytes "3.0.0"
105 | content-type "~1.0.4"
106 | debug "2.6.9"
107 | depd "~1.1.1"
108 | http-errors "~1.6.2"
109 | iconv-lite "0.4.19"
110 | on-finished "~2.3.0"
111 | qs "6.5.1"
112 | raw-body "2.3.2"
113 | type-is "~1.6.15"
114 |
115 | boom@4.x.x:
116 | version "4.3.1"
117 | resolved "https://registry.yarnpkg.com/boom/-/boom-4.3.1.tgz#4f8a3005cb4a7e3889f749030fd25b96e01d2e31"
118 | dependencies:
119 | hoek "4.x.x"
120 |
121 | boom@5.x.x:
122 | version "5.2.0"
123 | resolved "https://registry.yarnpkg.com/boom/-/boom-5.2.0.tgz#5dd9da6ee3a5f302077436290cb717d3f4a54e02"
124 | dependencies:
125 | hoek "4.x.x"
126 |
127 | botframework-directlinejs@^0.9.14:
128 | version "0.9.14"
129 | resolved "https://registry.yarnpkg.com/botframework-directlinejs/-/botframework-directlinejs-0.9.14.tgz#cd7b0c613b9eb7c1963a0cc2a4df11b7a54360d7"
130 | dependencies:
131 | rxjs "^5.0.3"
132 |
133 | brace-expansion@^1.1.7:
134 | version "1.1.8"
135 | resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.8.tgz#c07b211c7c952ec1f8efd51a77ef0d1d3990a292"
136 | dependencies:
137 | balanced-match "^1.0.0"
138 | concat-map "0.0.1"
139 |
140 | browser-stdout@1.3.0:
141 | version "1.3.0"
142 | resolved "https://registry.yarnpkg.com/browser-stdout/-/browser-stdout-1.3.0.tgz#f351d32969d32fa5d7a5567154263d928ae3bd1f"
143 |
144 | buffer-equal-constant-time@1.0.1:
145 | version "1.0.1"
146 | resolved "https://registry.yarnpkg.com/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz#f8e71132f7ffe6e01a5c9697a4c6f3e48d5cc819"
147 |
148 | buffers@~0.1.1:
149 | version "0.1.1"
150 | resolved "https://registry.yarnpkg.com/buffers/-/buffers-0.1.1.tgz#b24579c3bed4d6d396aeee6d9a8ae7f5482ab7bb"
151 |
152 | builtin-modules@^1.0.0:
153 | version "1.1.1"
154 | resolved "https://registry.yarnpkg.com/builtin-modules/-/builtin-modules-1.1.1.tgz#270f076c5a72c02f5b65a47df94c5fe3a278892f"
155 |
156 | bytes@3.0.0:
157 | version "3.0.0"
158 | resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.0.0.tgz#d32815404d689699f85a4ea4fa8755dd13a96048"
159 |
160 | caseless@~0.12.0:
161 | version "0.12.0"
162 | resolved "https://registry.yarnpkg.com/caseless/-/caseless-0.12.0.tgz#1b681c21ff84033c826543090689420d187151dc"
163 |
164 | chainsaw@~0.1.0:
165 | version "0.1.0"
166 | resolved "https://registry.yarnpkg.com/chainsaw/-/chainsaw-0.1.0.tgz#5eab50b28afe58074d0d58291388828b5e5fbc98"
167 | dependencies:
168 | traverse ">=0.3.0 <0.4"
169 |
170 | charenc@~0.0.1:
171 | version "0.0.2"
172 | resolved "https://registry.yarnpkg.com/charenc/-/charenc-0.0.2.tgz#c0a1d2f3a7092e03774bfa83f14c0fc5790a8667"
173 |
174 | chownr@^1.0.1:
175 | version "1.0.1"
176 | resolved "https://registry.yarnpkg.com/chownr/-/chownr-1.0.1.tgz#e2a75042a9551908bebd25b8523d5f9769d79181"
177 |
178 | co@^4.6.0:
179 | version "4.6.0"
180 | resolved "https://registry.yarnpkg.com/co/-/co-4.6.0.tgz#6ea6bdf3d853ae54ccb8e47bfa0bf3f9031fb184"
181 |
182 | combined-stream@^1.0.5, combined-stream@~1.0.5:
183 | version "1.0.5"
184 | resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.5.tgz#938370a57b4a51dea2c77c15d5c5fdf895164009"
185 | dependencies:
186 | delayed-stream "~1.0.0"
187 |
188 | commander@2.11.0:
189 | version "2.11.0"
190 | resolved "https://registry.yarnpkg.com/commander/-/commander-2.11.0.tgz#157152fd1e7a6c8d98a5b715cf376df928004563"
191 |
192 | commander@2.9.0:
193 | version "2.9.0"
194 | resolved "https://registry.yarnpkg.com/commander/-/commander-2.9.0.tgz#9c99094176e12240cb22d6c5146098400fe0f7d4"
195 | dependencies:
196 | graceful-readlink ">= 1.0.0"
197 |
198 | concat-map@0.0.1:
199 | version "0.0.1"
200 | resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b"
201 |
202 | concat-stream@^1.4.6:
203 | version "1.6.0"
204 | resolved "https://registry.yarnpkg.com/concat-stream/-/concat-stream-1.6.0.tgz#0aac662fd52be78964d5532f694784e70110acf7"
205 | dependencies:
206 | inherits "^2.0.3"
207 | readable-stream "^2.2.2"
208 | typedarray "^0.0.6"
209 |
210 | content-disposition@0.5.2:
211 | version "0.5.2"
212 | resolved "https://registry.yarnpkg.com/content-disposition/-/content-disposition-0.5.2.tgz#0cf68bb9ddf5f2be7961c3a85178cb85dba78cb4"
213 |
214 | content-type@~1.0.4:
215 | version "1.0.4"
216 | resolved "https://registry.yarnpkg.com/content-type/-/content-type-1.0.4.tgz#e138cc75e040c727b1966fe5e5f8c9aee256fe3b"
217 |
218 | cookie-signature@1.0.6:
219 | version "1.0.6"
220 | resolved "https://registry.yarnpkg.com/cookie-signature/-/cookie-signature-1.0.6.tgz#e303a882b342cc3ee8ca513a79999734dab3ae2c"
221 |
222 | cookie@0.3.1:
223 | version "0.3.1"
224 | resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.3.1.tgz#e7e0a1f9ef43b4c8ba925c5c5a96e806d16873bb"
225 |
226 | core-util-is@1.0.2, core-util-is@~1.0.0:
227 | version "1.0.2"
228 | resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7"
229 |
230 | crypt@~0.0.1:
231 | version "0.0.2"
232 | resolved "https://registry.yarnpkg.com/crypt/-/crypt-0.0.2.tgz#88d7ff7ec0dfb86f713dc87bbb42d044d3e6c41b"
233 |
234 | cryptiles@3.x.x:
235 | version "3.1.2"
236 | resolved "https://registry.yarnpkg.com/cryptiles/-/cryptiles-3.1.2.tgz#a89fbb220f5ce25ec56e8c4aa8a4fd7b5b0d29fe"
237 | dependencies:
238 | boom "5.x.x"
239 |
240 | dashdash@^1.12.0:
241 | version "1.14.1"
242 | resolved "https://registry.yarnpkg.com/dashdash/-/dashdash-1.14.1.tgz#853cfa0f7cbe2fed5de20326b8dd581035f6e2f0"
243 | dependencies:
244 | assert-plus "^1.0.0"
245 |
246 | debug@2.6.8:
247 | version "2.6.8"
248 | resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.8.tgz#e731531ca2ede27d188222427da17821d68ff4fc"
249 | dependencies:
250 | ms "2.0.0"
251 |
252 | debug@2.6.9, debug@^2.2.0:
253 | version "2.6.9"
254 | resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f"
255 | dependencies:
256 | ms "2.0.0"
257 |
258 | debug@3.1.0:
259 | version "3.1.0"
260 | resolved "https://registry.yarnpkg.com/debug/-/debug-3.1.0.tgz#5bb5a0672628b64149566ba16819e61518c67261"
261 | dependencies:
262 | ms "2.0.0"
263 |
264 | decompress-zip@^0.3.0:
265 | version "0.3.0"
266 | resolved "https://registry.yarnpkg.com/decompress-zip/-/decompress-zip-0.3.0.tgz#ae3bcb7e34c65879adfe77e19c30f86602b4bdb0"
267 | dependencies:
268 | binary "^0.3.0"
269 | graceful-fs "^4.1.3"
270 | mkpath "^0.1.0"
271 | nopt "^3.0.1"
272 | q "^1.1.2"
273 | readable-stream "^1.1.8"
274 | touch "0.0.3"
275 |
276 | delayed-stream@~1.0.0:
277 | version "1.0.0"
278 | resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619"
279 |
280 | delegates@^1.0.0:
281 | version "1.0.0"
282 | resolved "https://registry.yarnpkg.com/delegates/-/delegates-1.0.0.tgz#84c6e159b81904fdca59a0ef44cd870d31250f9a"
283 |
284 | depd@1.1.1, depd@~1.1.1:
285 | version "1.1.1"
286 | resolved "https://registry.yarnpkg.com/depd/-/depd-1.1.1.tgz#5783b4e1c459f06fa5ca27f991f3d06e7a310359"
287 |
288 | destroy@~1.0.4:
289 | version "1.0.4"
290 | resolved "https://registry.yarnpkg.com/destroy/-/destroy-1.0.4.tgz#978857442c44749e4206613e37946205826abd80"
291 |
292 | diff@3.2.0:
293 | version "3.2.0"
294 | resolved "https://registry.yarnpkg.com/diff/-/diff-3.2.0.tgz#c9ce393a4b7cbd0b058a725c93df299027868ff9"
295 |
296 | diff@3.3.1:
297 | version "3.3.1"
298 | resolved "https://registry.yarnpkg.com/diff/-/diff-3.3.1.tgz#aa8567a6eed03c531fc89d3f711cd0e5259dec75"
299 |
300 | diff@^3.1.0:
301 | version "3.4.0"
302 | resolved "https://registry.yarnpkg.com/diff/-/diff-3.4.0.tgz#b1d85507daf3964828de54b37d0d73ba67dda56c"
303 |
304 | dotenv@^4.0.0:
305 | version "4.0.0"
306 | resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-4.0.0.tgz#864ef1379aced55ce6f95debecdce179f7a0cd1d"
307 |
308 | ecc-jsbn@~0.1.1:
309 | version "0.1.1"
310 | resolved "https://registry.yarnpkg.com/ecc-jsbn/-/ecc-jsbn-0.1.1.tgz#0fc73a9ed5f0d53c38193398523ef7e543777505"
311 | dependencies:
312 | jsbn "~0.1.0"
313 |
314 | ecdsa-sig-formatter@1.0.9:
315 | version "1.0.9"
316 | resolved "https://registry.yarnpkg.com/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.9.tgz#4bc926274ec3b5abb5016e7e1d60921ac262b2a1"
317 | dependencies:
318 | base64url "^2.0.0"
319 | safe-buffer "^5.0.1"
320 |
321 | ee-first@1.1.1:
322 | version "1.1.1"
323 | resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d"
324 |
325 | encodeurl@~1.0.1:
326 | version "1.0.1"
327 | resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-1.0.1.tgz#79e3d58655346909fe6f0f45a5de68103b294d20"
328 |
329 | encoding@^0.1.11:
330 | version "0.1.12"
331 | resolved "https://registry.yarnpkg.com/encoding/-/encoding-0.1.12.tgz#538b66f3ee62cd1ab51ec323829d1f9480c74beb"
332 | dependencies:
333 | iconv-lite "~0.4.13"
334 |
335 | escape-html@~1.0.3:
336 | version "1.0.3"
337 | resolved "https://registry.yarnpkg.com/escape-html/-/escape-html-1.0.3.tgz#0258eae4d3d0c0974de1c169188ef0051d1d1988"
338 |
339 | escape-string-regexp@1.0.5:
340 | version "1.0.5"
341 | resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4"
342 |
343 | etag@~1.8.1:
344 | version "1.8.1"
345 | resolved "https://registry.yarnpkg.com/etag/-/etag-1.8.1.tgz#41ae2eeb65efa62268aebfea83ac7d79299b0887"
346 |
347 | express@^4.16.2:
348 | version "4.16.2"
349 | resolved "https://registry.yarnpkg.com/express/-/express-4.16.2.tgz#e35c6dfe2d64b7dca0a5cd4f21781be3299e076c"
350 | dependencies:
351 | accepts "~1.3.4"
352 | array-flatten "1.1.1"
353 | body-parser "1.18.2"
354 | content-disposition "0.5.2"
355 | content-type "~1.0.4"
356 | cookie "0.3.1"
357 | cookie-signature "1.0.6"
358 | debug "2.6.9"
359 | depd "~1.1.1"
360 | encodeurl "~1.0.1"
361 | escape-html "~1.0.3"
362 | etag "~1.8.1"
363 | finalhandler "1.1.0"
364 | fresh "0.5.2"
365 | merge-descriptors "1.0.1"
366 | methods "~1.1.2"
367 | on-finished "~2.3.0"
368 | parseurl "~1.3.2"
369 | path-to-regexp "0.1.7"
370 | proxy-addr "~2.0.2"
371 | qs "6.5.1"
372 | range-parser "~1.2.0"
373 | safe-buffer "5.1.1"
374 | send "0.16.1"
375 | serve-static "1.13.1"
376 | setprototypeof "1.1.0"
377 | statuses "~1.3.1"
378 | type-is "~1.6.15"
379 | utils-merge "1.0.1"
380 | vary "~1.1.2"
381 |
382 | extend@~3.0.1:
383 | version "3.0.1"
384 | resolved "https://registry.yarnpkg.com/extend/-/extend-3.0.1.tgz#a755ea7bc1adfcc5a31ce7e762dbaadc5e636444"
385 |
386 | extsprintf@1.3.0:
387 | version "1.3.0"
388 | resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.3.0.tgz#96918440e3041a7a414f8c52e3c574eb3c3e1e05"
389 |
390 | extsprintf@^1.2.0:
391 | version "1.4.0"
392 | resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.4.0.tgz#e2689f8f356fad62cca65a3a91c5df5f9551692f"
393 |
394 | fast-deep-equal@^1.0.0:
395 | version "1.0.0"
396 | resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-1.0.0.tgz#96256a3bc975595eb36d82e9929d060d893439ff"
397 |
398 | fast-json-stable-stringify@^2.0.0:
399 | version "2.0.0"
400 | resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.0.0.tgz#d5142c0caee6b1189f87d3a76111064f86c8bbf2"
401 |
402 | finalhandler@1.1.0:
403 | version "1.1.0"
404 | resolved "https://registry.yarnpkg.com/finalhandler/-/finalhandler-1.1.0.tgz#ce0b6855b45853e791b2fcc680046d88253dd7f5"
405 | dependencies:
406 | debug "2.6.9"
407 | encodeurl "~1.0.1"
408 | escape-html "~1.0.3"
409 | on-finished "~2.3.0"
410 | parseurl "~1.3.2"
411 | statuses "~1.3.1"
412 | unpipe "~1.0.0"
413 |
414 | forever-agent@~0.6.1:
415 | version "0.6.1"
416 | resolved "https://registry.yarnpkg.com/forever-agent/-/forever-agent-0.6.1.tgz#fbc71f0c41adeb37f96c577ad1ed42d8fdacca91"
417 |
418 | form-data@~2.3.1:
419 | version "2.3.1"
420 | resolved "https://registry.yarnpkg.com/form-data/-/form-data-2.3.1.tgz#6fb94fbd71885306d73d15cc497fe4cc4ecd44bf"
421 | dependencies:
422 | asynckit "^0.4.0"
423 | combined-stream "^1.0.5"
424 | mime-types "^2.1.12"
425 |
426 | formatio@1.2.0, formatio@^1.2.0:
427 | version "1.2.0"
428 | resolved "https://registry.yarnpkg.com/formatio/-/formatio-1.2.0.tgz#f3b2167d9068c4698a8d51f4f760a39a54d818eb"
429 | dependencies:
430 | samsam "1.x"
431 |
432 | forwarded@~0.1.2:
433 | version "0.1.2"
434 | resolved "https://registry.yarnpkg.com/forwarded/-/forwarded-0.1.2.tgz#98c23dab1175657b8c0573e8ceccd91b0ff18c84"
435 |
436 | fresh@0.5.2:
437 | version "0.5.2"
438 | resolved "https://registry.yarnpkg.com/fresh/-/fresh-0.5.2.tgz#3d8cadd90d976569fa835ab1f8e4b23a105605a7"
439 |
440 | fs.realpath@^1.0.0:
441 | version "1.0.0"
442 | resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f"
443 |
444 | gauge@~1.2.5:
445 | version "1.2.7"
446 | resolved "https://registry.yarnpkg.com/gauge/-/gauge-1.2.7.tgz#e9cec5483d3d4ee0ef44b60a7d99e4935e136d93"
447 | dependencies:
448 | ansi "^0.3.0"
449 | has-unicode "^2.0.0"
450 | lodash.pad "^4.1.0"
451 | lodash.padend "^4.1.0"
452 | lodash.padstart "^4.1.0"
453 |
454 | getpass@^0.1.1:
455 | version "0.1.7"
456 | resolved "https://registry.yarnpkg.com/getpass/-/getpass-0.1.7.tgz#5eff8e3e684d569ae4cb2b1282604e8ba62149fa"
457 | dependencies:
458 | assert-plus "^1.0.0"
459 |
460 | glob@7.1.1:
461 | version "7.1.1"
462 | resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.1.tgz#805211df04faaf1c63a3600306cdf5ade50b2ec8"
463 | dependencies:
464 | fs.realpath "^1.0.0"
465 | inflight "^1.0.4"
466 | inherits "2"
467 | minimatch "^3.0.2"
468 | once "^1.3.0"
469 | path-is-absolute "^1.0.0"
470 |
471 | glob@7.1.2, glob@^7.0.5:
472 | version "7.1.2"
473 | resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.2.tgz#c19c9df9a028702d678612384a6552404c636d15"
474 | dependencies:
475 | fs.realpath "^1.0.0"
476 | inflight "^1.0.4"
477 | inherits "2"
478 | minimatch "^3.0.4"
479 | once "^1.3.0"
480 | path-is-absolute "^1.0.0"
481 |
482 | google-auth-library@^0.10.0:
483 | version "0.10.0"
484 | resolved "https://registry.yarnpkg.com/google-auth-library/-/google-auth-library-0.10.0.tgz#6e15babee85fd1dd14d8d128a295b6838d52136e"
485 | dependencies:
486 | gtoken "^1.2.1"
487 | jws "^3.1.4"
488 | lodash.noop "^3.0.1"
489 | request "^2.74.0"
490 |
491 | google-p12-pem@^0.1.0:
492 | version "0.1.2"
493 | resolved "https://registry.yarnpkg.com/google-p12-pem/-/google-p12-pem-0.1.2.tgz#33c46ab021aa734fa0332b3960a9a3ffcb2f3177"
494 | dependencies:
495 | node-forge "^0.7.1"
496 |
497 | graceful-fs@^4.1.2, graceful-fs@^4.1.3:
498 | version "4.1.11"
499 | resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.1.11.tgz#0e8bdfe4d1ddb8854d64e04ea7c00e2a026e5658"
500 |
501 | "graceful-readlink@>= 1.0.0":
502 | version "1.0.1"
503 | resolved "https://registry.yarnpkg.com/graceful-readlink/-/graceful-readlink-1.0.1.tgz#4cafad76bc62f02fa039b2f94e9a3dd3a391a725"
504 |
505 | growl@1.10.3:
506 | version "1.10.3"
507 | resolved "https://registry.yarnpkg.com/growl/-/growl-1.10.3.tgz#1926ba90cf3edfe2adb4927f5880bc22c66c790f"
508 |
509 | growl@1.9.2:
510 | version "1.9.2"
511 | resolved "https://registry.yarnpkg.com/growl/-/growl-1.9.2.tgz#0ea7743715db8d8de2c5ede1775e1b45ac85c02f"
512 |
513 | gtoken@^1.2.1:
514 | version "1.2.3"
515 | resolved "https://registry.yarnpkg.com/gtoken/-/gtoken-1.2.3.tgz#5509571b8afd4322e124cf66cf68115284c476d8"
516 | dependencies:
517 | google-p12-pem "^0.1.0"
518 | jws "^3.0.0"
519 | mime "^1.4.1"
520 | request "^2.72.0"
521 |
522 | har-schema@^2.0.0:
523 | version "2.0.0"
524 | resolved "https://registry.yarnpkg.com/har-schema/-/har-schema-2.0.0.tgz#a94c2224ebcac04782a0d9035521f24735b7ec92"
525 |
526 | har-validator@~5.0.3:
527 | version "5.0.3"
528 | resolved "https://registry.yarnpkg.com/har-validator/-/har-validator-5.0.3.tgz#ba402c266194f15956ef15e0fcf242993f6a7dfd"
529 | dependencies:
530 | ajv "^5.1.0"
531 | har-schema "^2.0.0"
532 |
533 | has-flag@^1.0.0:
534 | version "1.0.0"
535 | resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-1.0.0.tgz#9d9e793165ce017a00f00418c43f942a7b1d11fa"
536 |
537 | has-flag@^2.0.0:
538 | version "2.0.0"
539 | resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-2.0.0.tgz#e8207af1cc7b30d446cc70b734b5e8be18f88d51"
540 |
541 | has-unicode@^2.0.0:
542 | version "2.0.1"
543 | resolved "https://registry.yarnpkg.com/has-unicode/-/has-unicode-2.0.1.tgz#e0e6fe6a28cf51138855e086d1691e771de2a8b9"
544 |
545 | hawk@~6.0.2:
546 | version "6.0.2"
547 | resolved "https://registry.yarnpkg.com/hawk/-/hawk-6.0.2.tgz#af4d914eb065f9b5ce4d9d11c1cb2126eecc3038"
548 | dependencies:
549 | boom "4.x.x"
550 | cryptiles "3.x.x"
551 | hoek "4.x.x"
552 | sntp "2.x.x"
553 |
554 | he@1.1.1:
555 | version "1.1.1"
556 | resolved "https://registry.yarnpkg.com/he/-/he-1.1.1.tgz#93410fd21b009735151f8868c2f271f3427e23fd"
557 |
558 | hoek@4.x.x:
559 | version "4.2.0"
560 | resolved "https://registry.yarnpkg.com/hoek/-/hoek-4.2.0.tgz#72d9d0754f7fe25ca2d01ad8f8f9a9449a89526d"
561 |
562 | hosted-git-info@^2.1.4, hosted-git-info@^2.1.5:
563 | version "2.5.0"
564 | resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-2.5.0.tgz#6d60e34b3abbc8313062c3b798ef8d901a07af3c"
565 |
566 | http-errors@1.6.2, http-errors@~1.6.2:
567 | version "1.6.2"
568 | resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-1.6.2.tgz#0a002cc85707192a7e7946ceedc11155f60ec736"
569 | dependencies:
570 | depd "1.1.1"
571 | inherits "2.0.3"
572 | setprototypeof "1.0.3"
573 | statuses ">= 1.3.1 < 2"
574 |
575 | http-signature@~1.2.0:
576 | version "1.2.0"
577 | resolved "https://registry.yarnpkg.com/http-signature/-/http-signature-1.2.0.tgz#9aecd925114772f3d95b65a60abb8f7c18fbace1"
578 | dependencies:
579 | assert-plus "^1.0.0"
580 | jsprim "^1.2.2"
581 | sshpk "^1.7.0"
582 |
583 | iconv-lite@0.4.19, iconv-lite@~0.4.13:
584 | version "0.4.19"
585 | resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.19.tgz#f7468f60135f5e5dad3399c0a81be9a1603a082b"
586 |
587 | inflight@^1.0.4:
588 | version "1.0.6"
589 | resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9"
590 | dependencies:
591 | once "^1.3.0"
592 | wrappy "1"
593 |
594 | inherits@2, inherits@2.0.3, inherits@^2.0.3, inherits@~2.0.1, inherits@~2.0.3:
595 | version "2.0.3"
596 | resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de"
597 |
598 | ipaddr.js@1.5.2:
599 | version "1.5.2"
600 | resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-1.5.2.tgz#d4b505bde9946987ccf0fc58d9010ff9607e3fa0"
601 |
602 | is-buffer@~1.1.1:
603 | version "1.1.6"
604 | resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-1.1.6.tgz#efaa2ea9daa0d7ab2ea13a97b2b8ad51fefbe8be"
605 |
606 | is-builtin-module@^1.0.0:
607 | version "1.0.0"
608 | resolved "https://registry.yarnpkg.com/is-builtin-module/-/is-builtin-module-1.0.0.tgz#540572d34f7ac3119f8f76c30cbc1b1e037affbe"
609 | dependencies:
610 | builtin-modules "^1.0.0"
611 |
612 | is-stream@^1.0.1:
613 | version "1.1.0"
614 | resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-1.1.0.tgz#12d4a3dd4e68e0b79ceb8dbc84173ae80d91ca44"
615 |
616 | is-typedarray@~1.0.0:
617 | version "1.0.0"
618 | resolved "https://registry.yarnpkg.com/is-typedarray/-/is-typedarray-1.0.0.tgz#e479c80858df0c1b11ddda6940f96011fcda4a9a"
619 |
620 | isarray@0.0.1:
621 | version "0.0.1"
622 | resolved "https://registry.yarnpkg.com/isarray/-/isarray-0.0.1.tgz#8a18acfca9a8f4177e09abfc6038939b05d1eedf"
623 |
624 | isarray@~1.0.0:
625 | version "1.0.0"
626 | resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11"
627 |
628 | isstream@~0.1.2:
629 | version "0.1.2"
630 | resolved "https://registry.yarnpkg.com/isstream/-/isstream-0.1.2.tgz#47e63f7af55afa6f92e1500e690eb8b8529c099a"
631 |
632 | jsbn@~0.1.0:
633 | version "0.1.1"
634 | resolved "https://registry.yarnpkg.com/jsbn/-/jsbn-0.1.1.tgz#a5e654c2e5a2deb5f201d96cefbca80c0ef2f513"
635 |
636 | json-schema-traverse@^0.3.0:
637 | version "0.3.1"
638 | resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.3.1.tgz#349a6d44c53a51de89b40805c5d5e59b417d3340"
639 |
640 | json-schema@0.2.3:
641 | version "0.2.3"
642 | resolved "https://registry.yarnpkg.com/json-schema/-/json-schema-0.2.3.tgz#b480c892e59a2f05954ce727bd3f2a4e882f9e13"
643 |
644 | json-stringify-safe@~5.0.1:
645 | version "5.0.1"
646 | resolved "https://registry.yarnpkg.com/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz#1296a2d58fd45f19a0f6ce01d65701e2c735b6eb"
647 |
648 | json3@3.3.2:
649 | version "3.3.2"
650 | resolved "https://registry.yarnpkg.com/json3/-/json3-3.3.2.tgz#3c0434743df93e2f5c42aee7b19bcb483575f4e1"
651 |
652 | jsprim@^1.2.2:
653 | version "1.4.1"
654 | resolved "https://registry.yarnpkg.com/jsprim/-/jsprim-1.4.1.tgz#313e66bc1e5cc06e438bc1b7499c2e5c56acb6a2"
655 | dependencies:
656 | assert-plus "1.0.0"
657 | extsprintf "1.3.0"
658 | json-schema "0.2.3"
659 | verror "1.10.0"
660 |
661 | just-extend@^1.1.26:
662 | version "1.1.27"
663 | resolved "https://registry.yarnpkg.com/just-extend/-/just-extend-1.1.27.tgz#ec6e79410ff914e472652abfa0e603c03d60e905"
664 |
665 | jwa@^1.1.4:
666 | version "1.1.5"
667 | resolved "https://registry.yarnpkg.com/jwa/-/jwa-1.1.5.tgz#a0552ce0220742cd52e153774a32905c30e756e5"
668 | dependencies:
669 | base64url "2.0.0"
670 | buffer-equal-constant-time "1.0.1"
671 | ecdsa-sig-formatter "1.0.9"
672 | safe-buffer "^5.0.1"
673 |
674 | jws@^3.0.0, jws@^3.1.4:
675 | version "3.1.4"
676 | resolved "https://registry.yarnpkg.com/jws/-/jws-3.1.4.tgz#f9e8b9338e8a847277d6444b1464f61880e050a2"
677 | dependencies:
678 | base64url "^2.0.0"
679 | jwa "^1.1.4"
680 | safe-buffer "^5.0.1"
681 |
682 | lock@^0.1.2:
683 | version "0.1.4"
684 | resolved "https://registry.yarnpkg.com/lock/-/lock-0.1.4.tgz#fec7deaef17e7c3a0a55e1da042803e25d91745d"
685 |
686 | lodash._baseassign@^3.0.0:
687 | version "3.2.0"
688 | resolved "https://registry.yarnpkg.com/lodash._baseassign/-/lodash._baseassign-3.2.0.tgz#8c38a099500f215ad09e59f1722fd0c52bfe0a4e"
689 | dependencies:
690 | lodash._basecopy "^3.0.0"
691 | lodash.keys "^3.0.0"
692 |
693 | lodash._basecopy@^3.0.0:
694 | version "3.0.1"
695 | resolved "https://registry.yarnpkg.com/lodash._basecopy/-/lodash._basecopy-3.0.1.tgz#8da0e6a876cf344c0ad8a54882111dd3c5c7ca36"
696 |
697 | lodash._basecreate@^3.0.0:
698 | version "3.0.3"
699 | resolved "https://registry.yarnpkg.com/lodash._basecreate/-/lodash._basecreate-3.0.3.tgz#1bc661614daa7fc311b7d03bf16806a0213cf821"
700 |
701 | lodash._getnative@^3.0.0:
702 | version "3.9.1"
703 | resolved "https://registry.yarnpkg.com/lodash._getnative/-/lodash._getnative-3.9.1.tgz#570bc7dede46d61cdcde687d65d3eecbaa3aaff5"
704 |
705 | lodash._isiterateecall@^3.0.0:
706 | version "3.0.9"
707 | resolved "https://registry.yarnpkg.com/lodash._isiterateecall/-/lodash._isiterateecall-3.0.9.tgz#5203ad7ba425fae842460e696db9cf3e6aac057c"
708 |
709 | lodash.camelcase@^4.3.0:
710 | version "4.3.0"
711 | resolved "https://registry.yarnpkg.com/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz#b28aa6288a2b9fc651035c7711f65ab6190331a6"
712 |
713 | lodash.create@3.1.1:
714 | version "3.1.1"
715 | resolved "https://registry.yarnpkg.com/lodash.create/-/lodash.create-3.1.1.tgz#d7f2849f0dbda7e04682bb8cd72ab022461debe7"
716 | dependencies:
717 | lodash._baseassign "^3.0.0"
718 | lodash._basecreate "^3.0.0"
719 | lodash._isiterateecall "^3.0.0"
720 |
721 | lodash.get@^4.4.2:
722 | version "4.4.2"
723 | resolved "https://registry.yarnpkg.com/lodash.get/-/lodash.get-4.4.2.tgz#2d177f652fa31e939b4438d5341499dfa3825e99"
724 |
725 | lodash.isarguments@^3.0.0:
726 | version "3.1.0"
727 | resolved "https://registry.yarnpkg.com/lodash.isarguments/-/lodash.isarguments-3.1.0.tgz#2f573d85c6a24289ff00663b491c1d338ff3458a"
728 |
729 | lodash.isarray@^3.0.0:
730 | version "3.0.4"
731 | resolved "https://registry.yarnpkg.com/lodash.isarray/-/lodash.isarray-3.0.4.tgz#79e4eb88c36a8122af86f844aa9bcd851b5fbb55"
732 |
733 | lodash.keys@^3.0.0:
734 | version "3.1.2"
735 | resolved "https://registry.yarnpkg.com/lodash.keys/-/lodash.keys-3.1.2.tgz#4dbc0472b156be50a0b286855d1bd0b0c656098a"
736 | dependencies:
737 | lodash._getnative "^3.0.0"
738 | lodash.isarguments "^3.0.0"
739 | lodash.isarray "^3.0.0"
740 |
741 | lodash.noop@^3.0.1:
742 | version "3.0.1"
743 | resolved "https://registry.yarnpkg.com/lodash.noop/-/lodash.noop-3.0.1.tgz#38188f4d650a3a474258439b96ec45b32617133c"
744 |
745 | lodash.pad@^4.1.0:
746 | version "4.5.1"
747 | resolved "https://registry.yarnpkg.com/lodash.pad/-/lodash.pad-4.5.1.tgz#4330949a833a7c8da22cc20f6a26c4d59debba70"
748 |
749 | lodash.padend@^4.1.0:
750 | version "4.6.1"
751 | resolved "https://registry.yarnpkg.com/lodash.padend/-/lodash.padend-4.6.1.tgz#53ccba047d06e158d311f45da625f4e49e6f166e"
752 |
753 | lodash.padstart@^4.1.0:
754 | version "4.6.1"
755 | resolved "https://registry.yarnpkg.com/lodash.padstart/-/lodash.padstart-4.6.1.tgz#d2e3eebff0d9d39ad50f5cbd1b52a7bce6bb611b"
756 |
757 | lodash.snakecase@^4.1.1:
758 | version "4.1.1"
759 | resolved "https://registry.yarnpkg.com/lodash.snakecase/-/lodash.snakecase-4.1.1.tgz#39d714a35357147837aefd64b5dcbb16becd8f8d"
760 |
761 | lodash@^4.14.0:
762 | version "4.17.4"
763 | resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.4.tgz#78203a4d1c328ae1d86dca6460e369b57f4055ae"
764 |
765 | lolex@^1.6.0:
766 | version "1.6.0"
767 | resolved "https://registry.yarnpkg.com/lolex/-/lolex-1.6.0.tgz#3a9a0283452a47d7439e72731b9e07d7386e49f6"
768 |
769 | lolex@^2.2.0:
770 | version "2.3.1"
771 | resolved "https://registry.yarnpkg.com/lolex/-/lolex-2.3.1.tgz#3d2319894471ea0950ef64692ead2a5318cff362"
772 |
773 | md5@^2.1.0:
774 | version "2.2.1"
775 | resolved "https://registry.yarnpkg.com/md5/-/md5-2.2.1.tgz#53ab38d5fe3c8891ba465329ea23fac0540126f9"
776 | dependencies:
777 | charenc "~0.0.1"
778 | crypt "~0.0.1"
779 | is-buffer "~1.1.1"
780 |
781 | media-typer@0.3.0:
782 | version "0.3.0"
783 | resolved "https://registry.yarnpkg.com/media-typer/-/media-typer-0.3.0.tgz#8710d7af0aa626f8fffa1ce00168545263255748"
784 |
785 | merge-descriptors@1.0.1:
786 | version "1.0.1"
787 | resolved "https://registry.yarnpkg.com/merge-descriptors/-/merge-descriptors-1.0.1.tgz#b00aaa556dd8b44568150ec9d1b953f3f90cbb61"
788 |
789 | methods@~1.1.2:
790 | version "1.1.2"
791 | resolved "https://registry.yarnpkg.com/methods/-/methods-1.1.2.tgz#5529a4d67654134edcc5266656835b0f851afcee"
792 |
793 | mime-db@~1.30.0:
794 | version "1.30.0"
795 | resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.30.0.tgz#74c643da2dd9d6a45399963465b26d5ca7d71f01"
796 |
797 | mime-types@^2.1.12, mime-types@~2.1.15, mime-types@~2.1.16, mime-types@~2.1.17:
798 | version "2.1.17"
799 | resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.17.tgz#09d7a393f03e995a79f8af857b70a9e0ab16557a"
800 | dependencies:
801 | mime-db "~1.30.0"
802 |
803 | mime@1.4.1:
804 | version "1.4.1"
805 | resolved "https://registry.yarnpkg.com/mime/-/mime-1.4.1.tgz#121f9ebc49e3766f311a76e1fa1c8003c4b03aa6"
806 |
807 | mime@^1.4.1:
808 | version "1.6.0"
809 | resolved "https://registry.yarnpkg.com/mime/-/mime-1.6.0.tgz#32cd9e5c64553bd58d19a568af452acff04981b1"
810 |
811 | minimatch@^3.0.2, minimatch@^3.0.4:
812 | version "3.0.4"
813 | resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083"
814 | dependencies:
815 | brace-expansion "^1.1.7"
816 |
817 | minimist@0.0.8:
818 | version "0.0.8"
819 | resolved "https://registry.yarnpkg.com/minimist/-/minimist-0.0.8.tgz#857fcabfc3397d2625b8228262e86aa7a011b05d"
820 |
821 | mkdirp@0.5.1, mkdirp@^0.5.0, mkdirp@~0.5.1:
822 | version "0.5.1"
823 | resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.1.tgz#30057438eac6cf7f8c4767f38648d6697d75c903"
824 | dependencies:
825 | minimist "0.0.8"
826 |
827 | mkpath@^0.1.0:
828 | version "0.1.0"
829 | resolved "https://registry.yarnpkg.com/mkpath/-/mkpath-0.1.0.tgz#7554a6f8d871834cc97b5462b122c4c124d6de91"
830 |
831 | mocha-circleci-reporter@^0.0.2:
832 | version "0.0.2"
833 | resolved "https://registry.yarnpkg.com/mocha-circleci-reporter/-/mocha-circleci-reporter-0.0.2.tgz#6cbb3f1a1911ce2365e79461a156370b63790c7e"
834 | dependencies:
835 | mocha "^3.0.0"
836 | mocha-junit-reporter "^1.12.0"
837 |
838 | mocha-junit-reporter@^1.12.0:
839 | version "1.15.0"
840 | resolved "https://registry.yarnpkg.com/mocha-junit-reporter/-/mocha-junit-reporter-1.15.0.tgz#309f4b7a20fcda26d0ad69c9b7d0808d772302c2"
841 | dependencies:
842 | debug "^2.2.0"
843 | md5 "^2.1.0"
844 | mkdirp "~0.5.1"
845 | xml "^1.0.0"
846 |
847 | mocha@^3.0.0:
848 | version "3.5.3"
849 | resolved "https://registry.yarnpkg.com/mocha/-/mocha-3.5.3.tgz#1e0480fe36d2da5858d1eb6acc38418b26eaa20d"
850 | dependencies:
851 | browser-stdout "1.3.0"
852 | commander "2.9.0"
853 | debug "2.6.8"
854 | diff "3.2.0"
855 | escape-string-regexp "1.0.5"
856 | glob "7.1.1"
857 | growl "1.9.2"
858 | he "1.1.1"
859 | json3 "3.3.2"
860 | lodash.create "3.1.1"
861 | mkdirp "0.5.1"
862 | supports-color "3.1.2"
863 |
864 | mocha@^4.1.0:
865 | version "4.1.0"
866 | resolved "https://registry.yarnpkg.com/mocha/-/mocha-4.1.0.tgz#7d86cfbcf35cb829e2754c32e17355ec05338794"
867 | dependencies:
868 | browser-stdout "1.3.0"
869 | commander "2.11.0"
870 | debug "3.1.0"
871 | diff "3.3.1"
872 | escape-string-regexp "1.0.5"
873 | glob "7.1.2"
874 | growl "1.10.3"
875 | he "1.1.1"
876 | mkdirp "0.5.1"
877 | supports-color "4.4.0"
878 |
879 | ms@2.0.0:
880 | version "2.0.0"
881 | resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8"
882 |
883 | negotiator@0.6.1:
884 | version "0.6.1"
885 | resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.1.tgz#2b327184e8992101177b28563fb5e7102acd0ca9"
886 |
887 | ngrok@^2.2.24:
888 | version "2.2.24"
889 | resolved "https://registry.yarnpkg.com/ngrok/-/ngrok-2.2.24.tgz#0fce41f4f60b1ba7484e052a7ed24868686bef53"
890 | dependencies:
891 | "@types/node" "^8.0.19"
892 | async "^2.3.0"
893 | decompress-zip "^0.3.0"
894 | lock "^0.1.2"
895 | request "^2.55.0"
896 | uuid "^3.0.0"
897 |
898 | nise@^1.2.0:
899 | version "1.2.0"
900 | resolved "https://registry.yarnpkg.com/nise/-/nise-1.2.0.tgz#079d6cadbbcb12ba30e38f1c999f36ad4d6baa53"
901 | dependencies:
902 | formatio "^1.2.0"
903 | just-extend "^1.1.26"
904 | lolex "^1.6.0"
905 | path-to-regexp "^1.7.0"
906 | text-encoding "^0.6.4"
907 |
908 | node-fetch@^1.7.3:
909 | version "1.7.3"
910 | resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-1.7.3.tgz#980f6f72d85211a5347c6b2bc18c5b84c3eb47ef"
911 | dependencies:
912 | encoding "^0.1.11"
913 | is-stream "^1.0.1"
914 |
915 | node-forge@^0.7.1:
916 | version "0.7.1"
917 | resolved "https://registry.yarnpkg.com/node-forge/-/node-forge-0.7.1.tgz#9da611ea08982f4b94206b3beb4cc9665f20c300"
918 |
919 | nopt@^3.0.1:
920 | version "3.0.6"
921 | resolved "https://registry.yarnpkg.com/nopt/-/nopt-3.0.6.tgz#c6465dbf08abcd4db359317f79ac68a646b28ff9"
922 | dependencies:
923 | abbrev "1"
924 |
925 | nopt@~1.0.10:
926 | version "1.0.10"
927 | resolved "https://registry.yarnpkg.com/nopt/-/nopt-1.0.10.tgz#6ddd21bd2a31417b92727dd585f8a6f37608ebee"
928 | dependencies:
929 | abbrev "1"
930 |
931 | "normalize-package-data@~1.0.1 || ^2.0.0":
932 | version "2.4.0"
933 | resolved "https://registry.yarnpkg.com/normalize-package-data/-/normalize-package-data-2.4.0.tgz#12f95a307d58352075a04907b84ac8be98ac012f"
934 | dependencies:
935 | hosted-git-info "^2.1.4"
936 | is-builtin-module "^1.0.0"
937 | semver "2 || 3 || 4 || 5"
938 | validate-npm-package-license "^3.0.1"
939 |
940 | npm-cli-login@^0.0.10:
941 | version "0.0.10"
942 | resolved "https://registry.yarnpkg.com/npm-cli-login/-/npm-cli-login-0.0.10.tgz#b1e8b5b7405008e0a26ccc170443434a59c5364c"
943 | dependencies:
944 | npm-registry-client "7.0.9"
945 |
946 | "npm-package-arg@^3.0.0 || ^4.0.0":
947 | version "4.2.1"
948 | resolved "https://registry.yarnpkg.com/npm-package-arg/-/npm-package-arg-4.2.1.tgz#593303fdea85f7c422775f17f9eb7670f680e3ec"
949 | dependencies:
950 | hosted-git-info "^2.1.5"
951 | semver "^5.1.0"
952 |
953 | npm-registry-client@7.0.9:
954 | version "7.0.9"
955 | resolved "https://registry.yarnpkg.com/npm-registry-client/-/npm-registry-client-7.0.9.tgz#1baf86ee5285c4e6d38d4556208ded56049231bb"
956 | dependencies:
957 | chownr "^1.0.1"
958 | concat-stream "^1.4.6"
959 | graceful-fs "^4.1.2"
960 | mkdirp "^0.5.0"
961 | normalize-package-data "~1.0.1 || ^2.0.0"
962 | npm-package-arg "^3.0.0 || ^4.0.0"
963 | once "^1.3.0"
964 | request "^2.47.0"
965 | retry "^0.8.0"
966 | rimraf "2"
967 | semver "2 >=2.2.1 || 3.x || 4 || 5"
968 | slide "^1.1.3"
969 | optionalDependencies:
970 | npmlog "~2.0.0"
971 |
972 | npmlog@~2.0.0:
973 | version "2.0.4"
974 | resolved "https://registry.yarnpkg.com/npmlog/-/npmlog-2.0.4.tgz#98b52530f2514ca90d09ec5b22c8846722375692"
975 | dependencies:
976 | ansi "~0.3.1"
977 | are-we-there-yet "~1.1.2"
978 | gauge "~1.2.5"
979 |
980 | oauth-sign@~0.8.2:
981 | version "0.8.2"
982 | resolved "https://registry.yarnpkg.com/oauth-sign/-/oauth-sign-0.8.2.tgz#46a6ab7f0aead8deae9ec0565780b7d4efeb9d43"
983 |
984 | on-finished@~2.3.0:
985 | version "2.3.0"
986 | resolved "https://registry.yarnpkg.com/on-finished/-/on-finished-2.3.0.tgz#20f1336481b083cd75337992a16971aa2d906947"
987 | dependencies:
988 | ee-first "1.1.1"
989 |
990 | once@^1.3.0:
991 | version "1.4.0"
992 | resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1"
993 | dependencies:
994 | wrappy "1"
995 |
996 | parseurl@~1.3.2:
997 | version "1.3.2"
998 | resolved "https://registry.yarnpkg.com/parseurl/-/parseurl-1.3.2.tgz#fc289d4ed8993119460c156253262cdc8de65bf3"
999 |
1000 | path-is-absolute@^1.0.0:
1001 | version "1.0.1"
1002 | resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f"
1003 |
1004 | path-to-regexp@0.1.7:
1005 | version "0.1.7"
1006 | resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-0.1.7.tgz#df604178005f522f15eb4490e7247a1bfaa67f8c"
1007 |
1008 | path-to-regexp@^1.7.0:
1009 | version "1.7.0"
1010 | resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-1.7.0.tgz#59fde0f435badacba103a84e9d3bc64e96b9937d"
1011 | dependencies:
1012 | isarray "0.0.1"
1013 |
1014 | performance-now@^2.1.0:
1015 | version "2.1.0"
1016 | resolved "https://registry.yarnpkg.com/performance-now/-/performance-now-2.1.0.tgz#6309f4e0e5fa913ec1c69307ae364b4b377c9e7b"
1017 |
1018 | process-nextick-args@~1.0.6:
1019 | version "1.0.7"
1020 | resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-1.0.7.tgz#150e20b756590ad3f91093f25a4f2ad8bff30ba3"
1021 |
1022 | proxy-addr@~2.0.2:
1023 | version "2.0.2"
1024 | resolved "https://registry.yarnpkg.com/proxy-addr/-/proxy-addr-2.0.2.tgz#6571504f47bb988ec8180253f85dd7e14952bdec"
1025 | dependencies:
1026 | forwarded "~0.1.2"
1027 | ipaddr.js "1.5.2"
1028 |
1029 | punycode@^1.4.1:
1030 | version "1.4.1"
1031 | resolved "https://registry.yarnpkg.com/punycode/-/punycode-1.4.1.tgz#c0d5a63b2718800ad8e1eb0fa5269c84dd41845e"
1032 |
1033 | q@^1.1.2:
1034 | version "1.5.1"
1035 | resolved "https://registry.yarnpkg.com/q/-/q-1.5.1.tgz#7e32f75b41381291d04611f1bf14109ac00651d7"
1036 |
1037 | qs@6.5.1, qs@~6.5.1:
1038 | version "6.5.1"
1039 | resolved "https://registry.yarnpkg.com/qs/-/qs-6.5.1.tgz#349cdf6eef89ec45c12d7d5eb3fc0c870343a6d8"
1040 |
1041 | range-parser@~1.2.0:
1042 | version "1.2.0"
1043 | resolved "https://registry.yarnpkg.com/range-parser/-/range-parser-1.2.0.tgz#f49be6b487894ddc40dcc94a322f611092e00d5e"
1044 |
1045 | raw-body@2.3.2:
1046 | version "2.3.2"
1047 | resolved "https://registry.yarnpkg.com/raw-body/-/raw-body-2.3.2.tgz#bcd60c77d3eb93cde0050295c3f379389bc88f89"
1048 | dependencies:
1049 | bytes "3.0.0"
1050 | http-errors "1.6.2"
1051 | iconv-lite "0.4.19"
1052 | unpipe "1.0.0"
1053 |
1054 | readable-stream@^1.1.8:
1055 | version "1.1.14"
1056 | resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-1.1.14.tgz#7cf4c54ef648e3813084c636dd2079e166c081d9"
1057 | dependencies:
1058 | core-util-is "~1.0.0"
1059 | inherits "~2.0.1"
1060 | isarray "0.0.1"
1061 | string_decoder "~0.10.x"
1062 |
1063 | readable-stream@^2.0.6, readable-stream@^2.2.2:
1064 | version "2.3.3"
1065 | resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.3.tgz#368f2512d79f9d46fdfc71349ae7878bbc1eb95c"
1066 | dependencies:
1067 | core-util-is "~1.0.0"
1068 | inherits "~2.0.3"
1069 | isarray "~1.0.0"
1070 | process-nextick-args "~1.0.6"
1071 | safe-buffer "~5.1.1"
1072 | string_decoder "~1.0.3"
1073 | util-deprecate "~1.0.1"
1074 |
1075 | request@^2.47.0, request@^2.55.0, request@^2.72.0, request@^2.74.0:
1076 | version "2.83.0"
1077 | resolved "https://registry.yarnpkg.com/request/-/request-2.83.0.tgz#ca0b65da02ed62935887808e6f510381034e3356"
1078 | dependencies:
1079 | aws-sign2 "~0.7.0"
1080 | aws4 "^1.6.0"
1081 | caseless "~0.12.0"
1082 | combined-stream "~1.0.5"
1083 | extend "~3.0.1"
1084 | forever-agent "~0.6.1"
1085 | form-data "~2.3.1"
1086 | har-validator "~5.0.3"
1087 | hawk "~6.0.2"
1088 | http-signature "~1.2.0"
1089 | is-typedarray "~1.0.0"
1090 | isstream "~0.1.2"
1091 | json-stringify-safe "~5.0.1"
1092 | mime-types "~2.1.17"
1093 | oauth-sign "~0.8.2"
1094 | performance-now "^2.1.0"
1095 | qs "~6.5.1"
1096 | safe-buffer "^5.1.1"
1097 | stringstream "~0.0.5"
1098 | tough-cookie "~2.3.3"
1099 | tunnel-agent "^0.6.0"
1100 | uuid "^3.1.0"
1101 |
1102 | retry@^0.8.0:
1103 | version "0.8.0"
1104 | resolved "https://registry.yarnpkg.com/retry/-/retry-0.8.0.tgz#2367628dc0edb247b1eab649dc53ac8628ac2d5f"
1105 |
1106 | rimraf@2:
1107 | version "2.6.2"
1108 | resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.6.2.tgz#2ed8150d24a16ea8651e6d6ef0f47c4158ce7a36"
1109 | dependencies:
1110 | glob "^7.0.5"
1111 |
1112 | rxjs@^5.0.3:
1113 | version "5.5.6"
1114 | resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-5.5.6.tgz#e31fb96d6fd2ff1fd84bcea8ae9c02d007179c02"
1115 | dependencies:
1116 | symbol-observable "1.0.1"
1117 |
1118 | safe-buffer@5.1.1, safe-buffer@^5.0.1, safe-buffer@^5.1.1, safe-buffer@~5.1.0, safe-buffer@~5.1.1:
1119 | version "5.1.1"
1120 | resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.1.tgz#893312af69b2123def71f57889001671eeb2c853"
1121 |
1122 | samsam@1.x:
1123 | version "1.3.0"
1124 | resolved "https://registry.yarnpkg.com/samsam/-/samsam-1.3.0.tgz#8d1d9350e25622da30de3e44ba692b5221ab7c50"
1125 |
1126 | "semver@2 >=2.2.1 || 3.x || 4 || 5", "semver@2 || 3 || 4 || 5", semver@^5.1.0:
1127 | version "5.4.1"
1128 | resolved "https://registry.yarnpkg.com/semver/-/semver-5.4.1.tgz#e059c09d8571f0540823733433505d3a2f00b18e"
1129 |
1130 | send@0.16.1:
1131 | version "0.16.1"
1132 | resolved "https://registry.yarnpkg.com/send/-/send-0.16.1.tgz#a70e1ca21d1382c11d0d9f6231deb281080d7ab3"
1133 | dependencies:
1134 | debug "2.6.9"
1135 | depd "~1.1.1"
1136 | destroy "~1.0.4"
1137 | encodeurl "~1.0.1"
1138 | escape-html "~1.0.3"
1139 | etag "~1.8.1"
1140 | fresh "0.5.2"
1141 | http-errors "~1.6.2"
1142 | mime "1.4.1"
1143 | ms "2.0.0"
1144 | on-finished "~2.3.0"
1145 | range-parser "~1.2.0"
1146 | statuses "~1.3.1"
1147 |
1148 | serve-static@1.13.1:
1149 | version "1.13.1"
1150 | resolved "https://registry.yarnpkg.com/serve-static/-/serve-static-1.13.1.tgz#4c57d53404a761d8f2e7c1e8a18a47dbf278a719"
1151 | dependencies:
1152 | encodeurl "~1.0.1"
1153 | escape-html "~1.0.3"
1154 | parseurl "~1.3.2"
1155 | send "0.16.1"
1156 |
1157 | setprototypeof@1.0.3:
1158 | version "1.0.3"
1159 | resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.0.3.tgz#66567e37043eeb4f04d91bd658c0cbefb55b8e04"
1160 |
1161 | setprototypeof@1.1.0:
1162 | version "1.1.0"
1163 | resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.1.0.tgz#d0bd85536887b6fe7c0d818cb962d9d91c54e656"
1164 |
1165 | sinon@^4.1.3:
1166 | version "4.1.3"
1167 | resolved "https://registry.yarnpkg.com/sinon/-/sinon-4.1.3.tgz#fc599eda47ed9f1a694ce774b94ab44260bd7ac5"
1168 | dependencies:
1169 | diff "^3.1.0"
1170 | formatio "1.2.0"
1171 | lodash.get "^4.4.2"
1172 | lolex "^2.2.0"
1173 | nise "^1.2.0"
1174 | supports-color "^4.4.0"
1175 | type-detect "^4.0.5"
1176 |
1177 | slide@^1.1.3:
1178 | version "1.1.6"
1179 | resolved "https://registry.yarnpkg.com/slide/-/slide-1.1.6.tgz#56eb027d65b4d2dce6cb2e2d32c4d4afc9e1d707"
1180 |
1181 | sntp@2.x.x:
1182 | version "2.1.0"
1183 | resolved "https://registry.yarnpkg.com/sntp/-/sntp-2.1.0.tgz#2c6cec14fedc2222739caf9b5c3d85d1cc5a2cc8"
1184 | dependencies:
1185 | hoek "4.x.x"
1186 |
1187 | spdx-correct@~1.0.0:
1188 | version "1.0.2"
1189 | resolved "https://registry.yarnpkg.com/spdx-correct/-/spdx-correct-1.0.2.tgz#4b3073d933ff51f3912f03ac5519498a4150db40"
1190 | dependencies:
1191 | spdx-license-ids "^1.0.2"
1192 |
1193 | spdx-expression-parse@~1.0.0:
1194 | version "1.0.4"
1195 | resolved "https://registry.yarnpkg.com/spdx-expression-parse/-/spdx-expression-parse-1.0.4.tgz#9bdf2f20e1f40ed447fbe273266191fced51626c"
1196 |
1197 | spdx-license-ids@^1.0.2:
1198 | version "1.2.2"
1199 | resolved "https://registry.yarnpkg.com/spdx-license-ids/-/spdx-license-ids-1.2.2.tgz#c9df7a3424594ade6bd11900d596696dc06bac57"
1200 |
1201 | sshpk@^1.7.0:
1202 | version "1.13.1"
1203 | resolved "https://registry.yarnpkg.com/sshpk/-/sshpk-1.13.1.tgz#512df6da6287144316dc4c18fe1cf1d940739be3"
1204 | dependencies:
1205 | asn1 "~0.2.3"
1206 | assert-plus "^1.0.0"
1207 | dashdash "^1.12.0"
1208 | getpass "^0.1.1"
1209 | optionalDependencies:
1210 | bcrypt-pbkdf "^1.0.0"
1211 | ecc-jsbn "~0.1.1"
1212 | jsbn "~0.1.0"
1213 | tweetnacl "~0.14.0"
1214 |
1215 | "statuses@>= 1.3.1 < 2":
1216 | version "1.4.0"
1217 | resolved "https://registry.yarnpkg.com/statuses/-/statuses-1.4.0.tgz#bb73d446da2796106efcc1b601a253d6c46bd087"
1218 |
1219 | statuses@~1.3.1:
1220 | version "1.3.1"
1221 | resolved "https://registry.yarnpkg.com/statuses/-/statuses-1.3.1.tgz#faf51b9eb74aaef3b3acf4ad5f61abf24cb7b93e"
1222 |
1223 | string_decoder@~0.10.x:
1224 | version "0.10.31"
1225 | resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-0.10.31.tgz#62e203bc41766c6c28c9fc84301dab1c5310fa94"
1226 |
1227 | string_decoder@~1.0.3:
1228 | version "1.0.3"
1229 | resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.0.3.tgz#0fc67d7c141825de94282dd536bec6b9bce860ab"
1230 | dependencies:
1231 | safe-buffer "~5.1.0"
1232 |
1233 | stringstream@~0.0.5:
1234 | version "0.0.5"
1235 | resolved "https://registry.yarnpkg.com/stringstream/-/stringstream-0.0.5.tgz#4e484cd4de5a0bbbee18e46307710a8a81621878"
1236 |
1237 | supports-color@3.1.2:
1238 | version "3.1.2"
1239 | resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-3.1.2.tgz#72a262894d9d408b956ca05ff37b2ed8a6e2a2d5"
1240 | dependencies:
1241 | has-flag "^1.0.0"
1242 |
1243 | supports-color@4.4.0:
1244 | version "4.4.0"
1245 | resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-4.4.0.tgz#883f7ddabc165142b2a61427f3352ded195d1a3e"
1246 | dependencies:
1247 | has-flag "^2.0.0"
1248 |
1249 | supports-color@^4.4.0:
1250 | version "4.5.0"
1251 | resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-4.5.0.tgz#be7a0de484dec5c5cddf8b3d59125044912f635b"
1252 | dependencies:
1253 | has-flag "^2.0.0"
1254 |
1255 | symbol-observable@1.0.1:
1256 | version "1.0.1"
1257 | resolved "https://registry.yarnpkg.com/symbol-observable/-/symbol-observable-1.0.1.tgz#8340fc4702c3122df5d22288f88283f513d3fdd4"
1258 |
1259 | text-encoding@^0.6.4:
1260 | version "0.6.4"
1261 | resolved "https://registry.yarnpkg.com/text-encoding/-/text-encoding-0.6.4.tgz#e399a982257a276dae428bb92845cb71bdc26d19"
1262 |
1263 | touch@0.0.3:
1264 | version "0.0.3"
1265 | resolved "https://registry.yarnpkg.com/touch/-/touch-0.0.3.tgz#51aef3d449571d4f287a5d87c9c8b49181a0db1d"
1266 | dependencies:
1267 | nopt "~1.0.10"
1268 |
1269 | tough-cookie@~2.3.3:
1270 | version "2.3.3"
1271 | resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-2.3.3.tgz#0b618a5565b6dea90bf3425d04d55edc475a7561"
1272 | dependencies:
1273 | punycode "^1.4.1"
1274 |
1275 | "traverse@>=0.3.0 <0.4":
1276 | version "0.3.9"
1277 | resolved "https://registry.yarnpkg.com/traverse/-/traverse-0.3.9.tgz#717b8f220cc0bb7b44e40514c22b2e8bbc70d8b9"
1278 |
1279 | tunnel-agent@^0.6.0:
1280 | version "0.6.0"
1281 | resolved "https://registry.yarnpkg.com/tunnel-agent/-/tunnel-agent-0.6.0.tgz#27a5dea06b36b04a0a9966774b290868f0fc40fd"
1282 | dependencies:
1283 | safe-buffer "^5.0.1"
1284 |
1285 | tweetnacl@^0.14.3, tweetnacl@~0.14.0:
1286 | version "0.14.5"
1287 | resolved "https://registry.yarnpkg.com/tweetnacl/-/tweetnacl-0.14.5.tgz#5ae68177f192d4456269d108afa93ff8743f4f64"
1288 |
1289 | type-detect@^4.0.5:
1290 | version "4.0.5"
1291 | resolved "https://registry.yarnpkg.com/type-detect/-/type-detect-4.0.5.tgz#d70e5bc81db6de2a381bcaca0c6e0cbdc7635de2"
1292 |
1293 | type-is@~1.6.15:
1294 | version "1.6.15"
1295 | resolved "https://registry.yarnpkg.com/type-is/-/type-is-1.6.15.tgz#cab10fb4909e441c82842eafe1ad646c81804410"
1296 | dependencies:
1297 | media-typer "0.3.0"
1298 | mime-types "~2.1.15"
1299 |
1300 | typedarray@^0.0.6:
1301 | version "0.0.6"
1302 | resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777"
1303 |
1304 | unpipe@1.0.0, unpipe@~1.0.0:
1305 | version "1.0.0"
1306 | resolved "https://registry.yarnpkg.com/unpipe/-/unpipe-1.0.0.tgz#b2bf4ee8514aae6165b4817829d21b2ef49904ec"
1307 |
1308 | util-deprecate@~1.0.1:
1309 | version "1.0.2"
1310 | resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf"
1311 |
1312 | utils-merge@1.0.1:
1313 | version "1.0.1"
1314 | resolved "https://registry.yarnpkg.com/utils-merge/-/utils-merge-1.0.1.tgz#9f95710f50a267947b2ccc124741c1028427e713"
1315 |
1316 | uuid@^3.0.0, uuid@^3.1.0:
1317 | version "3.1.0"
1318 | resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.1.0.tgz#3dd3d3e790abc24d7b0d3a034ffababe28ebbc04"
1319 |
1320 | validate-npm-package-license@^3.0.1:
1321 | version "3.0.1"
1322 | resolved "https://registry.yarnpkg.com/validate-npm-package-license/-/validate-npm-package-license-3.0.1.tgz#2804babe712ad3379459acfbe24746ab2c303fbc"
1323 | dependencies:
1324 | spdx-correct "~1.0.0"
1325 | spdx-expression-parse "~1.0.0"
1326 |
1327 | vary@~1.1.2:
1328 | version "1.1.2"
1329 | resolved "https://registry.yarnpkg.com/vary/-/vary-1.1.2.tgz#2299f02c6ded30d4a5961b0b9f74524a18f634fc"
1330 |
1331 | verror@1.10.0:
1332 | version "1.10.0"
1333 | resolved "https://registry.yarnpkg.com/verror/-/verror-1.10.0.tgz#3a105ca17053af55d6e270c1f8288682e18da400"
1334 | dependencies:
1335 | assert-plus "^1.0.0"
1336 | core-util-is "1.0.2"
1337 | extsprintf "^1.2.0"
1338 |
1339 | wrappy@1:
1340 | version "1.0.2"
1341 | resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f"
1342 |
1343 | xhr2@^0.1.4:
1344 | version "0.1.4"
1345 | resolved "https://registry.yarnpkg.com/xhr2/-/xhr2-0.1.4.tgz#7f87658847716db5026323812f818cadab387a5f"
1346 |
1347 | xml@^1.0.0:
1348 | version "1.0.1"
1349 | resolved "https://registry.yarnpkg.com/xml/-/xml-1.0.1.tgz#78ba72020029c5bc87b8a81a3cfcd74b4a2fc1e5"
1350 |
--------------------------------------------------------------------------------