diff --git a/src/web/app/common/mios.ts b/src/web/app/common/mios.ts index 3690d3171d..aa07cb0224 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 434b108b9e..cab5f4edb4 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 5805e58033..7ff85b5946 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 1f110bfd3b..533c232449 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 69758416dc..84e2174ec4 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 1fff2286b3..c1b5875cfb 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 cdf46d5d8d..b85af8f72b 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 febc5d498a..f5d47431cd 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 5d199a0742..5bec30143f 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 b12198d2fd..3d35ef4d9d 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 a4a73c561f..568b8b0372 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 8799f6fe6b..189af0ab30 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 98fc2352f2..25f4e461df 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 547e9494e5..6ff808b617 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 9df458f644..38a25f6686 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 3b4296d0b9..3965414836 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 0000000000..a3c4f01b77 --- /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 f0cd69f372..950e60fb3b 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 0000000000..c0a8b2e9ab --- /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 42b2600dc4..0f89aa3e4b 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 02cdf6de13..de5885bfc1 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 a0e03043fa..da8b37564b 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' }; });