mirror of
https://github.com/turt2live/matrix-dimension.git
synced 2024-10-01 01:05:53 -04:00
parent
22304d716c
commit
16e28019bc
27
README.md
27
README.md
@ -1,7 +1,30 @@
|
||||
# matrix-dimension
|
||||
# Dimension
|
||||
|
||||
[![Greenkeeper badge](https://badges.greenkeeper.io/turt2live/matrix-dimension.svg)](https://greenkeeper.io/) [![TravisCI badge](https://travis-ci.org/turt2live/matrix-dimension.svg?branch=master)](https://travis-ci.org/turt2live/matrix-dimension)
|
||||
|
||||
An alternative to Scalar for Riot.
|
||||
An alternative integrations manager for [Riot](https://riot.im).
|
||||
|
||||
Join us on matrix: [#dimension:t2l.io](https://matrix.to/#/#dimension:t2l.io)
|
||||
|
||||
# Configuring Riot to use Dimension
|
||||
|
||||
Change the values in Riot's `config.json` as shown below. If you do not have a `config.json`, copy the `config.sample.json` from Riot.
|
||||
|
||||
```
|
||||
"integrations_ui_url": "https://dimension.t2bot.io/riot",
|
||||
"integrations_rest_url": "https://dimension.t2bot.io/api/v1/scalar",
|
||||
```
|
||||
|
||||
The remaining settings should be tailored for your Riot deployment.
|
||||
|
||||
# Running your own
|
||||
|
||||
TODO
|
||||
|
||||
# Building
|
||||
|
||||
TODO
|
||||
|
||||
# Development
|
||||
|
||||
TODO
|
@ -5,26 +5,31 @@ bots:
|
||||
name: "Giphy"
|
||||
avatar: "/img/avatars/giphy.png"
|
||||
about: "Use `!giphy query` to find an animated GIF on demand"
|
||||
upstreamType: "giphy"
|
||||
# Guggy (from matrix.org)
|
||||
- mxid: "@neb_guggy:matrix.org"
|
||||
- mxid: "@_neb_guggy:matrix.org"
|
||||
name: "Guggy"
|
||||
avatar: "/img/avatars/guggy.png"
|
||||
about: "Use `!guggy sentence` to create an animated GIF from a sentence"
|
||||
upstreamType: "guggy"
|
||||
# Imgur (from matrix.org)
|
||||
- mxid: "@_neb_imgur:matrix.org"
|
||||
name: "Imgur"
|
||||
avatar: "/img/avatars/imgur.png"
|
||||
about: "Use `!imgur query` to find an image from Imgur"
|
||||
upstreamType: "imgur"
|
||||
# Wikipedia (from matrix.org)
|
||||
- mxid: "@_neb_wikipedia:matrix.org"
|
||||
name: "Wikipedia"
|
||||
avatar: "/img/avatars/wikipedia.png"
|
||||
about: "Use `!wikipedia query` to find something from Wikipedia"
|
||||
upstreamType: "wikipedia"
|
||||
# Google (from matrix.org)
|
||||
- mxid: "@_neb_google:matrix.org"
|
||||
name: "Google"
|
||||
avatar: "/img/avatars/google.png"
|
||||
about: "Use `!google image query` to find an image from Google"
|
||||
upstreamType: "google"
|
||||
|
||||
# The web settings for the service (API and UI)
|
||||
web:
|
||||
@ -34,6 +39,10 @@ web:
|
||||
# The address to bind to (0.0.0.0 for all interfaces)
|
||||
address: '0.0.0.0'
|
||||
|
||||
# Upstream scalar configuration. This should almost never change.
|
||||
scalar:
|
||||
upstreamRestUrl: "https://scalar.vector.im/api"
|
||||
|
||||
# Settings for controlling how logging works
|
||||
logging:
|
||||
file: logs/dimension.log
|
||||
|
@ -53,8 +53,10 @@ then we can expect a response object that looks like this:
|
||||
An error response will always have the following structure under `response`:
|
||||
```
|
||||
{
|
||||
"message": "Something went wrong",
|
||||
"_error": <original Error object>
|
||||
"error": {
|
||||
"message": "Something went wrong",
|
||||
"_error": <original Error object>
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
|
@ -0,0 +1,27 @@
|
||||
'use strict';
|
||||
|
||||
var dbm;
|
||||
var type;
|
||||
var seed;
|
||||
|
||||
/**
|
||||
* We receive the dbmigrate dependency from dbmigrate initially.
|
||||
* This enables us to not have to rely on NODE_PATH.
|
||||
*/
|
||||
exports.setup = function (options, seedLink) {
|
||||
dbm = options.dbmigrate;
|
||||
type = dbm.dataType;
|
||||
seed = seedLink;
|
||||
};
|
||||
|
||||
exports.up = function (db) {
|
||||
return db.addColumn('tokens', 'upstreamToken', {type: 'string', notNull: false}); // has to be nullable, despite our best intentions
|
||||
};
|
||||
|
||||
exports.down = function (db) {
|
||||
return db.removeColumn('tokens', 'upstreamToken');
|
||||
};
|
||||
|
||||
exports._meta = {
|
||||
"version": 1
|
||||
};
|
@ -42,6 +42,7 @@
|
||||
"@ng-bootstrap/ng-bootstrap": "^1.0.0-alpha.22",
|
||||
"@types/node": "^7.0.18",
|
||||
"angular2-template-loader": "^0.6.2",
|
||||
"angular2-toaster": "^4.0.0",
|
||||
"angular2-ui-switch": "^1.2.0",
|
||||
"autoprefixer": "^6.7.7",
|
||||
"awesome-typescript-loader": "^3.1.2",
|
||||
|
@ -6,12 +6,17 @@ var bodyParser = require('body-parser');
|
||||
var path = require("path");
|
||||
var MatrixLiteClient = require("./matrix/MatrixLiteClient");
|
||||
var randomString = require("random-string");
|
||||
var ScalarClient = require("./scalar/ScalarClient.js");
|
||||
var VectorScalarClient = require("./scalar/VectorScalarClient");
|
||||
|
||||
/**
|
||||
* Primary entry point for Dimension
|
||||
*/
|
||||
class Dimension {
|
||||
|
||||
// TODO: Spread the app out into other classes
|
||||
// eg: ScalarApi, DimensionApi, etc
|
||||
|
||||
/**
|
||||
* Creates a new Dimension
|
||||
* @param {DimensionStore} db the storage
|
||||
@ -41,8 +46,10 @@ class Dimension {
|
||||
});
|
||||
|
||||
this._app.post("/api/v1/scalar/register", this._scalarRegister.bind(this));
|
||||
this._app.get("/api/v1/dimension/bots", this._getBots.bind(this));
|
||||
this._app.get("/api/v1/scalar/checkToken", this._checkScalarToken.bind(this));
|
||||
|
||||
this._app.get("/api/v1/dimension/bots", this._getBots.bind(this));
|
||||
this._app.post("/api/v1/dimension/kick", this._kickUser.bind(this));
|
||||
}
|
||||
|
||||
start() {
|
||||
@ -50,6 +57,35 @@ class Dimension {
|
||||
log.info("Dimension", "API and UI listening on " + config.get("web.address") + ":" + config.get("web.port"));
|
||||
}
|
||||
|
||||
_kickUser(req, res) {
|
||||
// {roomId: roomId, userId: userId, scalarToken: scalarToken}
|
||||
var roomId = req.body.roomId;
|
||||
var userId = req.body.userId;
|
||||
var scalarToken = req.body.scalarToken;
|
||||
|
||||
if (!roomId || !userId || !scalarToken) {
|
||||
res.status(400).send({error: "Missing room, user, or token"});
|
||||
return;
|
||||
}
|
||||
|
||||
var integrationName = null;
|
||||
this._db.checkToken(scalarToken).then(() => {
|
||||
for (var bot of config.bots) {
|
||||
if (bot.mxid == userId) {
|
||||
integrationName = bot.upstreamType;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return this._db.getUpstreamToken(scalarToken);
|
||||
}).then(upstreamToken => {
|
||||
if (!upstreamToken || !integrationName) {
|
||||
res.status(400).send({error: "Missing token or integration name"});
|
||||
return Promise.resolve();
|
||||
} else return VectorScalarClient.removeIntegration(integrationName, roomId, upstreamToken);
|
||||
}).then(() => res.status(200).send({success: true})).catch(err => res.status(500).send({error: err.message}));
|
||||
}
|
||||
|
||||
_getBots(req, res) {
|
||||
res.setHeader("Content-Type", "application/json");
|
||||
res.send(JSON.stringify(config.bots));
|
||||
@ -57,29 +93,36 @@ class Dimension {
|
||||
|
||||
_checkScalarToken(req, res) {
|
||||
var token = req.query.scalar_token;
|
||||
if (!token) res.sendStatus(404);
|
||||
if (!token) res.sendStatus(400);
|
||||
else this._db.checkToken(token).then(() => {
|
||||
res.sendStatus(200);
|
||||
}, () => res.sendStatus(404));
|
||||
}).catch(() => res.sendStatus(401));
|
||||
}
|
||||
|
||||
_scalarRegister(req, res) {
|
||||
res.setHeader("Content-Type", "application/json");
|
||||
|
||||
var tokenInfo = req.body;
|
||||
if (!tokenInfo || !tokenInfo['access_token'] || !tokenInfo['token_type'] || !tokenInfo['matrix_server_name'] || !tokenInfo['expires_in']) {
|
||||
res.status(400).send('Missing OpenID');
|
||||
res.status(400).send({error: 'Missing OpenID'});
|
||||
return;
|
||||
}
|
||||
|
||||
var client = new MatrixLiteClient(tokenInfo);
|
||||
var scalarToken = randomString({length: 25});
|
||||
var userId;
|
||||
client.getSelfMxid().then(mxid => {
|
||||
return this._db.createToken(mxid, tokenInfo, scalarToken);
|
||||
userId = mxid;
|
||||
if (!mxid) throw new Error("Token does not resolve to a matrix user");
|
||||
return ScalarClient.register(tokenInfo);
|
||||
}).then(upstreamToken => {
|
||||
return this._db.createToken(userId, tokenInfo, scalarToken, upstreamToken);
|
||||
}).then(() => {
|
||||
res.setHeader("Content-Type", "application/json");
|
||||
res.send(JSON.stringify({scalar_token: scalarToken}));
|
||||
}, err => {
|
||||
throw err;
|
||||
//res.status(500).send(err.message);
|
||||
res.send({scalar_token: scalarToken});
|
||||
}).catch(err => {
|
||||
log.error("Dimension", err);
|
||||
res.status(500).send({error: err.message});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -43,6 +43,7 @@ class MatrixLiteClient {
|
||||
return new Promise((resolve, reject) => {
|
||||
request(params, (err, response, body) => {
|
||||
if (err) {
|
||||
log.error("MatrixLiteClient", err);
|
||||
reject(err);
|
||||
} else resolve(response, body);
|
||||
});
|
||||
|
55
src/scalar/ScalarClient.js
Normal file
55
src/scalar/ScalarClient.js
Normal file
@ -0,0 +1,55 @@
|
||||
var request = require('request');
|
||||
var log = require("../util/LogService");
|
||||
var config = require("config");
|
||||
|
||||
/**
|
||||
* Represents a scalar client
|
||||
*/
|
||||
class ScalarClient {
|
||||
|
||||
/**
|
||||
* Creates a new Scalar client
|
||||
*/
|
||||
constructor() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers for a scalar token
|
||||
* @param {OpenID} openId the open ID to register
|
||||
* @returns {Promise<string>} resolves to a scalar token
|
||||
*/
|
||||
register(openId) {
|
||||
return this._do("POST", "/register", null, openId).then((response, body) => {
|
||||
if (response.statusCode !== 200) {
|
||||
log.error("ScalarClient", response.body);
|
||||
return Promise.reject(response.body);
|
||||
}
|
||||
|
||||
return response.body['scalar_token'];
|
||||
});
|
||||
}
|
||||
|
||||
_do(method, endpoint, qs = null, body = null) {
|
||||
var url = config.scalar.upstreamRestUrl + endpoint;
|
||||
|
||||
log.verbose("ScalarClient", "Performing request: " + url);
|
||||
|
||||
var params = {
|
||||
url: url,
|
||||
method: method,
|
||||
json: body,
|
||||
qs: qs
|
||||
};
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
request(params, (err, response, body) => {
|
||||
if (err) {
|
||||
log.error("ScalarClient", err);
|
||||
reject(err);
|
||||
} else resolve(response, body);
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = new ScalarClient();
|
69
src/scalar/VectorScalarClient.js
Normal file
69
src/scalar/VectorScalarClient.js
Normal file
@ -0,0 +1,69 @@
|
||||
var request = require('request');
|
||||
var log = require("../util/LogService");
|
||||
var config = require("config");
|
||||
|
||||
/**
|
||||
* Represents a scalar client for vector.im
|
||||
*/
|
||||
class VectorScalarClient {
|
||||
|
||||
/**
|
||||
* Creates a new vector.im Scalar client
|
||||
*/
|
||||
constructor() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers for a scalar token
|
||||
* @param {OpenID} openId the open ID to register
|
||||
* @returns {Promise<string>} resolves to a scalar token
|
||||
*/
|
||||
register(openId) {
|
||||
return this._do("POST", "/register", null, openId).then((response, body) => {
|
||||
var json = JSON.parse(response.body);
|
||||
return json['scalar_token'];
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes a scalar integration
|
||||
* @param {string} type the type of integration to remove
|
||||
* @param {string} roomId the room ID to remove it from
|
||||
* @param {string} scalarToken the upstream scalar token
|
||||
* @return {Promise<>} resolves when complete
|
||||
*/
|
||||
removeIntegration(type, roomId, scalarToken) {
|
||||
return this._do("POST", "/removeIntegration", {scalar_token: scalarToken}, {type: type, room_id: roomId}).then((response, body) => {
|
||||
if (response.statusCode !== 200) {
|
||||
log.error("VectorScalarClient", response.body);
|
||||
return Promise.reject(response.body);
|
||||
}
|
||||
|
||||
// no success processing
|
||||
});
|
||||
}
|
||||
|
||||
_do(method, endpoint, qs = null, body = null) {
|
||||
var url = config.scalar.upstreamRestUrl + endpoint;
|
||||
|
||||
log.verbose("VectorScalarClient", "Performing request: " + url);
|
||||
|
||||
var params = {
|
||||
url: url,
|
||||
method: method,
|
||||
json: body,
|
||||
qs: qs
|
||||
};
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
request(params, (err, response, body) => {
|
||||
if (err) {
|
||||
log.error("VectorScalarClient", err);
|
||||
reject(err);
|
||||
} else resolve(response, body);
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = new VectorScalarClient();
|
@ -65,14 +65,16 @@ class DimensionStore {
|
||||
* @param {string} mxid the matrix user id
|
||||
* @param {OpenID} openId the open ID
|
||||
* @param {string} scalarToken the token associated with the user
|
||||
* @param {string} upstreamToken the upstream scalar token
|
||||
* @returns {Promise<>} resolves when complete
|
||||
*/
|
||||
createToken(mxid, openId, scalarToken) {
|
||||
createToken(mxid, openId, scalarToken, upstreamToken) {
|
||||
return this.__Tokens.create({
|
||||
matrixUserId: mxid,
|
||||
matrixServerName: openId.matrix_server_name,
|
||||
matrixAccessToken: openId.access_token,
|
||||
scalarToken: scalarToken,
|
||||
upstreamToken: upstreamToken,
|
||||
expires: moment().add(openId.expires_in, 'seconds').toDate()
|
||||
});
|
||||
}
|
||||
@ -89,6 +91,18 @@ class DimensionStore {
|
||||
return Promise.resolve();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the upstream token for a given scalar token
|
||||
* @param {string} scalarToken the scalar token to lookup
|
||||
* @returns {Promise<string>} resolves to the upstream token, or null if not found
|
||||
*/
|
||||
getUpstreamToken(scalarToken) {
|
||||
return this.__Tokens.find({where: {scalarToken: scalarToken}}).then(token => {
|
||||
if (!token) return null;
|
||||
return token.upstreamToken;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = DimensionStore;
|
@ -27,6 +27,11 @@ module.exports = function (sequelize, DataTypes) {
|
||||
allowNull: false,
|
||||
field: 'scalarToken'
|
||||
},
|
||||
upstreamToken: {
|
||||
type: DataTypes.STRING,
|
||||
allowNull: false,
|
||||
field: 'upstreamToken'
|
||||
},
|
||||
expires: {
|
||||
type: DataTypes.TIME,
|
||||
allowNull: false,
|
||||
|
@ -1,6 +1,7 @@
|
||||
<header>
|
||||
</header>
|
||||
<main>
|
||||
<toaster-container></toaster-container>
|
||||
<router-outlet></router-outlet>
|
||||
</main>
|
||||
<footer>
|
||||
|
@ -11,6 +11,9 @@ import { RiotComponent } from "./riot/riot.component";
|
||||
import { ApiService } from "./shared/api.service";
|
||||
import { BotComponent } from "./bot/bot.component";
|
||||
import { UiSwitchModule } from "angular2-ui-switch";
|
||||
import { ScalarService } from "./shared/scalar.service";
|
||||
import { ToasterModule } from "angular2-toaster";
|
||||
import { BrowserAnimationsModule } from "@angular/platform-browser/animations";
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
@ -20,6 +23,8 @@ import { UiSwitchModule } from "angular2-ui-switch";
|
||||
routing,
|
||||
NgbModule.forRoot(),
|
||||
UiSwitchModule,
|
||||
ToasterModule,
|
||||
BrowserAnimationsModule,
|
||||
],
|
||||
declarations: [
|
||||
AppComponent,
|
||||
@ -31,6 +36,7 @@ import { UiSwitchModule } from "angular2-ui-switch";
|
||||
],
|
||||
providers: [
|
||||
ApiService,
|
||||
ScalarService,
|
||||
|
||||
// Vendor
|
||||
],
|
||||
|
@ -4,7 +4,7 @@
|
||||
<b>{{ bot.name }}</b>
|
||||
<div style="display: flex;">
|
||||
<div class="switch">
|
||||
<ui-switch [checked]="bot.isEnabled" size="small" [disabled]="updating" (change)="update()"></ui-switch>
|
||||
<ui-switch [checked]="bot.isEnabled" size="small" [disabled]="bot.isBroken" (change)="update()"></ui-switch>
|
||||
</div>
|
||||
<div class="toolbar">
|
||||
<i class="fa fa-question-circle text-info" ngbTooltip="{{bot.about}}" *ngIf="bot.about"></i>
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { Component, Input } from "@angular/core";
|
||||
import { Component, Input, Output, EventEmitter } from "@angular/core";
|
||||
import { Bot } from "../shared/models/bot";
|
||||
|
||||
@Component({
|
||||
@ -9,15 +9,13 @@ import { Bot } from "../shared/models/bot";
|
||||
export class BotComponent {
|
||||
|
||||
@Input() bot: Bot;
|
||||
|
||||
public updating = false;
|
||||
public htmlAbout: string;
|
||||
@Output() updated: EventEmitter<any> = new EventEmitter();
|
||||
|
||||
constructor() {
|
||||
}
|
||||
|
||||
public update(): void {
|
||||
this.updating = true;
|
||||
setTimeout(() => this.updating = false, Math.random() * 15000);
|
||||
this.bot.isEnabled = !this.bot.isEnabled;
|
||||
this.updated.emit();
|
||||
}
|
||||
}
|
||||
|
@ -1,10 +1,13 @@
|
||||
<div *ngIf="error">
|
||||
<p class="text-danger">{{ error }}</p>
|
||||
</div>
|
||||
<div *ngIf="!error">
|
||||
<div *ngIf="loading && !error">
|
||||
<p><i class="fa fa-circle-o-notch fa-spin"></i> Loading...</p>
|
||||
</div>
|
||||
<div *ngIf="!error && !loading">
|
||||
<h3>Manage Integrations</h3>
|
||||
<p>Turn on anything you like. If an integration has some special configuration, it will have a configuration icon next to it.</p>
|
||||
<div class="integration-container">
|
||||
<my-bot *ngFor="let bot of bots" [bot]="bot"></my-bot>
|
||||
<my-bot *ngFor="let bot of bots" [bot]="bot" (updated)="updateBot(bot)"></my-bot>
|
||||
</div>
|
||||
</div>
|
@ -2,6 +2,8 @@ import { Component } from "@angular/core";
|
||||
import { ActivatedRoute } from "@angular/router";
|
||||
import { ApiService } from "../shared/api.service";
|
||||
import { Bot } from "../shared/models/bot";
|
||||
import { ScalarService } from "../shared/scalar.service";
|
||||
import { ToasterService } from "angular2-toaster";
|
||||
|
||||
@Component({
|
||||
selector: 'my-riot',
|
||||
@ -12,21 +14,71 @@ export class RiotComponent {
|
||||
|
||||
public error: string;
|
||||
public bots: Bot[] = [];
|
||||
public loading = true;
|
||||
public roomId: string;
|
||||
|
||||
constructor(private activatedRoute: ActivatedRoute, private api: ApiService) {
|
||||
private scalarToken: string;
|
||||
|
||||
constructor(private activatedRoute: ActivatedRoute, private api: ApiService, private scalar: ScalarService, private toaster:ToasterService) {
|
||||
let params: any = this.activatedRoute.snapshot.queryParams;
|
||||
if (!params.scalar_token || !params.room_id) this.error = "Missing scalar token or room ID";
|
||||
else this.api.checkScalarToken(params.scalar_token).then(isValid => {
|
||||
if (isValid) this.init();
|
||||
else this.error = "Invalid scalar token";
|
||||
});
|
||||
else {
|
||||
this.roomId = params.room_id;
|
||||
this.scalarToken = params.scalar_token;
|
||||
|
||||
this.api.checkScalarToken(params.scalar_token).then(isValid => {
|
||||
if (isValid) this.init();
|
||||
else this.error = "Invalid scalar token";
|
||||
}).catch(err => {
|
||||
this.error = "Unable to communicate with Dimension";
|
||||
console.error(err);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private init() {
|
||||
this.api.getBots().then(bots => {
|
||||
this.bots = bots;
|
||||
bots.map(b => b.isEnabled = Math.random() > 0.75);
|
||||
let promises = bots.map(b => this.updateBotState(b));
|
||||
return Promise.all(promises);
|
||||
}).then(() => this.loading = false);
|
||||
}
|
||||
|
||||
private updateBotState(bot: Bot) {
|
||||
return this.scalar.getMembershipState(this.roomId, bot.mxid).then(payload => {
|
||||
bot.isBroken = false;
|
||||
|
||||
if (!payload.response) {
|
||||
bot.isEnabled = false;
|
||||
return;
|
||||
}
|
||||
|
||||
bot.isEnabled = (payload.response.membership === 'join' || payload.response.membership === 'invite');
|
||||
}, (error) => {
|
||||
console.error(error);
|
||||
bot.isEnabled = false;
|
||||
bot.isBroken = true;
|
||||
});
|
||||
}
|
||||
|
||||
public updateBot(bot: Bot) {
|
||||
let promise = null;
|
||||
|
||||
if (!bot.isEnabled) {
|
||||
promise = this.api.kickUser(this.roomId, bot.mxid, this.scalarToken);
|
||||
} else promise = this.scalar.inviteUser(this.roomId, bot.mxid);
|
||||
|
||||
promise
|
||||
.then(() => this.toaster.pop("success", bot.name + " invited to the room"))
|
||||
.catch(err => {
|
||||
var errorMessage = "Could not update bot status";
|
||||
|
||||
if (err.json) {
|
||||
errorMessage = err.json().error;
|
||||
} else errorMessage = err.response.error.message;
|
||||
|
||||
bot.isEnabled = !bot.isEnabled;
|
||||
this.toaster.pop("error", errorMessage);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -16,4 +16,9 @@ export class ApiService {
|
||||
return this.http.get("/api/v1/dimension/bots")
|
||||
.map(res => res.json()).toPromise();
|
||||
}
|
||||
|
||||
kickUser(roomId: string, userId: string, scalarToken: string): Promise<any> {
|
||||
return this.http.post("/api/v1/dimension/kick", {roomId: roomId, userId: userId, scalarToken: scalarToken})
|
||||
.map(res => res.json()).toPromise();
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +0,0 @@
|
||||
// Services
|
||||
export * from './api.service';
|
||||
|
||||
// Models
|
||||
export * from './models/bot';
|
@ -1,7 +1,8 @@
|
||||
export class Bot {
|
||||
export interface Bot {
|
||||
mxid: string;
|
||||
name: string;
|
||||
avatar: string;
|
||||
about: string; // nullable
|
||||
isEnabled: boolean;
|
||||
isBroken: boolean;
|
||||
}
|
||||
|
27
web/app/shared/models/scalar_responses.ts
Normal file
27
web/app/shared/models/scalar_responses.ts
Normal file
@ -0,0 +1,27 @@
|
||||
export interface ScalarResponse {
|
||||
action: string;
|
||||
}
|
||||
|
||||
export interface ScalarRoomResponse extends ScalarResponse {
|
||||
room_id: string;
|
||||
}
|
||||
|
||||
export interface ScalarUserResponse extends ScalarRoomResponse {
|
||||
user_id: string;
|
||||
}
|
||||
|
||||
export interface ScalarErrorResponse extends ScalarResponse {
|
||||
response: {error: {message: string, _error: Error}};
|
||||
}
|
||||
|
||||
export interface ScalarSuccessResponse extends ScalarResponse {
|
||||
response: {success: boolean};
|
||||
}
|
||||
|
||||
export interface MembershipStateResponse extends ScalarUserResponse {
|
||||
response: {
|
||||
membership: string;
|
||||
avatar_url: string;
|
||||
displayname: string;
|
||||
};
|
||||
}
|
68
web/app/shared/scalar.service.ts
Normal file
68
web/app/shared/scalar.service.ts
Normal file
@ -0,0 +1,68 @@
|
||||
import { Injectable } from "@angular/core";
|
||||
import * as randomString from "random-string";
|
||||
import { MembershipStateResponse, ScalarSuccessResponse } from "./models/scalar_responses";
|
||||
|
||||
@Injectable()
|
||||
export class ScalarService {
|
||||
|
||||
private static actionMap: {[key: string]: {resolve: (obj: any) => void, reject: (obj: any) => void}} = {};
|
||||
|
||||
public static getAndRemoveActionHandler(requestKey: string): {resolve: (obj: any) => void, reject: (obj: any) => void} {
|
||||
let handler = ScalarService.actionMap[requestKey];
|
||||
ScalarService.actionMap[requestKey] = null;
|
||||
return handler;
|
||||
}
|
||||
|
||||
constructor() {
|
||||
}
|
||||
|
||||
public inviteUser(roomId: string, userId): Promise<ScalarSuccessResponse> {
|
||||
return this.callAction("invite", {
|
||||
room_id: roomId,
|
||||
user_id: userId
|
||||
});
|
||||
}
|
||||
|
||||
public getMembershipState(roomId: string, userId: string): Promise<MembershipStateResponse> {
|
||||
return this.callAction("membership_state", {
|
||||
room_id: roomId,
|
||||
user_id: userId
|
||||
});
|
||||
}
|
||||
|
||||
private callAction(action, payload) {
|
||||
let requestKey = randomString({length: 20});
|
||||
return new Promise((resolve, reject) => {
|
||||
if (!window.opener) {
|
||||
// Mimic an error response from scalar
|
||||
reject({response: {error: {message: "No window.opener", _error: new Error("No window.opener")}}});
|
||||
return;
|
||||
}
|
||||
|
||||
ScalarService.actionMap[requestKey] = {
|
||||
resolve: resolve,
|
||||
reject: reject
|
||||
};
|
||||
|
||||
let request = JSON.parse(JSON.stringify(payload));
|
||||
request["request_id"] = requestKey;
|
||||
request["action"] = action;
|
||||
|
||||
window.opener.postMessage(request, "*");
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Register the event listener here to ensure it gets created
|
||||
window.addEventListener("message", event => {
|
||||
if (!event.data) return;
|
||||
|
||||
let requestKey = event.data["request_id"];
|
||||
if (!requestKey) return;
|
||||
|
||||
let action = ScalarService.getAndRemoveActionHandler(requestKey);
|
||||
if (!action) return;
|
||||
|
||||
if (event.data.response && event.data.response.error) action.reject(event.data);
|
||||
else action.resolve(event.data);
|
||||
});
|
@ -19,13 +19,16 @@ if (document.readyState === 'complete') {
|
||||
document.addEventListener('DOMContentLoaded', main);
|
||||
}
|
||||
|
||||
(<any>String.prototype).hashCode = function() {
|
||||
(<any>String.prototype).hashCode = function () {
|
||||
let hash = 0, i, chr;
|
||||
if (this.length === 0) return hash;
|
||||
for (i = 0; i < this.length; i++) {
|
||||
chr = this.charCodeAt(i);
|
||||
hash = ((hash << 5) - hash) + chr;
|
||||
chr = this.charCodeAt(i);
|
||||
hash = ((hash << 5) - hash) + chr;
|
||||
hash |= 0; // Convert to 32bit integer
|
||||
}
|
||||
return hash;
|
||||
};
|
||||
|
||||
// HACK: Work around .opener not being available
|
||||
if (!window.opener && window.parent) window.opener = window.parent;
|
||||
|
@ -1,4 +1,6 @@
|
||||
// styles in src/style directory are applied to the whole page
|
||||
@import '../../node_modules/angular2-toaster/toaster';
|
||||
|
||||
body {
|
||||
background: #ddd !important;
|
||||
margin: 0;
|
||||
|
Loading…
Reference in New Issue
Block a user