Skip to content
This repository was archived by the owner on Jul 9, 2025. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from 18 commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
7d18388
move server tests into local __tests__ directories
a-b-r-o-w-n Sep 15, 2020
a156ce7
add tests for extensions controller
a-b-r-o-w-n Sep 15, 2020
cb6339a
set up jest for extension package
a-b-r-o-w-n Sep 16, 2020
7d58cb9
add build config
a-b-r-o-w-n Sep 16, 2020
f8a1adc
add test for manifest store
a-b-r-o-w-n Sep 16, 2020
f3451d6
change logger prefix
a-b-r-o-w-n Sep 16, 2020
08e0235
extract npm command to helper util
a-b-r-o-w-n Sep 16, 2020
3cb9f23
add missing dependency
a-b-r-o-w-n Sep 17, 2020
e88443f
rename env variables
a-b-r-o-w-n Sep 17, 2020
d89df1b
use different manifest for tests
a-b-r-o-w-n Sep 17, 2020
7fc71a4
rename parameter for clarity
a-b-r-o-w-n Sep 17, 2020
a11eead
load all extensions on server start
a-b-r-o-w-n Sep 17, 2020
66f3677
more name refactoring
a-b-r-o-w-n Sep 17, 2020
f730884
fix test mocks
a-b-r-o-w-n Sep 17, 2020
d7bb6e2
set up tests better
a-b-r-o-w-n Sep 21, 2020
820c9d4
load all extensions on server start
a-b-r-o-w-n Sep 21, 2020
1362169
refactor npm signature
a-b-r-o-w-n Sep 21, 2020
18de927
further scope npm search keywords
a-b-r-o-w-n Sep 21, 2020
3a18cec
apply code review feedback
a-b-r-o-w-n Sep 22, 2020
2db02de
Merge branch 'main' into abrown/extensions/install-from-npm
a-b-r-o-w-n Sep 22, 2020
d7a82f0
disable format message warnings for test
a-b-r-o-w-n Sep 22, 2020
3b3a370
reduce console noise in tests
a-b-r-o-w-n Sep 22, 2020
987f41e
make console override safer
a-b-r-o-w-n Sep 22, 2020
9ee8e46
load plugins from correct directory in test
a-b-r-o-w-n Sep 22, 2020
494b7d9
Merge branch 'main' into abrown/extensions/install-from-npm
a-b-r-o-w-n Sep 22, 2020
ac10396
Merge branch 'main' into abrown/extensions/install-from-npm
tonyanziano Sep 22, 2020
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions Composer/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,6 @@ packages/server/schemas/*.schema
packages/server/schemas/*.uischema
!packages/server/schemas/sdk.schema
!packages/server/schemas/sdk.uischema

# remote extensions
.composer
4 changes: 2 additions & 2 deletions Composer/packages/electron-server/src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ async function createAppDataDir() {
const azurePublishPath: string = join(composerAppDataPath, 'publishBots');
process.env.COMPOSER_APP_DATA = join(composerAppDataPath, 'data.json'); // path to the actual data file
process.env.COMPOSER_EXTENSION_DATA = join(composerAppDataPath, 'extensions.json');
process.env.COMPOSER_REMOTE_PLUGINS_DIR = join(composerAppDataPath, '.composer');
process.env.COMPOSER_REMOTE_EXTENSIONS_DIR = join(composerAppDataPath, '.composer');

log('creating composer app data path at: ', composerAppDataPath);

Expand Down Expand Up @@ -142,7 +142,7 @@ async function loadServer() {
// only change paths if packaged electron app
const unpackedDir = getUnpackedAsarPath();
process.env.COMPOSER_RUNTIME_FOLDER = join(unpackedDir, 'runtime');
process.env.COMPOSER_BUILTIN_PLUGINS_DIR = join(unpackedDir, 'build', 'plugins');
process.env.COMPOSER_BUILTIN_EXTENSIONS_DIR = join(unpackedDir, 'build', 'plugins');
}

// only create a new data directory if packaged electron app
Expand Down
3 changes: 3 additions & 0 deletions Composer/packages/extension/.gitignore
Original file line number Diff line number Diff line change
@@ -1 +1,4 @@
lib/
src/__tests__/__manifest__.json
src/__tests__/__builtin__
src/__tests__/__remote__
8 changes: 8 additions & 0 deletions Composer/packages/extension/jest.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
const path = require('path');

const { createConfig } = require('@bfc/test-utils');

module.exports = createConfig('extension', 'node', {
setupFiles: [path.resolve(__dirname, 'src/__tests__/setupEnv.ts')],
testPathIgnorePatterns: ['src/__tests__/setupEnv.ts'],
});
8 changes: 6 additions & 2 deletions Composer/packages/extension/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,15 @@
"private": true,
"scripts": {
"build": "yarn build:clean && yarn build:ts",
"build:ts": "tsc",
"build:ts": "tsc -p tsconfig.build.json",
"build:clean": "rimraf lib && rimraf build",
"lint": "eslint --quiet ./src"
"lint": "eslint --quiet ./src",
"test": "jest"
},
"devDependencies": {
"@bfc/test-utils": "*",
"@types/express": "^4.17.6",
"@types/fs-extra": "^9.0.1",
"@types/passport": "^1.0.3",
"@types/path-to-regexp": "^1.7.0",
"json-schema": "^0.2.5",
Expand All @@ -21,6 +24,7 @@
},
"dependencies": {
"debug": "^4.1.1",
"fs-extra": "^9.0.1",
"globby": "^11.0.0",
"passport": "^0.4.1",
"path-to-regexp": "^6.1.0"
Expand Down
8 changes: 8 additions & 0 deletions Composer/packages/extension/src/__tests__/setupEnv.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

import path from 'path';

process.env.COMPOSER_EXTENSION_DATA = path.resolve(__dirname, '__manifest__.json');
process.env.COMPOSER_BUILTIN_EXTENSIONS_DIR = path.resolve(__dirname, '__builtin__');
process.env.COMPOSER_REMOTE_EXTENSIONS_DIR = path.resolve(__dirname, '__remote__');
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,13 @@ import { pathToRegexp } from 'path-to-regexp';
import glob from 'globby';
import formatMessage from 'format-message';

import { UserIdentity, ExtensionCollection, RuntimeTemplate, DEFAULT_RUNTIME } from '../types/types';
import log from '../logger';
import { UserIdentity, ExtensionCollection, RuntimeTemplate, DEFAULT_RUNTIME } from './types/types';
import logger from './logger';
import { ExtensionRegistration } from './extensionRegistration';

import { ComposerPluginRegistration } from './composerPluginRegistration';
const log = logger.extend('extension-context');

export class PluginLoader {
class ExtensionContext {
private _passport: passport.PassportStatic;
private _webserver: Express | undefined;
public loginUri = '/login';
Expand Down Expand Up @@ -67,7 +68,8 @@ export class PluginLoader {
}

public async loadPlugin(name: string, description: string, thisPlugin: any) {
const pluginRegistration = new ComposerPluginRegistration(this, name, description);
log('Loading extension: %s', name);
const pluginRegistration = new ExtensionRegistration(this, name, description);
if (typeof thisPlugin.default === 'function') {
// the module exported just an init function
thisPlugin.default.call(null, pluginRegistration);
Expand All @@ -82,12 +84,12 @@ export class PluginLoader {
}
}

public async loadPluginFromFile(path: string) {
const packageJSON = fs.readFileSync(path, 'utf8');
public async loadPluginFromFile(packageJsonPath: string) {
const packageJSON = fs.readFileSync(packageJsonPath, 'utf8');
const json = JSON.parse(packageJSON);

if (json.extendsComposer) {
const modulePath = path.replace(/package\.json$/, '');
const modulePath = path.dirname(packageJsonPath);
try {
// eslint-disable-next-line security/detect-non-literal-require, @typescript-eslint/no-var-requires
const thisPlugin = require(modulePath);
Expand Down Expand Up @@ -131,10 +133,10 @@ export class PluginLoader {
}
}

static async getUserFromRequest(req): Promise<UserIdentity | undefined> {
public async getUserFromRequest(req): Promise<UserIdentity | undefined> {
return req.user || undefined;
}
}

export const pluginLoader = new PluginLoader();
export default pluginLoader;
const context = new ExtensionContext();
export { context as ExtensionContext };
Original file line number Diff line number Diff line change
Expand Up @@ -4,26 +4,27 @@
import { RequestHandler } from 'express-serve-static-core';
import { Debugger } from 'debug';

import log from '../logger';
import { PublishPlugin, RuntimeTemplate, BotTemplate } from '../types/types';
import logger from './logger';
import { PublishPlugin, RuntimeTemplate, BotTemplate } from './types/types';
import { ExtensionContext } from './extensionContext';

import { PluginLoader } from './pluginLoader';
const log = logger.extend('extension-registration');

export class ComposerPluginRegistration {
public loader: PluginLoader;
export class ExtensionRegistration {
public context: typeof ExtensionContext;
private _name: string;
private _description: string;
private _log: Debugger;

constructor(loader: PluginLoader, name: string, description: string) {
this.loader = loader;
constructor(context: typeof ExtensionContext, name: string, description: string) {
this.context = context;
this._name = name;
this._description = description;
this._log = log.extend(name);
}

public get passport() {
return this.loader.passport;
return this.context.passport;
}

public get name(): string {
Expand All @@ -46,8 +47,8 @@ export class ComposerPluginRegistration {
* Storage related features
*************************************************************************************/
public async useStorage(customStorageClass: any) {
if (!this.loader.extensions.storage.customStorageClass) {
this.loader.extensions.storage.customStorageClass = customStorageClass;
if (!this.context.extensions.storage.customStorageClass) {
this.context.extensions.storage.customStorageClass = customStorageClass;
} else {
throw new Error('Cannot redefine storage driver once set.');
}
Expand All @@ -58,7 +59,7 @@ export class ComposerPluginRegistration {
*************************************************************************************/
public async addPublishMethod(plugin: PublishPlugin) {
log('registering publish method', this.name);
this.loader.extensions.publish[plugin.customName || this.name] = {
this.context.extensions.publish[plugin.customName || this.name] = {
plugin: {
name: plugin.customName || this.name,
description: plugin.customDescription || this.description,
Expand Down Expand Up @@ -90,56 +91,56 @@ export class ComposerPluginRegistration {
* ```
*/
public addRuntimeTemplate(plugin: RuntimeTemplate) {
this.loader.extensions.runtimeTemplates.push(plugin);
this.context.extensions.runtimeTemplates.push(plugin);
}

/**************************************************************************************
* Get current runtime from project
*************************************************************************************/
public getRuntimeByProject(project): RuntimeTemplate {
return this.loader.getRuntimeByProject(project);
return this.context.getRuntimeByProject(project);
}

/**************************************************************************************
* Get current runtime by type
*************************************************************************************/
public getRuntime(type: string | undefined): RuntimeTemplate {
return this.loader.getRuntime(type);
return this.context.getRuntime(type);
}

/**************************************************************************************
* Add Bot Template (aka, SampleBot)
*************************************************************************************/
public addBotTemplate(template: BotTemplate) {
this.loader.extensions.botTemplates.push(template);
this.context.extensions.botTemplates.push(template);
}

/**************************************************************************************
* Add Base Template (aka, BoilerPlate)
*************************************************************************************/
public addBaseTemplate(template: BotTemplate) {
this.loader.extensions.baseTemplates.push(template);
this.context.extensions.baseTemplates.push(template);
}

/**************************************************************************************
* Express/web related features
*************************************************************************************/
public addWebMiddleware(middleware: RequestHandler) {
if (!this.loader.webserver) {
if (!this.context.webserver) {
throw new Error('Plugin loaded in context without webserver. Cannot add web middleware.');
} else {
this.loader.webserver.use(middleware);
this.context.webserver.use(middleware);
}
}

public addWebRoute(type: string, url: string, ...handlers: RequestHandler[]) {
if (!this.loader.webserver) {
if (!this.context.webserver) {
throw new Error('Plugin loaded in context without webserver. Cannot add web route.');
} else {
const method = this.loader.webserver[type.toLowerCase()];
const method = this.context.webserver[type.toLowerCase()];

if (typeof method === 'function') {
method.call(this.loader.webserver, url, ...handlers);
method.call(this.context.webserver, url, ...handlers);
} else {
throw new Error(`Unhandled web route type ${type}`);
}
Expand All @@ -151,55 +152,55 @@ export class ComposerPluginRegistration {
*************************************************************************************/
public usePassportStrategy(passportStrategy) {
// set up the passport strategy to be used
this.loader.passport.use(passportStrategy);
this.context.passport.use(passportStrategy);

// bind a basic auth middleware. this can be overridden. see setAuthMiddleware below
this.loader.extensions.authentication.middleware = (req, res, next) => {
this.context.extensions.authentication.middleware = (req, res, next) => {
if (req.isAuthenticated()) {
next();
} else {
log('Rejecting access to ', req.url);
res.redirect(this.loader.loginUri);
res.redirect(this.context.loginUri);
}
};

// set up default serializer, takes entire object and json encodes
this.loader.extensions.authentication.serializeUser = (user, done) => {
this.context.extensions.authentication.serializeUser = (user, done) => {
done(null, JSON.stringify(user));
};

// set up default deserializer.
this.loader.extensions.authentication.deserializeUser = (user, done) => {
this.context.extensions.authentication.deserializeUser = (user, done) => {
done(null, JSON.parse(user));
};

// use a wrapper on the serializer that calls configured serializer
this.passport.serializeUser((user, done) => {
if (this.loader.extensions.authentication.serializeUser) {
this.loader.extensions.authentication.serializeUser(user, done);
if (this.context.extensions.authentication.serializeUser) {
this.context.extensions.authentication.serializeUser(user, done);
}
});

// use a wrapper on the deserializer that calls configured deserializer
this.passport.deserializeUser((user, done) => {
if (this.loader.extensions.authentication.deserializeUser) {
this.loader.extensions.authentication.deserializeUser(user, done);
if (this.context.extensions.authentication.deserializeUser) {
this.context.extensions.authentication.deserializeUser(user, done);
}
});
}

public useAuthMiddleware(middleware: RequestHandler) {
this.loader.extensions.authentication.middleware = middleware;
this.context.extensions.authentication.middleware = middleware;
}

public useUserSerializers(serialize, deserialize) {
this.loader.extensions.authentication.serializeUser = serialize;
this.loader.extensions.authentication.deserializeUser = deserialize;
this.context.extensions.authentication.serializeUser = serialize;
this.context.extensions.authentication.deserializeUser = deserialize;
}

public addAllowedUrl(url: string) {
if (this.loader.extensions.authentication.allowedUrls.indexOf(url) < 0) {
this.loader.extensions.authentication.allowedUrls.push(url);
if (this.context.extensions.authentication.allowedUrls.indexOf(url) < 0) {
this.context.extensions.authentication.allowedUrls.push(url);
}
}
}
3 changes: 2 additions & 1 deletion Composer/packages/extension/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@

export { JSONSchema7 } from 'json-schema';

export * from './loader';
export * from './manager';
export * from './storage';
export * from './types/types';
export * from './extensionContext';
export * from './extensionRegistration';
5 changes: 0 additions & 5 deletions Composer/packages/extension/src/loader/index.ts

This file was deleted.

2 changes: 1 addition & 1 deletion Composer/packages/extension/src/logger.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,4 @@

import debug from 'debug';

export default debug('composer:plugins');
export default debug('composer:extensions');
Loading