Unverified Commit 200c8cd0 authored by Bradley Hilton's avatar Bradley Hilton
Browse files

Rename the environment from rocketlets to apps

parent 5aa19523
import { IRocketletActivationBridge } from 'temporary-rocketlets-server/server/bridges';
import { ProxiedRocketlet } from 'temporary-rocketlets-server/server/ProxiedRocketlet';
import { IAppActivationBridge } from '@rocket.chat/apps-engine/server/bridges';
import { ProxiedApp } from '@rocket.chat/apps-engine/server/ProxiedApp';
export class ServerRocketletActivationBridge implements IRocketletActivationBridge {
public rocketletEnabled(rocketlet: ProxiedRocketlet): void {
console.log(`The Rocketlet ${ rocketlet.getName() } (${ rocketlet.getID() }) has been enabled.`);
export class ServerAppActivationBridge implements IAppActivationBridge {
public appEnabled(app: ProxiedApp): void {
console.log(`The App ${ app.getName() } (${ app.getID() }) has been enabled.`);
}
public rocketletDisabled(rocketlet: ProxiedRocketlet): void {
console.log(`The Rocketlet ${ rocketlet.getName() } (${ rocketlet.getID() }) has been disabled.`);
public appDisabled(app: ProxiedApp): void {
console.log(`The App ${ app.getName() } (${ app.getID() }) has been disabled.`);
}
public rocketletLoaded(rocketlet: ProxiedRocketlet, enabled: boolean): void {
console.log(`The Rocketlet ${ rocketlet.getName() } (${ rocketlet.getID() }) has been loaded.`);
public appLoaded(app: ProxiedApp, enabled: boolean): void {
console.log(`The App ${ app.getName() } (${ app.getID() }) has been loaded.`);
}
public rocketletUpdated(rocketlet: ProxiedRocketlet, enabled: boolean): void {
console.log(`The Rocketlet ${ rocketlet.getName() } (${ rocketlet.getID() }) has been updated.`);
public appUpdated(app: ProxiedApp, enabled: boolean): void {
console.log(`The App ${ app.getName() } (${ app.getID() }) has been updated.`);
}
public rocketletRemoved(rocketlet: ProxiedRocketlet): void {
console.log(`The Rocketlet ${ rocketlet.getName() } (${ rocketlet.getID() }) has been removed.`);
public appRemoved(app: ProxiedApp): void {
console.log(`The App ${ app.getName() } (${ app.getID() }) has been removed.`);
}
}
import { ServerRocketletActivationBridge } from './activation';
import { ServerAppActivationBridge } from './activation';
import { ServerCommandBridge } from './command';
import { ServerEnvironmentalVariableBridge } from './environmental';
import { ServerSettingBridge } from './settings';
import {
AppBridges,
IAppActivationBridge,
IAppCommandBridge,
IEnvironmentalVariableBridge,
IHttpBridge,
IMessageBridge,
IPersistenceBridge,
IRocketletActivationBridge,
IRocketletCommandBridge,
IRoomBridge,
IServerSettingBridge,
IUserBridge,
RocketletBridges,
} from 'temporary-rocketlets-server/server/bridges';
} from '@rocket.chat/apps-engine/server/bridges';
export class ServerRocketletBridges extends RocketletBridges {
export class ServerAppBridges extends AppBridges {
private readonly cmdBridge: ServerCommandBridge;
private readonly setsBridge: ServerSettingBridge;
private readonly envBridge: ServerEnvironmentalVariableBridge;
private readonly actsBridge: ServerRocketletActivationBridge;
private readonly actsBridge: ServerAppActivationBridge;
constructor() {
super();
this.cmdBridge = new ServerCommandBridge();
this.setsBridge = new ServerSettingBridge();
this.envBridge = new ServerEnvironmentalVariableBridge();
this.actsBridge = new ServerRocketletActivationBridge();
this.actsBridge = new ServerAppActivationBridge();
}
public getCommandBridge(): ServerCommandBridge {
......@@ -54,7 +54,7 @@ export class ServerRocketletBridges extends RocketletBridges {
throw new Error('Method not implemented.');
}
public getRocketletActivationBridge(): IRocketletActivationBridge {
public getAppActivationBridge(): IAppActivationBridge {
return this.actsBridge;
}
......
import { IRocketletCommandBridge } from 'temporary-rocketlets-server/server/bridges';
import { ISlashCommand, SlashCommandContext } from 'temporary-rocketlets-ts-definition/slashcommands';
import { IAppCommandBridge } from '@rocket.chat/apps-engine/server/bridges';
import { ISlashCommand, SlashCommandContext } from '@rocket.chat/apps-ts-definition/slashcommands';
export class ServerCommandBridge implements IRocketletCommandBridge {
export class ServerCommandBridge implements IAppCommandBridge {
private commands: Map<string, ISlashCommand>;
constructor() {
......@@ -12,25 +12,25 @@ export class ServerCommandBridge implements IRocketletCommandBridge {
return Array.from(this.commands.keys());
}
public doesCommandExist(command: string, rocketletId: string): boolean {
public doesCommandExist(command: string, appId: string): boolean {
console.log('Checking if the command exists:', command);
return this.commands.has(command);
}
public enableCommand(command: string, rocketletId: string): void {
console.log(`Enabling the command "${command}" per request of the rocketlet: ${rocketletId}`);
public enableCommand(command: string, appId: string): void {
console.log(`Enabling the command "${command}" per request of the app: ${appId}`);
}
public disableCommand(command: string, rocketletId: string): void {
console.log(`Disabling the command "${command}" per request of the rocketlet: ${rocketletId}`);
public disableCommand(command: string, appId: string): void {
console.log(`Disabling the command "${command}" per request of the app: ${appId}`);
}
public modifyCommand(command: ISlashCommand, rocketletId: string): void {
public modifyCommand(command: ISlashCommand, appId: string): void {
throw new Error('Not implemented.');
}
// tslint:disable-next-line:max-line-length
public registerCommand(command: ISlashCommand, rocketletId: string): void {
public registerCommand(command: ISlashCommand, appId: string): void {
if (this.commands.has(command.command)) {
throw new Error(`Command "${command.command}" has already been registered.`);
}
......@@ -39,7 +39,7 @@ export class ServerCommandBridge implements IRocketletCommandBridge {
console.log(`Registered the command "${command.command}".`);
}
public unregisterCommand(command: string, rocketletId: string): void {
public unregisterCommand(command: string, appId: string): void {
const removed = this.commands.delete(command);
if (removed) {
......
import { IEnvironmentalVariableBridge } from 'temporary-rocketlets-server/server/bridges';
import { IEnvironmentalVariableBridge } from '@rocket.chat/apps-engine/server/bridges';
export class ServerEnvironmentalVariableBridge implements IEnvironmentalVariableBridge {
public getValueByName(envVarName: string, rocketletId: string): string {
public getValueByName(envVarName: string, appId: string): string {
throw new Error('Method not implemented.');
}
public isReadable(envVarName: string, rocketletId: string): boolean {
public isReadable(envVarName: string, appId: string): boolean {
throw new Error('Method not implemented.');
}
public isSet(envVarName: string, rocketletId: string): boolean {
public isSet(envVarName: string, appId: string): boolean {
throw new Error('Method not implemented.');
}
}
import { IServerSettingBridge } from 'temporary-rocketlets-server/server/bridges';
import { ISetting } from 'temporary-rocketlets-ts-definition/settings';
import { IServerSettingBridge } from '@rocket.chat/apps-engine/server/bridges';
import { ISetting } from '@rocket.chat/apps-ts-definition/settings';
export class ServerSettingBridge implements IServerSettingBridge {
public getAll(rocketletId: string): Array<ISetting> {
public getAll(appId: string): Array<ISetting> {
throw new Error('Method not implemented.');
}
public getOneById(id: string, rocketletId: string): ISetting {
public getOneById(id: string, appId: string): ISetting {
throw new Error('Method not implemented.');
}
public hideGroup(name: string, rocketletId: string): void {
public hideGroup(name: string, appId: string): void {
throw new Error('Method not implemented.');
}
public hideSetting(id: string, rocketletId: string): void {
public hideSetting(id: string, appId: string): void {
throw new Error('Method not implemented.');
}
public isReadableById(id: string, rocketletId: string): boolean {
public isReadableById(id: string, appId: string): boolean {
throw new Error('Method not implemented.');
}
public updateOne(setting: ISetting, rocketletId: string): void {
public updateOne(setting: ISetting, appId: string): void {
throw new Error('Method not implemented.');
}
}
import { AppManager } from '@rocket.chat/apps-engine/server/AppManager';
import { App } from '@rocket.chat/apps-ts-definition/App';
import * as fs from 'fs';
import * as path from 'path';
import * as socketIO from 'socket.io';
import { RocketletManager } from 'temporary-rocketlets-server/server/RocketletManager';
import { Rocketlet } from 'temporary-rocketlets-ts-definition/Rocketlet';
import { ServerRocketletBridges } from './bridges/bridges';
import { ServerRocketletStorage } from './storage';
import { ServerAppBridges } from './bridges/bridges';
import { ServerAppStorage } from './storage';
export class Orchestrator {
public bridges: ServerRocketletBridges;
public storage: ServerRocketletStorage;
public manager: RocketletManager;
public bridges: ServerAppBridges;
public storage: ServerAppStorage;
public manager: AppManager;
private io: SocketIO.Server;
constructor() {
this.bridges = new ServerRocketletBridges();
this.storage = new ServerRocketletStorage();
this.manager = new RocketletManager(this.storage, this.bridges);
this.bridges = new ServerAppBridges();
this.storage = new ServerAppStorage();
this.manager = new AppManager(this.storage, this.bridges);
}
public loadAndUpdate(): Promise<boolean> {
......@@ -26,7 +26,7 @@ export class Orchestrator {
.filter((file) => file.endsWith('.zip') && fs.statSync(path.join('dist', file)).isFile())
.map((file) => fs.readFileSync(path.join('dist', file), 'base64'))
.map((zip) => this.manager.add(zip).catch((err: Error) => {
if (err.message === 'Rocketlet already exists.') {
if (err.message === 'App already exists.') {
return this.manager.update(zip);
} else {
console.log(err);
......@@ -35,8 +35,8 @@ export class Orchestrator {
})));
}).then(() => {
if (typeof this.io !== 'undefined') {
this.io.emit('status', { loaded: this.manager.areRocketletsLoaded() });
this.sendRocketletsInfo();
this.io.emit('status', { loaded: this.manager.areAppsLoaded() });
this.sendAppsInfo();
}
this.manager.get().forEach((rl) => console.log('Successfully loaded:', rl.getName()));
......@@ -48,8 +48,8 @@ export class Orchestrator {
this.io = server;
this.io.on('connection', (socket) => {
socket.emit('status', { loaded: this.manager.areRocketletsLoaded() });
this.sendRocketletsInfo(socket);
socket.emit('status', { loaded: this.manager.areAppsLoaded() });
this.sendAppsInfo(socket);
socket.on('get/enabled', (fn) => {
fn(this.manager.get({ enabled: true }).map((rl) => rl.getInfo()));
......@@ -67,14 +67,14 @@ export class Orchestrator {
});
}
private sendRocketletsInfo(socket?: SocketIO.Socket): void {
private sendAppsInfo(socket?: SocketIO.Socket): void {
const enabled = this.manager.get({ enabled: true }).map((rl) => rl.getInfo());
const disabled = this.manager.get({ disabled: true }).map((rl) => rl.getInfo());
if (socket) {
socket.emit('rocketlets', { enabled, disabled });
socket.emit('apps', { enabled, disabled });
} else {
this.io.emit('rocketlets', { enabled, disabled });
this.io.emit('apps', { enabled, disabled });
}
}
}
......@@ -14,11 +14,11 @@ app.use(bodyParser.json());
app.use(express.static(path.join(__dirname, 'site')));
app.get('/loaded', (req, res) => {
res.json({ rocketlets: orch.manager.get().map((rc) => rc.getName()) });
res.json({ apps: orch.manager.get().map((rc) => rc.getName()) });
});
app.post('/load', (req, res) => {
if (req.body.rocketletId) {
if (req.body.appId) {
res.status(501).json({ success: false, err: 'Coming soon.' });
} else {
orch = new Orchestrator();
......
import { AppStorage, IAppStorageItem } from '@rocket.chat/apps-engine/server/storage';
import * as Datastore from 'nedb';
import { IRocketletStorageItem, RocketletStorage } from 'temporary-rocketlets-server/server/storage';
export class ServerRocketletStorage extends RocketletStorage {
export class ServerAppStorage extends AppStorage {
private db: Datastore;
constructor() {
super('nedb');
this.db = new Datastore({ filename: '.server-data/rocketlets.nedb', autoload: true });
this.db = new Datastore({ filename: '.server-data/apps.nedb', autoload: true });
this.db.ensureIndex({ fieldName: 'id', unique: true });
}
public create(item: IRocketletStorageItem): Promise<IRocketletStorageItem> {
public create(item: IAppStorageItem): Promise<IAppStorageItem> {
return new Promise((resolve, reject) => {
item.createdAt = new Date();
item.updatedAt = new Date();
// tslint:disable-next-line
this.db.findOne({ $or: [{ id: item.id }, { 'info.nameSlug': item.info.nameSlug }] }, (err: Error, doc: IRocketletStorageItem) => {
this.db.findOne({ $or: [{ id: item.id }, { 'info.nameSlug': item.info.nameSlug }] }, (err: Error, doc: IAppStorageItem) => {
if (err) {
reject(err);
} else if (doc) {
reject(new Error('Rocketlet already exists.'));
reject(new Error('App already exists.'));
} else {
this.db.insert(item, (err2: Error, doc2: IRocketletStorageItem) => {
this.db.insert(item, (err2: Error, doc2: IAppStorageItem) => {
if (err2) {
reject(err2);
} else {
......@@ -34,9 +34,9 @@ export class ServerRocketletStorage extends RocketletStorage {
});
}
public retrieveOne(id: string): Promise<IRocketletStorageItem> {
public retrieveOne(id: string): Promise<IAppStorageItem> {
return new Promise((resolve, reject) => {
this.db.findOne({ id }, (err: Error, doc: IRocketletStorageItem) => {
this.db.findOne({ id }, (err: Error, doc: IAppStorageItem) => {
if (err) {
reject(err);
} else if (doc) {
......@@ -48,13 +48,13 @@ export class ServerRocketletStorage extends RocketletStorage {
});
}
public retrieveAll(): Promise<Map<string, IRocketletStorageItem>> {
public retrieveAll(): Promise<Map<string, IAppStorageItem>> {
return new Promise((resolve, reject) => {
this.db.find({}, (err: Error, docs: Array<IRocketletStorageItem>) => {
this.db.find({}, (err: Error, docs: Array<IAppStorageItem>) => {
if (err) {
reject(err);
} else {
const items = new Map<string, IRocketletStorageItem>();
const items = new Map<string, IAppStorageItem>();
docs.forEach((i) => items.set(i.id, i));
......@@ -64,13 +64,13 @@ export class ServerRocketletStorage extends RocketletStorage {
});
}
public update(item: IRocketletStorageItem): Promise<IRocketletStorageItem> {
public update(item: IAppStorageItem): Promise<IAppStorageItem> {
return new Promise((resolve, reject) => {
this.db.update({ id: item.id }, item, (err: Error, numOfUpdated: number) => {
if (err) {
reject(err);
} else {
this.retrieveOne(item.id).then((updated: IRocketletStorageItem) => resolve(updated))
this.retrieveOne(item.id).then((updated: IAppStorageItem) => resolve(updated))
.catch((err2: Error) => reject(err2));
}
});
......
import { RocketletServerCommunicator } from 'temporary-rocketlets-server/client/RocketletServerCommunicator';
import { IRocketletInfo } from 'temporary-rocketlets-ts-definition/metadata/IRocketletInfo';
import { AppServerCommunicator } from '@rocket.chat/apps-engine/client/AppServerCommunicator';
import { IAppInfo } from '@rocket.chat/apps-ts-definition/metadata/IAppInfo';
export class DevCommunicator extends RocketletServerCommunicator {
export class DevCommunicator extends AppServerCommunicator {
constructor(private readonly socket: SocketIO.Socket) {
super();
}
public getEnabledRocketlets(): Promise<Array<IRocketletInfo>> {
public getEnabledApps(): Promise<Array<IAppInfo>> {
return new Promise((resolve) => {
this.socket.emit('get/enabled', (enableds) => {
resolve(enableds);
......@@ -14,7 +14,7 @@ export class DevCommunicator extends RocketletServerCommunicator {
});
}
public getDisabledRocketlets(): Promise<Array<IRocketletInfo>> {
public getDisabledApps(): Promise<Array<IAppInfo>> {
return new Promise((resolve) => {
this.socket.emit('get/disabled', (disableds) => {
resolve(disableds);
......
<!DOCTYPE html>
<html>
<head>
<title>Rocketlets Dev Env</title>
<title>Rocket.Chat Apps Dev Env</title>
<link rel="stylesheet" type="text/css" href="index.css">
<link rel="stylesheet" type="text/css" href="fontello/css/fontello.css">
</head>
......@@ -13,10 +13,10 @@
<h2>
<a href="#favorite" class="toggle-favorite"><i class="icon-star favorite-room"></i></a>
<i class="icon-hash"></i>
<span class="room-title">rocketlets-dev</span>
<span class="room-title">apps-dev</span>
<span class="room-topic">
Here are some useful links for Rocketlet Development.
<a href="https://github.com/graywolf336/temporary-rocketlets-ts-definition" target="_blank">TypeScript Definition</a> |
Here are some useful links for Rocket.Chat App Development.
<a href="https://github.com/RocketChat/Rocket.Chat.Apps-ts-definition" target="_blank">TypeScript Definition</a> |
<a href="http://localhost:3003/fontello/demo.html" target="_blank">Usable Contextual Bar Button Icons</a>
</span>
</h2>
......
import { RocketletClientManager } from 'temporary-rocketlets-server/client/RocketletClientManager';
import { AppClientManager } from '@rocket.chat/apps-engine/client/AppClientManager';
import { DevCommunicator } from './client/communicator';
export class DevClient {
......@@ -9,7 +9,7 @@ export class DevClient {
this.socket.on('status', (data) => {
if (data.loaded) {
const manager = new RocketletClientManager(new DevCommunicator(this.socket));
const manager = new AppClientManager(new DevCommunicator(this.socket));
manager.load();
}
});
......
......@@ -2,9 +2,9 @@
"json.schemas": [
{
"fileMatch": [
"*rocketlet.json"
"*app.json"
],
"url": "./rocketlet-schema.json"
"url": "./app-schema.json"
}
],
"tslint.exclude": ["**node_modules**"]
......
# temporary-rocketlets-dev-environment
Development environment for getting started developing Rocketlets.
# Rocket.Chat Apps Development Environment
Development environment for getting started developing Rocket.Chat Apps.
## Getting Started
Extremely simple.
```
git clone git@github.com:graywolf336/temporary-rocketlets-dev-environment.git
git clone git@github.com:RocketChat/Rocket.Chat.Apps-dev-environment.git
npm install
npm start
```
This will watch the `rocketlets` directory for changes and run everything.
This will watch the `apps` directory for changes and run everything.
## Environment Layout Explained
In trying to make this a smooth development environment there were a few decisions made.
First up, unless you're extending the development environment, the only folder you should be concerned about is `rocketlets` and `dist`.
First up, unless you're extending the development environment, the only folder you should be concerned about is `apps` and `dist`.
The other folders are for making the development environment a pleasure to work with (hopefully).
The `.server` directory is the "mock" server with storage, this is where the code lives which mocks up the Rocket.Chat server.
## Generating a Rocketlet ID
We require UUID Version 4 for IDs. To generate one for your Rocketlet we recommend this site: https://www.uuidgenerator.net/version4
## Generating an App ID
We require UUID Version 4 for IDs. To generate one for your App we recommend this site: https://www.uuidgenerator.net/version4
## Logging Inside a Rocketlet
## Logging Inside a App
Due to limitations of NodeJS's `vm` package we have had to implement a custom logger class.
To make usage of this you can use `this.getLogger()` and then do the normal `console` style logging.
{
"$schema": "http://json-schema.org/draft-04/schema#",
"title": "Rocket.Chat Rocketlet",
"description": "A Rocketlet declaration for usage inside of Rocket.Chat.",
"title": "Rocket.Chat App",
"description": "A Rocket.Chat App declaration for usage inside of Rocket.Chat.",
"type": "object",
"properties": {
"id": {
"description": "The Rocketlet's unique identifier in uuid v4 format. This is optional, although recommended, however if you are going to publish on the Rocketlet store, you will be assigned one.",
"description": "The App's unique identifier in uuid v4 format. This is optional, although recommended, however if you are going to publish on the App store, you will be assigned one.",
"type": "string",
"pattern": "^[0-9a-fA-f]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}$",
"minLength": 36,
"maxLength": 36
},
"name": {
"description": "The public visible name of this Rocketlet.",
"description": "The public visible name of this App.",
"type": "string"
},
"nameSlug": {
"description": "A url friendly slugged version of your Rocketlet's name.",
"description": "A url friendly slugged version of your App's name.",
"type": "string",
"pattern": "^([a-z]|\\-)+$",
"minLength": 3
},
"version": {
"description": "The version of this Rocketlet which will be used for display publicly and letting users know there is an update. This uses the semver format.",
"description": "The version of this App which will be used for display publicly and letting users know there is an update. This uses the semver format.",
"type": "string",
"pattern": "\\bv?(?:0|[1-9]\\d*)\\.(?:0|[1-9]\\d*)\\.(?:0|[1-9]\\d*)(?:-[\\da-z\\-]+(?:\\.[\\da-z\\-]+)*)?(?:\\+[\\da-z\\-]+(?:\\.[\\da-z\\-]+)*)?\\b",
"minLength": 5
},
"description": {
"description": "A description of this Rocketlet, used to explain what this Rocketlet does and provides for the user.",
"description": "A description of this App, used to explain what this App does and provides for the user.",
"type": "string"
},
"requiredApiVersion": {
"description": "The required version of the Rocketlet's API which this Rocketlet depends on. This uses the semver format.",
"description": "The required version of the App's API which this App depends on. This uses the semver format.",
"type": "string",
"pattern": "\\bv?(?:0|[1-9]\\d*)\\.(?:0|[1-9]\\d*)\\.(?:0|[1-9]\\d*)(?:-[\\da-z\\-]+(?:\\.[\\da-z\\-]+)*)?(?:\\+[\\da-z\\-]+(?:\\.[\\da-z\\-]+)*)?\\b",
"minLength": 5
......@@ -41,15 +41,15 @@
"type": "object",
"properties": {
"name": {
"description": "The author's name who created this Rocketlet.",
"description": "The author's name who created this App.",
"type": "string"
},
"support": {
"description": "The place where people can get support for this Rocketlet, whether email or website.",
"description": "The place where people can get support for this App, whether email or website.",
"type": "string"
},
"homepage": {
"description": "The homepage for this Rocketlet, it can be a Github or the author's website.",
"description": "The homepage for this App, it can be a Github or the author's website.",
"type": "string",
"format": "uri"
}
......@@ -58,7 +58,7 @@
},
"classFile": {
"type": "string",
"description": "The name of the file which contains your Rocketlet TypeScript source code.",
"description": "The name of the file which contains your App TypeScript source code.",
"pattern": "^.*\\.(ts)$"
},
"iconFile": {
......
# Apps Folder
Create a new folder per App, the name of the folder won't really matter since the zip file produced will be named `name_id_version`.
......@@ -10,11 +10,11 @@ import {
IEnvironmentRead,
IHttp,
IRead,
} from 'temporary-rocketlets-ts-definition/accessors';
import { Rocketlet } from 'temporary-rocketlets-ts-definition/Rocketlet';
import { ISetting, SettingType } from 'temporary-rocketlets-ts-definition/settings';
} from '@rocket.chat/apps-ts-definition/accessors';
import { App } from '@rocket.chat/apps-ts-definition/App';
import { ISetting, SettingType } from '@rocket.chat/apps-ts-definition/settings';
export class AsciiArtCommandsRocketlet extends Rocketlet {