diff --git a/src/web/app/ch/tags/channel.tag b/src/web/app/ch/tags/channel.tag
index 48c5c705d3..8300bd5715 100644
--- a/src/web/app/ch/tags/channel.tag
+++ b/src/web/app/ch/tags/channel.tag
@@ -55,7 +55,7 @@
 	</style>
 	<script>
 		import Progress from '../../common/scripts/loading';
-		import ChannelStream from '../../common/scripts/channel-stream';
+		import ChannelStream from '../../common/scripts/streaming/channel-stream';
 
 		this.mixin('i');
 		this.mixin('api');
diff --git a/src/web/app/common/mios.ts b/src/web/app/common/mios.ts
index 23f6980cf4..9704e92af8 100644
--- a/src/web/app/common/mios.ts
+++ b/src/web/app/common/mios.ts
@@ -2,7 +2,7 @@ import { EventEmitter } from 'eventemitter3';
 import * as riot from 'riot';
 import signout from './scripts/signout';
 import Progress from './scripts/loading';
-import Connection from './scripts/home-stream';
+import HomeStreamManager from './scripts/streaming/home-stream-manager';
 import CONFIG from './scripts/config';
 import api from './scripts/api';
 
@@ -33,9 +33,9 @@ export default class MiOS extends EventEmitter {
 	}
 
 	/**
-	 * A connection of home stream
+	 * A connection manager of home stream
 	 */
-	public stream: Connection;
+	public stream: HomeStreamManager;
 
 	constructor() {
 		super();
@@ -67,21 +67,27 @@ export default class MiOS extends EventEmitter {
 				body: JSON.stringify({
 					i: token
 				})
-			}).then(res => { // When success
+			})
+			// When success
+			.then(res => {
 				// When failed to authenticate user
 				if (res.status !== 200) {
 					return signout();
 				}
 
+				// Parse response
 				res.json().then(i => {
 					me = i;
 					me.token = token;
 					done();
 				});
-			}, () => { // When failure
+			})
+			// When failure
+			.catch(() => {
 				// Render the error screen
 				document.body.innerHTML = '<mk-error />';
 				riot.mount('*');
+
 				Progress.done();
 			});
 
