diff --git a/src/web/app/common/mios.ts b/src/web/app/common/mios.ts index 3690d3171..aa07cb022 100644 --- a/src/web/app/common/mios.ts +++ b/src/web/app/common/mios.ts @@ -1,9 +1,11 @@ import Vue from 'vue'; import { EventEmitter } from 'eventemitter3'; import * as merge from 'object-assign-deep'; +import * as uuid from 'uuid'; import { host, apiUrl, swPublickey, version, lang, googleMapsApiKey } from '../config'; import Progress from './scripts/loading'; +import Connection from './scripts/streaming/stream'; import { HomeStreamManager } from './scripts/streaming/home'; import { DriveStreamManager } from './scripts/streaming/drive'; import { ServerStreamManager } from './scripts/streaming/server'; @@ -151,9 +153,6 @@ export default class MiOS extends EventEmitter { this.shouldRegisterSw = shouldRegisterSw; - this.streams.serverStream = new ServerStreamManager(); - this.streams.requestsStream = new RequestsStreamManager(); - //#region BIND this.log = this.log.bind(this); this.logInfo = this.logInfo.bind(this); @@ -165,16 +164,6 @@ export default class MiOS extends EventEmitter { this.registerSw = this.registerSw.bind(this); //#endregion - this.once('signedin', () => { - // Init home stream manager - this.stream = new HomeStreamManager(this, this.i); - - // Init other stream manager - this.streams.driveStream = new DriveStreamManager(this.i); - this.streams.messagingIndexStream = new MessagingIndexStreamManager(this.i); - this.streams.othelloStream = new OthelloStreamManager(this.i); - }); - if (this.debug) { (window as any).os = this; } @@ -240,6 +229,21 @@ export default class MiOS extends EventEmitter { * @param callback A function that call when initialized */ public async init(callback) { + //#region Init stream managers + this.streams.serverStream = new ServerStreamManager(this); + this.streams.requestsStream = new RequestsStreamManager(this); + + this.once('signedin', () => { + // Init home stream manager + this.stream = new HomeStreamManager(this, this.i); + + // Init other stream manager + this.streams.driveStream = new DriveStreamManager(this, this.i); + this.streams.messagingIndexStream = new MessagingIndexStreamManager(this, this.i); + this.streams.othelloStream = new OthelloStreamManager(this, this.i); + }); + //#endregion + // ユーザーをフェッチしてコールバックする const fetchme = (token, cb) => { let me = null; @@ -414,6 +418,8 @@ export default class MiOS extends EventEmitter { }); } + public requests = []; + /** * Misskey APIにリクエストします * @param endpoint エンドポイント名 @@ -446,22 +452,41 @@ export default class MiOS extends EventEmitter { data }); } else {*/ + const req = { + id: uuid(), + date: new Date(), + name: endpoint, + data, + res: null, + status: null + }; + + if (this.debug) { + this.requests.push(req); + } + // Send request fetch(endpoint.indexOf('://') > -1 ? endpoint : `${apiUrl}/${endpoint}`, { method: 'POST', body: JSON.stringify(data), credentials: endpoint === 'signin' ? 'include' : 'omit', cache: 'no-cache' - }).then(res => { + }).then(async (res) => { if (--pending === 0) spinner.parentNode.removeChild(spinner); + + const body = await res.json(); + + if (this.debug) { + req.status = res.status; + req.res = body; + } + if (res.status === 200) { - res.json().then(resolve); + resolve(body); } else if (res.status === 204) { resolve(); } else { - res.json().then(err => { - reject(err.error); - }, reject); + reject(body.error); } }).catch(reject); /*}*/ @@ -499,17 +524,29 @@ export default class MiOS extends EventEmitter { } }); } + + public connections: Connection[] = []; + + public registerStreamConnection(connection: Connection) { + this.connections.push(connection); + } + + public unregisterStreamConnection(connection: Connection) { + this.connections = this.connections.filter(c => c != connection); + } } -class WindowSystem { - private windows = new Set(); +class WindowSystem extends EventEmitter { + public windows = new Set(); public add(window) { this.windows.add(window); + this.emit('added', window); } public remove(window) { this.windows.delete(window); + this.emit('removed', window); } public getAll() { diff --git a/src/web/app/common/scripts/streaming/channel.ts b/src/web/app/common/scripts/streaming/channel.ts index 434b108b9..cab5f4edb 100644 --- a/src/web/app/common/scripts/streaming/channel.ts +++ b/src/web/app/common/scripts/streaming/channel.ts @@ -1,11 +1,12 @@ import Stream from './stream'; +import MiOS from '../../mios'; /** * Channel stream connection */ export default class Connection extends Stream { - constructor(channelId) { - super('channel', { + constructor(os: MiOS, channelId) { + super(os, 'channel', { channel: channelId }); } diff --git a/src/web/app/common/scripts/streaming/drive.ts b/src/web/app/common/scripts/streaming/drive.ts index 5805e5803..7ff85b594 100644 --- a/src/web/app/common/scripts/streaming/drive.ts +++ b/src/web/app/common/scripts/streaming/drive.ts @@ -1,12 +1,13 @@ import Stream from './stream'; import StreamManager from './stream-manager'; +import MiOS from '../../mios'; /** * Drive stream connection */ export class DriveStream extends Stream { - constructor(me) { - super('drive', { + constructor(os: MiOS, me) { + super(os, 'drive', { i: me.token }); } @@ -14,16 +15,18 @@ export class DriveStream extends Stream { export class DriveStreamManager extends StreamManager { private me; + private os: MiOS; - constructor(me) { + constructor(os: MiOS, me) { super(); this.me = me; + this.os = os; } public getConnection() { if (this.connection == null) { - this.connection = new DriveStream(this.me); + this.connection = new DriveStream(this.os, this.me); } return this.connection; diff --git a/src/web/app/common/scripts/streaming/home.ts b/src/web/app/common/scripts/streaming/home.ts index 1f110bfd3..533c23244 100644 --- a/src/web/app/common/scripts/streaming/home.ts +++ b/src/web/app/common/scripts/streaming/home.ts @@ -9,7 +9,7 @@ import MiOS from '../../mios'; */ export class HomeStream extends Stream { constructor(os: MiOS, me) { - super('', { + super(os, '', { i: me.token }); diff --git a/src/web/app/common/scripts/streaming/messaging-index.ts b/src/web/app/common/scripts/streaming/messaging-index.ts index 69758416d..84e2174ec 100644 --- a/src/web/app/common/scripts/streaming/messaging-index.ts +++ b/src/web/app/common/scripts/streaming/messaging-index.ts @@ -1,12 +1,13 @@ import Stream from './stream'; import StreamManager from './stream-manager'; +import MiOS from '../../mios'; /** * Messaging index stream connection */ export class MessagingIndexStream extends Stream { - constructor(me) { - super('messaging-index', { + constructor(os: MiOS, me) { + super(os, 'messaging-index', { i: me.token }); } @@ -14,16 +15,18 @@ export class MessagingIndexStream extends Stream { export class MessagingIndexStreamManager extends StreamManager { private me; + private os: MiOS; - constructor(me) { + constructor(os: MiOS, me) { super(); this.me = me; + this.os = os; } public getConnection() { if (this.connection == null) { - this.connection = new MessagingIndexStream(this.me); + this.connection = new MessagingIndexStream(this.os, this.me); } return this.connection; diff --git a/src/web/app/common/scripts/streaming/messaging.ts b/src/web/app/common/scripts/streaming/messaging.ts index 1fff2286b..c1b5875cf 100644 --- a/src/web/app/common/scripts/streaming/messaging.ts +++ b/src/web/app/common/scripts/streaming/messaging.ts @@ -1,11 +1,12 @@ import Stream from './stream'; +import MiOS from '../../mios'; /** * Messaging stream connection */ export class MessagingStream extends Stream { - constructor(me, otherparty) { - super('messaging', { + constructor(os: MiOS, me, otherparty) { + super(os, 'messaging', { i: me.token, otherparty }); diff --git a/src/web/app/common/scripts/streaming/othello-game.ts b/src/web/app/common/scripts/streaming/othello-game.ts index cdf46d5d8..b85af8f72 100644 --- a/src/web/app/common/scripts/streaming/othello-game.ts +++ b/src/web/app/common/scripts/streaming/othello-game.ts @@ -1,8 +1,9 @@ import Stream from './stream'; +import MiOS from '../../mios'; export class OthelloGameStream extends Stream { - constructor(me, game) { - super('othello-game', { + constructor(os: MiOS, me, game) { + super(os, 'othello-game', { i: me ? me.token : null, game: game.id }); diff --git a/src/web/app/common/scripts/streaming/othello.ts b/src/web/app/common/scripts/streaming/othello.ts index febc5d498..f5d47431c 100644 --- a/src/web/app/common/scripts/streaming/othello.ts +++ b/src/web/app/common/scripts/streaming/othello.ts @@ -1,9 +1,10 @@ import StreamManager from './stream-manager'; import Stream from './stream'; +import MiOS from '../../mios'; export class OthelloStream extends Stream { - constructor(me) { - super('othello', { + constructor(os: MiOS, me) { + super(os, 'othello', { i: me.token }); } @@ -11,16 +12,18 @@ export class OthelloStream extends Stream { export class OthelloStreamManager extends StreamManager { private me; + private os: MiOS; - constructor(me) { + constructor(os: MiOS, me) { super(); this.me = me; + this.os = os; } public getConnection() { if (this.connection == null) { - this.connection = new OthelloStream(this.me); + this.connection = new OthelloStream(this.os, this.me); } return this.connection; diff --git a/src/web/app/common/scripts/streaming/requests.ts b/src/web/app/common/scripts/streaming/requests.ts index 5d199a074..5bec30143 100644 --- a/src/web/app/common/scripts/streaming/requests.ts +++ b/src/web/app/common/scripts/streaming/requests.ts @@ -1,19 +1,28 @@ import Stream from './stream'; import StreamManager from './stream-manager'; +import MiOS from '../../mios'; /** * Requests stream connection */ export class RequestsStream extends Stream { - constructor() { - super('requests'); + constructor(os: MiOS) { + super(os, 'requests'); } } export class RequestsStreamManager extends StreamManager { + private os: MiOS; + + constructor(os: MiOS) { + super(); + + this.os = os; + } + public getConnection() { if (this.connection == null) { - this.connection = new RequestsStream(); + this.connection = new RequestsStream(this.os); } return this.connection; diff --git a/src/web/app/common/scripts/streaming/server.ts b/src/web/app/common/scripts/streaming/server.ts index b12198d2f..3d35ef4d9 100644 --- a/src/web/app/common/scripts/streaming/server.ts +++ b/src/web/app/common/scripts/streaming/server.ts @@ -1,19 +1,28 @@ import Stream from './stream'; import StreamManager from './stream-manager'; +import MiOS from '../../mios'; /** * Server stream connection */ export class ServerStream extends Stream { - constructor() { - super('server'); + constructor(os: MiOS) { + super(os, 'server'); } } export class ServerStreamManager extends StreamManager { + private os: MiOS; + + constructor(os: MiOS) { + super(); + + this.os = os; + } + public getConnection() { if (this.connection == null) { - this.connection = new ServerStream(); + this.connection = new ServerStream(this.os); } return this.connection; diff --git a/src/web/app/common/scripts/streaming/stream-manager.ts b/src/web/app/common/scripts/streaming/stream-manager.ts index a4a73c561..568b8b037 100644 --- a/src/web/app/common/scripts/streaming/stream-manager.ts +++ b/src/web/app/common/scripts/streaming/stream-manager.ts @@ -31,6 +31,8 @@ export default abstract class StreamManager extends EventE this._connection.on('_disconnected_', () => { this.emit('_disconnected_'); }); + + this._connection.user = 'Managed'; } } @@ -77,6 +79,8 @@ export default abstract class StreamManager extends EventE this.users.push(userId); + this._connection.user = `Managed (${ this.users.length })`; + return userId; } @@ -87,6 +91,8 @@ export default abstract class StreamManager extends EventE public dispose(userId) { this.users = this.users.filter(id => id != userId); + this._connection.user = `Managed (${ this.users.length })`; + // 誰もコネクションの利用者がいなくなったら if (this.users.length == 0) { // また直ぐに再利用される可能性があるので、一定時間待ち、 diff --git a/src/web/app/common/scripts/streaming/stream.ts b/src/web/app/common/scripts/streaming/stream.ts index 8799f6fe6..189af0ab3 100644 --- a/src/web/app/common/scripts/streaming/stream.ts +++ b/src/web/app/common/scripts/streaming/stream.ts @@ -1,6 +1,8 @@ import { EventEmitter } from 'eventemitter3'; +import * as uuid from 'uuid'; import * as ReconnectingWebsocket from 'reconnecting-websocket'; import { apiUrl } from '../../../config'; +import MiOS from '../../mios'; /** * Misskey stream connection @@ -8,9 +10,21 @@ import { apiUrl } from '../../../config'; export default class Connection extends EventEmitter { public state: string; private buffer: any[]; - private socket: ReconnectingWebsocket; + public socket: ReconnectingWebsocket; + public name: string; + public connectedAt: Date; + public user: string = null; + public in: number = 0; + public out: number = 0; + public inout: Array<{ + type: 'in' | 'out', + at: Date, + data: string + }> = []; + public id: string; + private os: MiOS; - constructor(endpoint, params?) { + constructor(os: MiOS, endpoint, params?) { super(); //#region BIND @@ -21,6 +35,9 @@ export default class Connection extends EventEmitter { this.close = this.close.bind(this); //#endregion + this.id = uuid(); + this.os = os; + this.name = endpoint; this.state = 'initializing'; this.buffer = []; @@ -35,6 +52,9 @@ export default class Connection extends EventEmitter { this.socket.addEventListener('open', this.onOpen); this.socket.addEventListener('close', this.onClose); this.socket.addEventListener('message', this.onMessage); + + // Register this connection for debugging + this.os.registerStreamConnection(this); } /** @@ -44,11 +64,18 @@ export default class Connection extends EventEmitter { this.state = 'connected'; this.emit('_connected_'); + this.connectedAt = new Date(); + // バッファーを処理 const _buffer = [].concat(this.buffer); // Shallow copy this.buffer = []; // Clear buffer - _buffer.forEach(message => { - this.send(message); // Resend each buffered messages + _buffer.forEach(data => { + this.send(data); // Resend each buffered messages + + if (this.os.debug) { + this.out++; + this.inout.push({ type: 'out', at: new Date(), data }); + } }); } @@ -64,6 +91,11 @@ export default class Connection extends EventEmitter { * Callback of when received a message from connection */ private onMessage(message) { + if (this.os.debug) { + this.in++; + this.inout.push({ type: 'in', at: new Date(), data: message.data }); + } + try { const msg = JSON.parse(message.data); if (msg.type) this.emit(msg.type, msg.body); @@ -75,20 +107,26 @@ export default class Connection extends EventEmitter { /** * Send a message to connection */ - public send(message) { + public send(data) { // まだ接続が確立されていなかったらバッファリングして次に接続した時に送信する if (this.state != 'connected') { - this.buffer.push(message); + this.buffer.push(data); return; } - this.socket.send(JSON.stringify(message)); + if (this.os.debug) { + this.out++; + this.inout.push({ type: 'out', at: new Date(), data }); + } + + this.socket.send(JSON.stringify(data)); } /** * Close this connection */ public close() { + this.os.unregisterStreamConnection(this); this.socket.removeEventListener('open', this.onOpen); this.socket.removeEventListener('message', this.onMessage); } diff --git a/src/web/app/common/views/components/index.ts b/src/web/app/common/views/components/index.ts index 98fc2352f..25f4e461d 100644 --- a/src/web/app/common/views/components/index.ts +++ b/src/web/app/common/views/components/index.ts @@ -10,6 +10,7 @@ import pollEditor from './poll-editor.vue'; import reactionIcon from './reaction-icon.vue'; import reactionsViewer from './reactions-viewer.vue'; import time from './time.vue'; +import timer from './timer.vue'; import images from './images.vue'; import uploader from './uploader.vue'; import specialMessage from './special-message.vue'; @@ -33,6 +34,7 @@ Vue.component('mk-poll-editor', pollEditor); Vue.component('mk-reaction-icon', reactionIcon); Vue.component('mk-reactions-viewer', reactionsViewer); Vue.component('mk-time', time); +Vue.component('mk-timer', timer); Vue.component('mk-images', images); Vue.component('mk-uploader', uploader); Vue.component('mk-special-message', specialMessage); diff --git a/src/web/app/common/views/components/messaging-room.vue b/src/web/app/common/views/components/messaging-room.vue index 547e9494e..6ff808b61 100644 --- a/src/web/app/common/views/components/messaging-room.vue +++ b/src/web/app/common/views/components/messaging-room.vue @@ -66,7 +66,7 @@ export default Vue.extend({ }, mounted() { - this.connection = new MessagingStream((this as any).os.i, this.user.id); + this.connection = new MessagingStream((this as any).os, (this as any).os.i, this.user.id); this.connection.on('message', this.onMessage); this.connection.on('read', this.onRead); diff --git a/src/web/app/common/views/components/othello.gameroom.vue b/src/web/app/common/views/components/othello.gameroom.vue index 9df458f64..38a25f668 100644 --- a/src/web/app/common/views/components/othello.gameroom.vue +++ b/src/web/app/common/views/components/othello.gameroom.vue @@ -25,7 +25,7 @@ export default Vue.extend({ }, created() { this.g = this.game; - this.connection = new OthelloGameStream((this as any).os.i, this.game); + this.connection = new OthelloGameStream((this as any).os, (this as any).os.i, this.game); this.connection.on('started', this.onStarted); }, beforeDestroy() { diff --git a/src/web/app/common/views/components/othello.room.vue b/src/web/app/common/views/components/othello.room.vue index 3b4296d0b..396541483 100644 --- a/src/web/app/common/views/components/othello.room.vue +++ b/src/web/app/common/views/components/othello.room.vue @@ -135,44 +135,6 @@ export default Vue.extend({ if (this.game.user1_id != (this as any).os.i.id && this.game.settings.form1) this.form = this.game.settings.form1; if (this.game.user2_id != (this as any).os.i.id && this.game.settings.form2) this.form = this.game.settings.form2; - - // for debugging - if ((this as any).os.i.username == 'test1') { - setTimeout(() => { - this.connection.send({ - type: 'init-form', - body: [{ - id: 'button1', - type: 'button', - label: 'Enable hoge', - value: false - }, { - id: 'radio1', - type: 'radio', - label: '強さ', - value: 2, - items: [{ - label: '弱', - value: 1 - }, { - label: '中', - value: 2 - }, { - label: '強', - value: 3 - }] - }] - }); - - this.connection.send({ - type: 'message', - body: { - text: 'Hey', - type: 'info' - } - }); - }, 2000); - } }, beforeDestroy() { diff --git a/src/web/app/common/views/components/timer.vue b/src/web/app/common/views/components/timer.vue new file mode 100644 index 000000000..a3c4f01b7 --- /dev/null +++ b/src/web/app/common/views/components/timer.vue @@ -0,0 +1,49 @@ + + + diff --git a/src/web/app/desktop/views/components/settings.vue b/src/web/app/desktop/views/components/settings.vue index f0cd69f37..950e60fb3 100644 --- a/src/web/app/desktop/views/components/settings.vue +++ b/src/web/app/desktop/views/components/settings.vue @@ -173,6 +173,10 @@ 実験的機能を有効にするとMisskeyの動作が不安定になる可能性があります。この設定はブラウザに記憶されます。 +
+ ツール + +
@@ -196,6 +200,7 @@ import XSignins from './settings.signins.vue'; import XDrive from './settings.drive.vue'; import { url, docsUrl, license, lang, version } from '../../../config'; import checkForUpdate from '../../../common/scripts/check-for-update'; +import MkTaskManager from './taskmanager.vue'; export default Vue.extend({ components: { @@ -226,6 +231,11 @@ export default Vue.extend({ enableExperimental: localStorage.getItem('enableExperimental') == 'true' }; }, + computed: { + licenseUrl(): string { + return `${docsUrl}/${lang}/license`; + } + }, watch: { autoPopout() { localStorage.setItem('autoPopout', this.autoPopout ? 'true' : 'false'); @@ -252,17 +262,15 @@ export default Vue.extend({ localStorage.setItem('enableExperimental', this.enableExperimental ? 'true' : 'false'); } }, - computed: { - licenseUrl(): string { - return `${docsUrl}/${lang}/license`; - } - }, created() { (this as any).os.getMeta().then(meta => { this.meta = meta; }); }, methods: { + taskmngr() { + (this as any).os.new(MkTaskManager); + }, customizeHome() { this.$router.push('/i/customize-home'); this.$emit('done'); diff --git a/src/web/app/desktop/views/components/taskmanager.vue b/src/web/app/desktop/views/components/taskmanager.vue new file mode 100644 index 000000000..c0a8b2e9a --- /dev/null +++ b/src/web/app/desktop/views/components/taskmanager.vue @@ -0,0 +1,204 @@ + + + + + + + diff --git a/src/web/app/desktop/views/components/window.vue b/src/web/app/desktop/views/components/window.vue index 42b2600dc..0f89aa3e4 100644 --- a/src/web/app/desktop/views/components/window.vue +++ b/src/web/app/desktop/views/components/window.vue @@ -68,7 +68,12 @@ export default Vue.extend({ default: 'auto' }, popoutUrl: { - type: [String, Function] + type: [String, Function], + default: null + }, + name: { + type: String, + default: null } }, diff --git a/src/web/app/desktop/views/widgets/channel.channel.vue b/src/web/app/desktop/views/widgets/channel.channel.vue index 02cdf6de1..de5885bfc 100644 --- a/src/web/app/desktop/views/widgets/channel.channel.vue +++ b/src/web/app/desktop/views/widgets/channel.channel.vue @@ -54,7 +54,7 @@ export default Vue.extend({ }); this.disconnect(); - this.connection = new ChannelStream(this.channel.id); + this.connection = new ChannelStream((this as any).os, this.channel.id); this.connection.on('post', this.onPost); }); }, diff --git a/webpack.config.ts b/webpack.config.ts index a0e03043f..da8b37564 100644 --- a/webpack.config.ts +++ b/webpack.config.ts @@ -243,8 +243,12 @@ module.exports = entries.map(x => { cache: true, devtool: 'source-map', optimization: { - minimize: doMinify + minimize: isProduction && doMinify }, - mode: doMinify ? 'production' : 'development' + mode: isProduction + ? doMinify + ? 'production' + : 'development' + : 'development' }; });