@@ -104,6 +110,7 @@ export default class MiOS extends EventEmitter {
 				// ローカルストレージにキャッシュ
 				localStorage.setItem('me', JSON.stringify(me));
 
+				// 自分の情報が更新されたとき
 				me.on('updated', () => {
 					// キャッシュ更新
 					localStorage.setItem('me', JSON.stringify(me));
@@ -112,8 +119,10 @@ export default class MiOS extends EventEmitter {
 
 			this.i = me;
 
-			// Init home stream connection
-			this.stream = this.i ? new Connection(this.i) : null;
+			// Init home stream manager
+			this.stream = this.isSignedin
+				? new HomeStreamManager(this.i)
+				: null;
 
 			// Finish init
 			callback();
diff --git a/src/web/app/common/mixins.ts b/src/web/app/common/mixins.ts
index 98818118f4..e9c3625937 100644
--- a/src/web/app/common/mixins.ts
+++ b/src/web/app/common/mixins.ts
@@ -1,10 +1,10 @@
 import * as riot from 'riot';
 
 import MiOS from './mios';
-import ServerStreamManager from './scripts/server-stream-manager';
-import RequestsStreamManager from './scripts/requests-stream-manager';
-import MessagingIndexStreamManager from './scripts/messaging-index-stream-manager';
-import DriveStreamManager from './scripts/drive-stream-manager';
+import ServerStreamManager from './scripts/streaming/server-stream-manager';
+import RequestsStreamManager from './scripts/streaming/requests-stream-manager';
+import MessagingIndexStreamManager from './scripts/streaming/messaging-index-stream-manager';
+import DriveStreamManager from './scripts/streaming/drive-stream-manager';
 
 export default (mios: MiOS) => {
 	(riot as any).mixin('os', {
diff --git a/src/web/app/common/scripts/stream-manager.ts b/src/web/app/common/scripts/stream-manager.ts
deleted file mode 100644
index 4eaf0f9a45..0000000000
--- a/src/web/app/common/scripts/stream-manager.ts
+++ /dev/null
@@ -1,33 +0,0 @@
-import * as uuid from 'uuid';
-import Connection from './stream';
-
-export default abstract class StreamManager<T extends Connection> {
-	protected connection: T = null;
-
-	/**
-	 * コネクションを必要としているユーザー
-	 */
-	private users = [];
-
-	public abstract getConnection(): T;
-
-	public use() {
-		// ユーザーID生成
-		const userId = uuid();
-
-		this.users.push(userId);
-
-		return userId;
-	}
-
-	public dispose(userId) {
-		this.users = this.users.filter(id => id != userId);
-
-		// 誰もコネクションの利用者がいなくなったら
-		if (this.users.length == 0) {
-			// コネクションを切断する
-			this.connection.close();
-			this.connection = null;
-		}
-	}
-}
diff --git a/src/web/app/common/scripts/channel-stream.ts b/src/web/app/common/scripts/streaming/channel-stream.ts
similarity index 70%
rename from src/web/app/common/scripts/channel-stream.ts
rename to src/web/app/common/scripts/streaming/channel-stream.ts
index c9c7fbbcda..434b108b9e 100644
--- a/src/web/app/common/scripts/channel-stream.ts
+++ b/src/web/app/common/scripts/streaming/channel-stream.ts
@@ -3,12 +3,10 @@ import Stream from './stream';
 /**
  * Channel stream connection
  */
-class Connection extends Stream {
+export default class Connection extends Stream {
 	constructor(channelId) {
 		super('channel', {
 			channel: channelId
 		});
 	}
 }
-
-export default Connection;
diff --git a/src/web/app/common/scripts/drive-stream-manager.ts b/src/web/app/common/scripts/streaming/drive-stream-manager.ts
similarity index 100%
rename from src/web/app/common/scripts/drive-stream-manager.ts
rename to src/web/app/common/scripts/streaming/drive-stream-manager.ts
diff --git a/src/web/app/common/scripts/drive-stream.ts b/src/web/app/common/scripts/streaming/drive-stream.ts
similarity index 67%
rename from src/web/app/common/scripts/drive-stream.ts
rename to src/web/app/common/scripts/streaming/drive-stream.ts
index 1b33435578..0da3f12554 100644
--- a/src/web/app/common/scripts/drive-stream.ts
+++ b/src/web/app/common/scripts/streaming/drive-stream.ts
@@ -3,12 +3,10 @@ import Stream from './stream';
 /**
  * Drive stream connection
  */
-class Connection extends Stream {
+export default class Connection extends Stream {
 	constructor(me) {
 		super('drive', {
 			i: me.token
 		});
 	}
 }
-
-export default Connection;
diff --git a/src/web/app/common/scripts/streaming/home-stream-manager.ts b/src/web/app/common/scripts/streaming/home-stream-manager.ts
new file mode 100644
index 0000000000..ad1dc870eb
--- /dev/null
+++ b/src/web/app/common/scripts/streaming/home-stream-manager.ts
@@ -0,0 +1,20 @@
+import StreamManager from './stream-manager';
+import Connection from './home-stream';
+
+export default class HomeStreamManager extends StreamManager<Connection> {
+	private me;
+
+	constructor(me) {
+		super();
+
+		this.me = me;
+	}
+
+	public getConnection() {
+		if (this.connection == null) {
+			this.connection = new Connection(this.me);
+		}
+
+		return this.connection;
+	}
+}
diff --git a/src/web/app/common/scripts/home-stream.ts b/src/web/app/common/scripts/streaming/home-stream.ts
similarity index 62%
rename from src/web/app/common/scripts/home-stream.ts
rename to src/web/app/common/scripts/streaming/home-stream.ts
index d6324eeb7b..a78f4acdbe 100644
--- a/src/web/app/common/scripts/home-stream.ts
+++ b/src/web/app/common/scripts/streaming/home-stream.ts
@@ -1,10 +1,10 @@
 import Stream from './stream';
-import signout from './signout';
+import signout from '../signout';
 
 /**
  * Home stream connection
  */
-class Connection extends Stream {
+export default class Connection extends Stream {
 	constructor(me) {
 		super('', {
 			i: me.token
@@ -15,13 +15,14 @@ class Connection extends Stream {
 			this.send({ type: 'alive' });
 		}, 1000 * 60);
 
+		// 自分の情報が更新されたとき
 		(this as any).on('i_updated', me.update);
 
+		// トークンが再生成されたとき
+		// このままではAPIが利用できないので強制的にサインアウトさせる
 		(this as any).on('my_token_regenerated', () => {
 			alert('%i18n:common.my-token-regenerated%');
 			signout();
 		});
 	}
 }
-
-export default Connection;
diff --git a/src/web/app/common/scripts/messaging-index-stream-manager.ts b/src/web/app/common/scripts/streaming/messaging-index-stream-manager.ts
similarity index 100%
rename from src/web/app/common/scripts/messaging-index-stream-manager.ts
rename to src/web/app/common/scripts/streaming/messaging-index-stream-manager.ts
diff --git a/src/web/app/common/scripts/messaging-index-stream.ts b/src/web/app/common/scripts/streaming/messaging-index-stream.ts
similarity index 70%
rename from src/web/app/common/scripts/messaging-index-stream.ts
rename to src/web/app/common/scripts/streaming/messaging-index-stream.ts
index c194e663c2..8015c840b4 100644
--- a/src/web/app/common/scripts/messaging-index-stream.ts
+++ b/src/web/app/common/scripts/streaming/messaging-index-stream.ts
@@ -3,12 +3,10 @@ import Stream from './stream';
 /**
  * Messaging index stream connection
  */
-class Connection extends Stream {
+export default class Connection extends Stream {
 	constructor(me) {
 		super('messaging-index', {
 			i: me.token
 		});
 	}
 }
-
-export default Connection;
diff --git a/src/web/app/common/scripts/messaging-stream.ts b/src/web/app/common/scripts/streaming/messaging-stream.ts
similarity index 80%
rename from src/web/app/common/scripts/messaging-stream.ts
rename to src/web/app/common/scripts/streaming/messaging-stream.ts
index 306a733274..68dfc5ec09 100644
--- a/src/web/app/common/scripts/messaging-stream.ts
+++ b/src/web/app/common/scripts/streaming/messaging-stream.ts
@@ -3,7 +3,7 @@ import Stream from './stream';
 /**
  * Messaging stream connection
  */
-class Connection extends Stream {
+export default class Connection extends Stream {
 	constructor(me, otherparty) {
 		super('messaging', {
 			i: me.token,
@@ -17,5 +17,3 @@ class Connection extends Stream {
 		});
 	}
 }
-
-export default Connection;
diff --git a/src/web/app/common/scripts/requests-stream-manager.ts b/src/web/app/common/scripts/streaming/requests-stream-manager.ts
similarity index 100%
rename from src/web/app/common/scripts/requests-stream-manager.ts
rename to src/web/app/common/scripts/streaming/requests-stream-manager.ts
diff --git a/src/web/app/common/scripts/requests-stream.ts b/src/web/app/common/scripts/streaming/requests-stream.ts
similarity index 64%
rename from src/web/app/common/scripts/requests-stream.ts
rename to src/web/app/common/scripts/streaming/requests-stream.ts
index df342693a7..22ecea6c07 100644
--- a/src/web/app/common/scripts/requests-stream.ts
+++ b/src/web/app/common/scripts/streaming/requests-stream.ts
@@ -3,10 +3,8 @@ import Stream from './stream';
 /**
  * Requests stream connection
  */
-class Connection extends Stream {
+export default class Connection extends Stream {
 	constructor() {
 		super('requests');
 	}
 }
-
-export default Connection;
diff --git a/src/web/app/common/scripts/server-stream-manager.ts b/src/web/app/common/scripts/streaming/server-stream-manager.ts
similarity index 100%
rename from src/web/app/common/scripts/server-stream-manager.ts
rename to src/web/app/common/scripts/streaming/server-stream-manager.ts
diff --git a/src/web/app/common/scripts/server-stream.ts b/src/web/app/common/scripts/streaming/server-stream.ts
similarity index 63%
rename from src/web/app/common/scripts/server-stream.ts
rename to src/web/app/common/scripts/streaming/server-stream.ts
index 938ae4bfe5..b9e0684465 100644
--- a/src/web/app/common/scripts/server-stream.ts
+++ b/src/web/app/common/scripts/streaming/server-stream.ts
@@ -3,10 +3,8 @@ import Stream from './stream';
 /**
  * Server stream connection
  */
-class Connection extends Stream {
+export default class Connection extends Stream {
 	constructor() {
 		super('server');
 	}
 }
-
-export default Connection;
diff --git a/src/web/app/common/scripts/streaming/stream-manager.ts b/src/web/app/common/scripts/streaming/stream-manager.ts
new file mode 100644
index 0000000000..de27235426
--- /dev/null
+++ b/src/web/app/common/scripts/streaming/stream-manager.ts
@@ -0,0 +1,73 @@
+import { EventEmitter } from 'eventemitter3';
+import * as uuid from 'uuid';
+import Connection from './stream';
+
+/**
+ * ストリーム接続を管理するクラス
+ * 複数の場所から同じストリームを利用する際、接続をまとめたりする
+ */
+export default abstract class StreamManager<T extends Connection> extends EventEmitter {
+	protected _connection: T = null;
+
+	/**
+	 * コネクションを必要としているユーザー
+	 */
+	private users = [];
+
+	protected set connection(connection: T) {
+		this._connection = connection;
+
+		if (this._connection == null) {
+			this.emit('disconnected');
+		} else {
+			this.emit('connected', this._connection);
+		}
+	}
+
+	protected get connection() {
+		return this._connection;
+	}
+
+	/**
+	 * コネクションを持っているか否か
+	 */
+	public get hasConnection() {
+		return this._connection != null;
+	}
+
+	/**
+	 * コネクションを要求します
+	 */
+	public abstract getConnection(): T;
+
+	public borrow() {
+		return this._connection;
+	}
+
+	/**
+	 * コネクションを要求するためのユーザーIDを発行します
+	 */
+	public use() {
+		// ユーザーID生成
+		const userId = uuid();
+
+		this.users.push(userId);
+
+		return userId;
+	}
+
+	/**
+	 * コネクションを利用し終わってもう必要ないことを通知します
+	 * @param userId use で発行したユーザーID
+	 */
+	public dispose(userId) {
+		this.users = this.users.filter(id => id != userId);
+
+		// 誰もコネクションの利用者がいなくなったら
+		if (this.users.length == 0) {
+			// コネクションを切断する
+			this.connection.close();
+			this.connection = null;
+		}
+	}
+}
diff --git a/src/web/app/common/scripts/stream.ts b/src/web/app/common/scripts/streaming/stream.ts
similarity index 85%
rename from src/web/app/common/scripts/stream.ts
rename to src/web/app/common/scripts/streaming/stream.ts
index 6843394644..97ebdcdd74 100644
--- a/src/web/app/common/scripts/stream.ts
+++ b/src/web/app/common/scripts/streaming/stream.ts
@@ -1,25 +1,25 @@
+import { EventEmitter } from 'eventemitter3';
 import * as ReconnectingWebsocket from 'reconnecting-websocket';
-import * as riot from 'riot';
-import CONFIG from './config';
+import CONFIG from '../config';
 
 /**
  * Misskey stream connection
  */
-class Connection {
+export default class Connection extends EventEmitter {
 	private state: string;
 	private buffer: any[];
 	private socket: ReconnectingWebsocket;
 
 	constructor(endpoint, params?) {
-		// BIND -----------------------------------
+		super();
+
+		//#region BIND
 		this.onOpen =    this.onOpen.bind(this);
 		this.onClose =   this.onClose.bind(this);
 		this.onMessage = this.onMessage.bind(this);
 		this.send =      this.send.bind(this);
 		this.close =     this.close.bind(this);
-		// ----------------------------------------
-
-		riot.observable(this);
+		//#endregion
 
 		this.state = 'initializing';
 		this.buffer = [];
@@ -42,7 +42,7 @@ class Connection {
 	 */
 	private onOpen() {
 		this.state = 'connected';
-		(this as any).trigger('_connected_');
+		this.emit('_connected_');
 
 		// バッファーを処理
 		const _buffer = [].concat(this.buffer); // Shallow copy
@@ -57,7 +57,7 @@ class Connection {
 	 */
 	private onClose() {
 		this.state = 'reconnecting';
-		(this as any).trigger('_closed_');
+		this.emit('_closed_');
 	}
 
 	/**
@@ -66,7 +66,7 @@ class Connection {
 	private onMessage(message) {
 		try {
 			const msg = JSON.parse(message.data);
-			if (msg.type) (this as any).trigger(msg.type, msg.body);
+			if (msg.type) this.emit(msg.type, msg.body);
 		} catch (e) {
 			// noop
 		}
@@ -93,5 +93,3 @@ class Connection {
 		this.socket.removeEventListener('message', this.onMessage);
 	}
 }
-
-export default Connection;
diff --git a/src/web/app/common/tags/messaging/room.tag b/src/web/app/common/tags/messaging/room.tag
index b1082e26be..a930327841 100644
--- a/src/web/app/common/tags/messaging/room.tag
+++ b/src/web/app/common/tags/messaging/room.tag
@@ -162,7 +162,7 @@
 
 	</style>
 	<script>
-		import MessagingStreamConnection from '../../scripts/messaging-stream';
+		import MessagingStreamConnection from '../../scripts/streaming/messaging-stream';
 
 		this.mixin('i');
 		this.mixin('api');
diff --git a/src/web/app/common/tags/signin-history.tag b/src/web/app/common/tags/signin-history.tag
index 9c96746249..b9bd859851 100644
--- a/src/web/app/common/tags/signin-history.tag
+++ b/src/web/app/common/tags/signin-history.tag
@@ -50,7 +50,10 @@
 	<script>
 		this.mixin('i');
 		this.mixin('api');
+
 		this.mixin('stream');
+		this.connection = this.stream.getConnection();
+		this.connectionId = this.stream.use();
 
 		this.history = [];
 		this.fetching = true;
@@ -63,11 +66,12 @@
 				});
 			});
 
-			this.stream.on('signin', this.onSignin);
+			this.connection.on('signin', this.onSignin);
 		});
 
 		this.on('unmount', () => {
-			this.stream.off('signin', this.onSignin);
+			this.connection.off('signin', this.onSignin);
+			this.stream.dispose(this.connectionId);
 		});
 
 		this.onSignin = signin => {
diff --git a/src/web/app/common/tags/stream-indicator.tag b/src/web/app/common/tags/stream-indicator.tag
index ea1c437035..0d74985c88 100644
--- a/src/web/app/common/tags/stream-indicator.tag
+++ b/src/web/app/common/tags/stream-indicator.tag
@@ -1,13 +1,13 @@
 <mk-stream-indicator>
-	<p if={ stream.state == 'initializing' }>
+	<p if={ connection.state == 'initializing' }>
 		<i class="fa fa-spinner fa-spin"></i>
 		<span>%i18n:common.tags.mk-stream-indicator.connecting%<mk-ellipsis/></span>
 	</p>
-	<p if={ stream.state == 'reconnecting' }>
+	<p if={ connection.state == 'reconnecting' }>
 		<i class="fa fa-spinner fa-spin"></i>
 		<span>%i18n:common.tags.mk-stream-indicator.reconnecting%<mk-ellipsis/></span>
 	</p>
-	<p if={ stream.state == 'connected' }>
+	<p if={ connection.state == 'connected' }>
 		<i class="fa fa-check"></i>
 		<span>%i18n:common.tags.mk-stream-indicator.connected%</span>
 	</p>
@@ -38,34 +38,41 @@
 		import anime from 'animejs';
 
 		this.mixin('i');
+
 		this.mixin('stream');
+		this.connection = this.stream.getConnection();
+		this.connectionId = this.stream.use();
 
 		this.on('before-mount', () => {
-			if (this.stream.state == 'connected') {
+			if (this.connection.state == 'connected') {
 				this.root.style.opacity = 0;
 			}
-		});
 
-		this.stream.on('_connected_', () => {
-			this.update();
-			setTimeout(() => {
+			this.connection.on('_connected_', () => {
+				this.update();
+				setTimeout(() => {
+					anime({
+						targets: this.root,
+						opacity: 0,
+						easing: 'linear',
+						duration: 200
+					});
+				}, 1000);
+			});
+
+			this.connection.on('_closed_', () => {
+				this.update();
 				anime({
 					targets: this.root,
-					opacity: 0,
+					opacity: 1,
 					easing: 'linear',
-					duration: 200
+					duration: 100
 				});
-			}, 1000);
+			});
 		});
 
-		this.stream.on('_closed_', () => {
-			this.update();
-			anime({
-				targets: this.root,
-				opacity: 1,
-				easing: 'linear',
-				duration: 100
-			});
+		this.on('unmount', () => {
+			this.stream.dispose(this.connectionId);
 		});
 	</script>
 </mk-stream-indicator>
diff --git a/src/web/app/desktop/script.ts b/src/web/app/desktop/script.ts
index b4a8d829d6..bc0fc8dfe3 100644
--- a/src/web/app/desktop/script.ts
+++ b/src/web/app/desktop/script.ts
@@ -13,6 +13,7 @@ import route from './router';
 import fuckAdBlock from './scripts/fuck-ad-block';
 import getPostSummary from '../../../common/get-post-summary';
 import MiOS from '../common/mios';
+import HomeStreamManager from '../common/scripts/streaming/home-stream-manager';
 
 /**
  * init
@@ -41,52 +42,62 @@ init(async (mios: MiOS) => {
 	route(mios);
 });
 
-function registerNotifications(stream) {
+function registerNotifications(stream: HomeStreamManager) {
 	if (stream == null) return;
 
-	stream.on('drive_file_created', file => {
-		const n = new Notification('ファイルがアップロードされました', {
-			body: file.name,
-			icon: file.url + '?thumbnail&size=64'
-		});
-		setTimeout(n.close.bind(n), 5000);
+	if (stream.hasConnection) {
+		attach(stream.borrow());
+	}
+
+	stream.on('connected', connection => {
+		attach(connection);
 	});
 
-	stream.on('mention', post => {
-		const n = new Notification(`${post.user.name}さんから:`, {
-			body: getPostSummary(post),
-			icon: post.user.avatar_url + '?thumbnail&size=64'
-		});
-		setTimeout(n.close.bind(n), 6000);
-	});
-
-	stream.on('reply', post => {
-		const n = new Notification(`${post.user.name}さんから返信:`, {
-			body: getPostSummary(post),
-			icon: post.user.avatar_url + '?thumbnail&size=64'
-		});
-		setTimeout(n.close.bind(n), 6000);
-	});
-
-	stream.on('quote', post => {
-		const n = new Notification(`${post.user.name}さんが引用:`, {
-			body: getPostSummary(post),
-			icon: post.user.avatar_url + '?thumbnail&size=64'
-		});
-		setTimeout(n.close.bind(n), 6000);
-	});
-
-	stream.on('unread_messaging_message', message => {
-		const n = new Notification(`${message.user.name}さんからメッセージ:`, {
-			body: message.text, // TODO: getMessagingMessageSummary(message),
-			icon: message.user.avatar_url + '?thumbnail&size=64'
-		});
-		n.onclick = () => {
-			n.close();
-			(riot as any).mount(document.body.appendChild(document.createElement('mk-messaging-room-window')), {
-				user: message.user
+	function attach(connection) {
+		connection.on('drive_file_created', file => {
+			const n = new Notification('ファイルがアップロードされました', {
+				body: file.name,
+				icon: file.url + '?thumbnail&size=64'
 			});
-		};
-		setTimeout(n.close.bind(n), 7000);
-	});
+			setTimeout(n.close.bind(n), 5000);
+		});
+
+		connection.on('mention', post => {
+			const n = new Notification(`${post.user.name}さんから:`, {
+				body: getPostSummary(post),
+				icon: post.user.avatar_url + '?thumbnail&size=64'
+			});
+			setTimeout(n.close.bind(n), 6000);
+		});
+
+		connection.on('reply', post => {
+			const n = new Notification(`${post.user.name}さんから返信:`, {
+				body: getPostSummary(post),
+				icon: post.user.avatar_url + '?thumbnail&size=64'
+			});
+			setTimeout(n.close.bind(n), 6000);
+		});
+
+		connection.on('quote', post => {
+			const n = new Notification(`${post.user.name}さんが引用:`, {
+				body: getPostSummary(post),
+				icon: post.user.avatar_url + '?thumbnail&size=64'
+			});
+			setTimeout(n.close.bind(n), 6000);
+		});
+
+		connection.on('unread_messaging_message', message => {
+			const n = new Notification(`${message.user.name}さんからメッセージ:`, {
+				body: message.text, // TODO: getMessagingMessageSummary(message),
+				icon: message.user.avatar_url + '?thumbnail&size=64'
+			});
+			n.onclick = () => {
+				n.close();
+				(riot as any).mount(document.body.appendChild(document.createElement('mk-messaging-room-window')), {
+					user: message.user
+				});
+			};
+			setTimeout(n.close.bind(n), 7000);
+		});
+	}
 }
diff --git a/src/web/app/desktop/tags/big-follow-button.tag b/src/web/app/desktop/tags/big-follow-button.tag
index 86df2d4924..8897748ae1 100644
--- a/src/web/app/desktop/tags/big-follow-button.tag
+++ b/src/web/app/desktop/tags/big-follow-button.tag
@@ -74,7 +74,10 @@
 
 		this.mixin('i');
 		this.mixin('api');
+
 		this.mixin('stream');
+		this.connection = this.stream.getConnection();
+		this.connectionId = this.stream.use();
 
 		this.user = null;
 		this.userPromise = isPromise(this.opts.user)
@@ -89,14 +92,15 @@
 					init: false,
 					user: user
 				});
-				this.stream.on('follow', this.onStreamFollow);
-				this.stream.on('unfollow', this.onStreamUnfollow);
+				this.connection.on('follow', this.onStreamFollow);
+				this.connection.on('unfollow', this.onStreamUnfollow);
 			});
 		});
 
 		this.on('unmount', () => {
-			this.stream.off('follow', this.onStreamFollow);
-			this.stream.off('unfollow', this.onStreamUnfollow);
+			this.connection.off('follow', this.onStreamFollow);
+			this.connection.off('unfollow', this.onStreamUnfollow);
+			this.stream.dispose(this.connectionId);
 		});
 
 		this.onStreamFollow = user => {
diff --git a/src/web/app/desktop/tags/follow-button.tag b/src/web/app/desktop/tags/follow-button.tag
index 00ff686f69..a1cbc191d8 100644
--- a/src/web/app/desktop/tags/follow-button.tag
+++ b/src/web/app/desktop/tags/follow-button.tag
@@ -71,7 +71,10 @@
 
 		this.mixin('i');
 		this.mixin('api');
+
 		this.mixin('stream');
+		this.connection = this.stream.getConnection();
+		this.connectionId = this.stream.use();
 
 		this.user = null;
 		this.userPromise = isPromise(this.opts.user)
@@ -86,14 +89,15 @@
 					init: false,
 					user: user
 				});
-				this.stream.on('follow', this.onStreamFollow);
-				this.stream.on('unfollow', this.onStreamUnfollow);
+				this.connection.on('follow', this.onStreamFollow);
+				this.connection.on('unfollow', this.onStreamUnfollow);
 			});
 		});
 
 		this.on('unmount', () => {
-			this.stream.off('follow', this.onStreamFollow);
-			this.stream.off('unfollow', this.onStreamUnfollow);
+			this.connection.off('follow', this.onStreamFollow);
+			this.connection.off('unfollow', this.onStreamUnfollow);
+			this.stream.dispose(this.connectionId);
 		});
 
 		this.onStreamFollow = user => {
diff --git a/src/web/app/desktop/tags/home-widgets/channel.tag b/src/web/app/desktop/tags/home-widgets/channel.tag
index 28c66df5c0..fc4a27e4f4 100644
--- a/src/web/app/desktop/tags/home-widgets/channel.tag
+++ b/src/web/app/desktop/tags/home-widgets/channel.tag
@@ -138,7 +138,7 @@
 
 	</style>
 	<script>
-		import ChannelStream from '../../../common/scripts/channel-stream';
+		import ChannelStream from '../../../common/scripts/streaming/channel-stream';
 
 		this.mixin('api');
 
diff --git a/src/web/app/desktop/tags/home-widgets/photo-stream.tag b/src/web/app/desktop/tags/home-widgets/photo-stream.tag
index fa9207c48d..05658c9025 100644
--- a/src/web/app/desktop/tags/home-widgets/photo-stream.tag
+++ b/src/web/app/desktop/tags/home-widgets/photo-stream.tag
@@ -75,13 +75,16 @@
 		};
 
 		this.mixin('widget');
+
 		this.mixin('stream');
+		this.connection = this.stream.getConnection();
+		this.connectionId = this.stream.use();
 
 		this.images = [];
 		this.initializing = true;
 
 		this.on('mount', () => {
-			this.stream.on('drive_file_created', this.onStreamDriveFileCreated);
+			this.connection.on('drive_file_created', this.onStreamDriveFileCreated);
 
 			this.api('drive/stream', {
 				type: 'image/*',
@@ -95,7 +98,8 @@
 		});
 
 		this.on('unmount', () => {
-			this.stream.off('drive_file_created', this.onStreamDriveFileCreated);
+			this.connection.off('drive_file_created', this.onStreamDriveFileCreated);
+			this.stream.dispose(this.connectionId);
 		});
 
 		this.onStreamDriveFileCreated = file => {
diff --git a/src/web/app/desktop/tags/home-widgets/timeline.tag b/src/web/app/desktop/tags/home-widgets/timeline.tag
index 5b141b632d..c751069f74 100644
--- a/src/web/app/desktop/tags/home-widgets/timeline.tag
+++ b/src/web/app/desktop/tags/home-widgets/timeline.tag
@@ -40,7 +40,10 @@
 	<script>
 		this.mixin('i');
 		this.mixin('api');
+
 		this.mixin('stream');
+		this.connection = this.stream.getConnection();
+		this.connectionId = this.stream.use();
 
 		this.isLoading = true;
 		this.isEmpty = false;
@@ -48,9 +51,9 @@
 		this.noFollowing = this.I.following_count == 0;
 
 		this.on('mount', () => {
-			this.stream.on('post', this.onStreamPost);
-			this.stream.on('follow', this.onStreamFollow);
-			this.stream.on('unfollow', this.onStreamUnfollow);
+			this.connection.on('post', this.onStreamPost);
+			this.connection.on('follow', this.onStreamFollow);
+			this.connection.on('unfollow', this.onStreamUnfollow);
 
 			document.addEventListener('keydown', this.onDocumentKeydown);
 			window.addEventListener('scroll', this.onScroll);
@@ -59,9 +62,10 @@
 		});
 
 		this.on('unmount', () => {
-			this.stream.off('post', this.onStreamPost);
-			this.stream.off('follow', this.onStreamFollow);
-			this.stream.off('unfollow', this.onStreamUnfollow);
+			this.connection.off('post', this.onStreamPost);
+			this.connection.off('follow', this.onStreamFollow);
+			this.connection.off('unfollow', this.onStreamUnfollow);
+			this.stream.dispose(this.connectionId);
 
 			document.removeEventListener('keydown', this.onDocumentKeydown);
 			window.removeEventListener('scroll', this.onScroll);
diff --git a/src/web/app/desktop/tags/notifications.tag b/src/web/app/desktop/tags/notifications.tag
index a4f66105a8..d7855363ea 100644
--- a/src/web/app/desktop/tags/notifications.tag
+++ b/src/web/app/desktop/tags/notifications.tag
@@ -212,9 +212,12 @@
 
 		this.mixin('i');
 		this.mixin('api');
-		this.mixin('stream');
 		this.mixin('user-preview');
 
+		this.mixin('stream');
+		this.connection = this.stream.getConnection();
+		this.connectionId = this.stream.use();
+
 		this.notifications = [];
 		this.loading = true;
 
@@ -235,11 +238,12 @@
 				});
 			});
 
-			this.stream.on('notification', this.onNotification);
+			this.connection.on('notification', this.onNotification);
 		});
 
 		this.on('unmount', () => {
-			this.stream.off('notification', this.onNotification);
+			this.connection.off('notification', this.onNotification);
+			this.stream.dispose(this.connectionId);
 		});
 
 		this.on('update', () => {
@@ -253,7 +257,7 @@
 
 		this.onNotification = notification => {
 			// TODO: ユーザーが画面を見てないと思われるとき(ブラウザやタブがアクティブじゃないなど)は送信しない
-			this.stream.send({
+			this.connection.send({
 				type: 'read_notification',
 				id: notification.id
 			});
diff --git a/src/web/app/desktop/tags/pages/home.tag b/src/web/app/desktop/tags/pages/home.tag
index e8ba4023de..3c8f4ec570 100644
--- a/src/web/app/desktop/tags/pages/home.tag
+++ b/src/web/app/desktop/tags/pages/home.tag
@@ -12,10 +12,12 @@
 
 		this.mixin('i');
 		this.mixin('api');
+
 		this.mixin('stream');
+		this.connection = this.stream.getConnection();
+		this.connectionId = this.stream.use();
 
 		this.unreadCount = 0;
-
 		this.page = this.opts.mode || 'timeline';
 
 		this.on('mount', () => {
@@ -24,12 +26,14 @@
 			});
 			document.title = 'Misskey';
 			Progress.start();
-			this.stream.on('post', this.onStreamPost);
+
+			this.connection.on('post', this.onStreamPost);
 			document.addEventListener('visibilitychange', this.windowOnVisibilitychange, false);
 		});
 
 		this.on('unmount', () => {
-			this.stream.off('post', this.onStreamPost);
+			this.connection.off('post', this.onStreamPost);
+			this.stream.dispose(this.connectionId);
 			document.removeEventListener('visibilitychange', this.windowOnVisibilitychange);
 		});
 
diff --git a/src/web/app/desktop/tags/timeline.tag b/src/web/app/desktop/tags/timeline.tag
index 5e3b883b47..3e745976f3 100644
--- a/src/web/app/desktop/tags/timeline.tag
+++ b/src/web/app/desktop/tags/timeline.tag
@@ -430,9 +430,12 @@
 
 		this.mixin('i');
 		this.mixin('api');
-		this.mixin('stream');
 		this.mixin('user-preview');
 
+		this.mixin('stream');
+		this.connection = this.stream.getConnection();
+		this.connectionId = this.stream.use();
+
 		this.isDetailOpened = false;
 
 		this.set = post => {
@@ -468,21 +471,21 @@
 
 		this.capture = withHandler => {
 			if (this.SIGNIN) {
-				this.stream.send({
+				this.connection.send({
 					type: 'capture',
 					id: this.post.id
 				});
-				if (withHandler) this.stream.on('post-updated', this.onStreamPostUpdated);
+				if (withHandler) this.connection.on('post-updated', this.onStreamPostUpdated);
 			}
 		};
 
 		this.decapture = withHandler => {
 			if (this.SIGNIN) {
-				this.stream.send({
+				this.connection.send({
 					type: 'decapture',
 					id: this.post.id
 				});
-				if (withHandler) this.stream.off('post-updated', this.onStreamPostUpdated);
+				if (withHandler) this.connection.off('post-updated', this.onStreamPostUpdated);
 			}
 		};
 
@@ -490,7 +493,7 @@
 			this.capture(true);
 
 			if (this.SIGNIN) {
-				this.stream.on('_connected_', this.onStreamConnected);
+				this.connection.on('_connected_', this.onStreamConnected);
 			}
 
 			if (this.p.text) {
@@ -515,7 +518,8 @@
 
 		this.on('unmount', () => {
 			this.decapture(true);
-			this.stream.off('_connected_', this.onStreamConnected);
+			this.connection.off('_connected_', this.onStreamConnected);
+			this.stream.dispose(this.connectionId);
 		});
 
 		this.reply = () => {
diff --git a/src/web/app/desktop/tags/ui.tag b/src/web/app/desktop/tags/ui.tag
index 36f9cc343f..f7e1bf7463 100644
--- a/src/web/app/desktop/tags/ui.tag
+++ b/src/web/app/desktop/tags/ui.tag
@@ -423,14 +423,17 @@
 	<script>
 		this.mixin('i');
 		this.mixin('api');
+
 		this.mixin('stream');
+		this.connection = this.stream.getConnection();
+		this.connectionId = this.stream.use();
 
 		this.page = this.opts.page;
 
 		this.on('mount', () => {
 			if (this.SIGNIN) {
-				this.stream.on('read_all_messaging_messages', this.onReadAllMessagingMessages);
-				this.stream.on('unread_messaging_message', this.onUnreadMessagingMessage);
+				this.connection.on('read_all_messaging_messages', this.onReadAllMessagingMessages);
+				this.connection.on('unread_messaging_message', this.onUnreadMessagingMessage);
 
 				// Fetch count of unread messaging messages
 				this.api('messaging/unread').then(res => {
@@ -445,8 +448,9 @@
 
 		this.on('unmount', () => {
 			if (this.SIGNIN) {
-				this.stream.off('read_all_messaging_messages', this.onReadAllMessagingMessages);
-				this.stream.off('unread_messaging_message', this.onUnreadMessagingMessage);
+				this.connection.off('read_all_messaging_messages', this.onReadAllMessagingMessages);
+				this.connection.off('unread_messaging_message', this.onUnreadMessagingMessage);
+				this.stream.dispose(this.connectionId);
 			}
 		});
 
diff --git a/src/web/app/mobile/tags/follow-button.tag b/src/web/app/mobile/tags/follow-button.tag
index 67d580eb99..f25b2ed50e 100644
--- a/src/web/app/mobile/tags/follow-button.tag
+++ b/src/web/app/mobile/tags/follow-button.tag
@@ -52,7 +52,10 @@
 
 		this.mixin('i');
 		this.mixin('api');
+
 		this.mixin('stream');
+		this.connection = this.stream.getConnection();
+		this.connectionId = this.stream.use();
 
 		this.user = null;
 		this.userPromise = isPromise(this.opts.user)
@@ -67,14 +70,15 @@
 					init: false,
 					user: user
 				});
-				this.stream.on('follow', this.onStreamFollow);
-				this.stream.on('unfollow', this.onStreamUnfollow);
+				this.connection.on('follow', this.onStreamFollow);
+				this.connection.on('unfollow', this.onStreamUnfollow);
 			});
 		});
 
 		this.on('unmount', () => {
-			this.stream.off('follow', this.onStreamFollow);
-			this.stream.off('unfollow', this.onStreamUnfollow);
+			this.connection.off('follow', this.onStreamFollow);
+			this.connection.off('unfollow', this.onStreamUnfollow);
+			this.stream.dispose(this.connectionId);
 		});
 
 		this.onStreamFollow = user => {
diff --git a/src/web/app/mobile/tags/home-timeline.tag b/src/web/app/mobile/tags/home-timeline.tag
index 051158597d..e96823fa10 100644
--- a/src/web/app/mobile/tags/home-timeline.tag
+++ b/src/web/app/mobile/tags/home-timeline.tag
@@ -12,7 +12,10 @@
 	<script>
 		this.mixin('i');
 		this.mixin('api');
+
 		this.mixin('stream');
+		this.connection = this.stream.getConnection();
+		this.connectionId = this.stream.use();
 
 		this.noFollowing = this.I.following_count == 0;
 
@@ -30,15 +33,16 @@
 		};
 
 		this.on('mount', () => {
-			this.stream.on('post', this.onStreamPost);
-			this.stream.on('follow', this.onStreamFollow);
-			this.stream.on('unfollow', this.onStreamUnfollow);
+			this.connection.on('post', this.onStreamPost);
+			this.connection.on('follow', this.onStreamFollow);
+			this.connection.on('unfollow', this.onStreamUnfollow);
 		});
 
 		this.on('unmount', () => {
-			this.stream.off('post', this.onStreamPost);
-			this.stream.off('follow', this.onStreamFollow);
-			this.stream.off('unfollow', this.onStreamUnfollow);
+			this.connection.off('post', this.onStreamPost);
+			this.connection.off('follow', this.onStreamFollow);
+			this.connection.off('unfollow', this.onStreamUnfollow);
+			this.stream.dispose(this.connectionId);
 		});
 
 		this.more = () => {
diff --git a/src/web/app/mobile/tags/notifications.tag b/src/web/app/mobile/tags/notifications.tag
index 2e95990314..7406fd95e2 100644
--- a/src/web/app/mobile/tags/notifications.tag
+++ b/src/web/app/mobile/tags/notifications.tag
@@ -82,7 +82,10 @@
 		this.getPostSummary = getPostSummary;
 
 		this.mixin('api');
+
 		this.mixin('stream');
+		this.connection = this.stream.getConnection();
+		this.connectionId = this.stream.use();
 
 		this.notifications = [];
 		this.loading = true;
@@ -106,11 +109,12 @@
 				this.trigger('fetched');
 			});
 
-			this.stream.on('notification', this.onNotification);
+			this.connection.on('notification', this.onNotification);
 		});
 
 		this.on('unmount', () => {
-			this.stream.off('notification', this.onNotification);
+			this.connection.off('notification', this.onNotification);
+			this.stream.dispose(this.connectionId);
 		});
 
 		this.on('update', () => {
@@ -124,7 +128,7 @@
 
 		this.onNotification = notification => {
 			// TODO: ユーザーが画面を見てないと思われるとき(ブラウザやタブがアクティブじゃないなど)は送信しない
-			this.stream.send({
+			this.connection.send({
 				type: 'read_notification',
 				id: notification.id
 			});
diff --git a/src/web/app/mobile/tags/page/home.tag b/src/web/app/mobile/tags/page/home.tag
index 3b0255b293..1b2a4b1e13 100644
--- a/src/web/app/mobile/tags/page/home.tag
+++ b/src/web/app/mobile/tags/page/home.tag
@@ -13,7 +13,10 @@
 		import openPostForm from '../../scripts/open-post-form';
 
 		this.mixin('i');
+
 		this.mixin('stream');
+		this.connection = this.stream.getConnection();
+		this.connectionId = this.stream.use();
 
 		this.unreadCount = 0;
 
@@ -28,7 +31,7 @@
 
 			Progress.start();
 
-			this.stream.on('post', this.onStreamPost);
+			this.connection.on('post', this.onStreamPost);
 			document.addEventListener('visibilitychange', this.onVisibilitychange, false);
 
 			this.refs.ui.refs.home.on('loaded', () => {
@@ -37,7 +40,8 @@
 		});
 
 		this.on('unmount', () => {
-			this.stream.off('post', this.onStreamPost);
+			this.connection.off('post', this.onStreamPost);
+			this.stream.dispose(this.connectionId);
 			document.removeEventListener('visibilitychange', this.onVisibilitychange);
 		});
 
diff --git a/src/web/app/mobile/tags/timeline.tag b/src/web/app/mobile/tags/timeline.tag
index 52f6f27ea8..1d6ce23598 100644
--- a/src/web/app/mobile/tags/timeline.tag
+++ b/src/web/app/mobile/tags/timeline.tag
@@ -473,7 +473,10 @@
 
 		this.mixin('i');
 		this.mixin('api');
+
 		this.mixin('stream');
+		this.connection = this.stream.getConnection();
+		this.connectionId = this.stream.use();
 
 		this.set = post => {
 			this.post = post;
@@ -508,21 +511,21 @@
 
 		this.capture = withHandler => {
 			if (this.SIGNIN) {
-				this.stream.send({
+				this.connection.send({
 					type: 'capture',
 					id: this.post.id
 				});
-				if (withHandler) this.stream.on('post-updated', this.onStreamPostUpdated);
+				if (withHandler) this.connection.on('post-updated', this.onStreamPostUpdated);
 			}
 		};
 
 		this.decapture = withHandler => {
 			if (this.SIGNIN) {
-				this.stream.send({
+				this.connection.send({
 					type: 'decapture',
 					id: this.post.id
 				});
-				if (withHandler) this.stream.off('post-updated', this.onStreamPostUpdated);
+				if (withHandler) this.connection.off('post-updated', this.onStreamPostUpdated);
 			}
 		};
 
@@ -530,7 +533,7 @@
 			this.capture(true);
 
 			if (this.SIGNIN) {
-				this.stream.on('_connected_', this.onStreamConnected);
+				this.connection.on('_connected_', this.onStreamConnected);
 			}
 
 			if (this.p.text) {
@@ -555,7 +558,8 @@
 
 		this.on('unmount', () => {
 			this.decapture(true);
-			this.stream.off('_connected_', this.onStreamConnected);
+			this.connection.off('_connected_', this.onStreamConnected);
+			this.stream.dispose(this.connectionId);
 		});
 
 		this.reply = () => {
diff --git a/src/web/app/mobile/tags/ui.tag b/src/web/app/mobile/tags/ui.tag
index b2d96f6b8b..0c969d3902 100644
--- a/src/web/app/mobile/tags/ui.tag
+++ b/src/web/app/mobile/tags/ui.tag
@@ -12,16 +12,20 @@
 	</style>
 	<script>
 		this.mixin('i');
+
 		this.mixin('stream');
+		this.connection = this.stream.getConnection();
+		this.connectionId = this.stream.use();
 
 		this.isDrawerOpening = false;
 
 		this.on('mount', () => {
-			this.stream.on('notification', this.onStreamNotification);
+			this.connection.on('notification', this.onStreamNotification);
 		});
 
 		this.on('unmount', () => {
-			this.stream.off('notification', this.onStreamNotification);
+			this.connection.off('notification', this.onStreamNotification);
+			this.stream.dispose(this.connectionId);
 		});
 
 		this.toggleDrawer = () => {
@@ -31,7 +35,7 @@
 
 		this.onStreamNotification = notification => {
 			// TODO: ユーザーが画面を見てないと思われるとき(ブラウザやタブがアクティブじゃないなど)は送信しない
-			this.stream.send({
+			this.connection.send({
 				type: 'read_notification',
 				id: notification.id
 			});
@@ -145,15 +149,18 @@
 		import ui from '../scripts/ui-event';
 
 		this.mixin('api');
+
 		this.mixin('stream');
+		this.connection = this.stream.getConnection();
+		this.connectionId = this.stream.use();
 
 		this.func = null;
 		this.funcIcon = null;
 
 		this.on('mount', () => {
-			this.stream.on('read_all_notifications', this.onReadAllNotifications);
-			this.stream.on('read_all_messaging_messages', this.onReadAllMessagingMessages);
-			this.stream.on('unread_messaging_message', this.onUnreadMessagingMessage);
+			this.connection.on('read_all_notifications', this.onReadAllNotifications);
+			this.connection.on('read_all_messaging_messages', this.onReadAllMessagingMessages);
+			this.connection.on('unread_messaging_message', this.onUnreadMessagingMessage);
 
 			// Fetch count of unread notifications
 			this.api('notifications/get_unread_count').then(res => {
@@ -175,9 +182,10 @@
 		});
 
 		this.on('unmount', () => {
-			this.stream.off('read_all_notifications', this.onReadAllNotifications);
-			this.stream.off('read_all_messaging_messages', this.onReadAllMessagingMessages);
-			this.stream.off('unread_messaging_message', this.onUnreadMessagingMessage);
+			this.connection.off('read_all_notifications', this.onReadAllNotifications);
+			this.connection.off('read_all_messaging_messages', this.onReadAllMessagingMessages);
+			this.connection.off('unread_messaging_message', this.onUnreadMessagingMessage);
+			this.stream.dispose(this.connectionId);
 
 			ui.off('title', this.setTitle);
 			ui.off('func', this.setFunc);
@@ -348,12 +356,15 @@
 		this.mixin('i');
 		this.mixin('page');
 		this.mixin('api');
+
 		this.mixin('stream');
+		this.connection = this.stream.getConnection();
+		this.connectionId = this.stream.use();
 
 		this.on('mount', () => {
-			this.stream.on('read_all_notifications', this.onReadAllNotifications);
-			this.stream.on('read_all_messaging_messages', this.onReadAllMessagingMessages);
-			this.stream.on('unread_messaging_message', this.onUnreadMessagingMessage);
+			this.connection.on('read_all_notifications', this.onReadAllNotifications);
+			this.connection.on('read_all_messaging_messages', this.onReadAllMessagingMessages);
+			this.connection.on('unread_messaging_message', this.onUnreadMessagingMessage);
 
 			// Fetch count of unread notifications
 			this.api('notifications/get_unread_count').then(res => {
@@ -375,9 +386,10 @@
 		});
 
 		this.on('unmount', () => {
-			this.stream.off('read_all_notifications', this.onReadAllNotifications);
-			this.stream.off('read_all_messaging_messages', this.onReadAllMessagingMessages);
-			this.stream.off('unread_messaging_message', this.onUnreadMessagingMessage);
+			this.connection.off('read_all_notifications', this.onReadAllNotifications);
+			this.connection.off('read_all_messaging_messages', this.onReadAllMessagingMessages);
+			this.connection.off('unread_messaging_message', this.onUnreadMessagingMessage);
+			this.stream.dispose(this.connectionId);
 		});
 
 		this.onReadAllNotifications = () => {
diff --git a/src/web/app/status/tags/index.tag b/src/web/app/status/tags/index.tag
index 8e875daf39..8750942c65 100644
--- a/src/web/app/status/tags/index.tag
+++ b/src/web/app/status/tags/index.tag
@@ -51,7 +51,7 @@
 					color #546567
 	</style>
 	<script>
-		import Connection from '../../common/scripts/server-stream';
+		import Connection from '../../common/scripts/streaming/server-stream';
 
 		this.mixin('api');