summaryrefslogtreecommitdiff
path: root/src/client/app/common/scripts
diff options
context:
space:
mode:
authorsyuilo <syuilotan@yahoo.co.jp>2018-03-29 20:32:18 +0900
committersyuilo <syuilotan@yahoo.co.jp>2018-03-29 20:32:18 +0900
commitcf33e483f7e6f40e8cbbbc0118a7df70bdaf651f (patch)
tree318279530d3392ee40d91968477fc0e78d5cf0f7 /src/client/app/common/scripts
parentUpdate .travis.yml (diff)
downloadsharkey-cf33e483f7e6f40e8cbbbc0118a7df70bdaf651f.tar.gz
sharkey-cf33e483f7e6f40e8cbbbc0118a7df70bdaf651f.tar.bz2
sharkey-cf33e483f7e6f40e8cbbbc0118a7df70bdaf651f.zip
整理した
Diffstat (limited to 'src/client/app/common/scripts')
-rw-r--r--src/client/app/common/scripts/check-for-update.ts33
-rw-r--r--src/client/app/common/scripts/compose-notification.ts67
-rw-r--r--src/client/app/common/scripts/contains.ts8
-rw-r--r--src/client/app/common/scripts/copy-to-clipboard.ts13
-rw-r--r--src/client/app/common/scripts/date-stringify.ts13
-rw-r--r--src/client/app/common/scripts/fuck-ad-block.ts21
-rw-r--r--src/client/app/common/scripts/gcd.ts2
-rw-r--r--src/client/app/common/scripts/get-kao.ts5
-rw-r--r--src/client/app/common/scripts/get-median.ts11
-rw-r--r--src/client/app/common/scripts/loading.ts21
-rw-r--r--src/client/app/common/scripts/parse-search-query.ts53
-rw-r--r--src/client/app/common/scripts/streaming/channel.ts13
-rw-r--r--src/client/app/common/scripts/streaming/drive.ts34
-rw-r--r--src/client/app/common/scripts/streaming/home.ts57
-rw-r--r--src/client/app/common/scripts/streaming/messaging-index.ts34
-rw-r--r--src/client/app/common/scripts/streaming/messaging.ts20
-rw-r--r--src/client/app/common/scripts/streaming/othello-game.ts11
-rw-r--r--src/client/app/common/scripts/streaming/othello.ts31
-rw-r--r--src/client/app/common/scripts/streaming/requests.ts30
-rw-r--r--src/client/app/common/scripts/streaming/server.ts30
-rw-r--r--src/client/app/common/scripts/streaming/stream-manager.ts108
-rw-r--r--src/client/app/common/scripts/streaming/stream.ts137
22 files changed, 752 insertions, 0 deletions
diff --git a/src/client/app/common/scripts/check-for-update.ts b/src/client/app/common/scripts/check-for-update.ts
new file mode 100644
index 0000000000..81c1eb9812
--- /dev/null
+++ b/src/client/app/common/scripts/check-for-update.ts
@@ -0,0 +1,33 @@
+import MiOS from '../mios';
+import { version as current } from '../../config';
+
+export default async function(mios: MiOS, force = false, silent = false) {
+ const meta = await mios.getMeta(force);
+ const newer = meta.version;
+
+ if (newer != current) {
+ localStorage.setItem('should-refresh', 'true');
+ localStorage.setItem('v', newer);
+
+ // Clear cache (serive worker)
+ try {
+ if (navigator.serviceWorker.controller) {
+ navigator.serviceWorker.controller.postMessage('clear');
+ }
+
+ navigator.serviceWorker.getRegistrations().then(registrations => {
+ registrations.forEach(registration => registration.unregister());
+ });
+ } catch (e) {
+ console.error(e);
+ }
+
+ if (!silent) {
+ alert('%i18n:common.update-available%'.replace('{newer}', newer).replace('{current}', current));
+ }
+
+ return newer;
+ } else {
+ return null;
+ }
+}
diff --git a/src/client/app/common/scripts/compose-notification.ts b/src/client/app/common/scripts/compose-notification.ts
new file mode 100644
index 0000000000..273579cbc6
--- /dev/null
+++ b/src/client/app/common/scripts/compose-notification.ts
@@ -0,0 +1,67 @@
+import getPostSummary from '../../../../common/get-post-summary';
+import getReactionEmoji from '../../../../common/get-reaction-emoji';
+
+type Notification = {
+ title: string;
+ body: string;
+ icon: string;
+ onclick?: any;
+};
+
+// TODO: i18n
+
+export default function(type, data): Notification {
+ switch (type) {
+ case 'drive_file_created':
+ return {
+ title: 'ファイルがアップロードされました',
+ body: data.name,
+ icon: data.url + '?thumbnail&size=64'
+ };
+
+ case 'mention':
+ return {
+ title: `${data.user.name}さんから:`,
+ body: getPostSummary(data),
+ icon: data.user.avatarUrl + '?thumbnail&size=64'
+ };
+
+ case 'reply':
+ return {
+ title: `${data.user.name}さんから返信:`,
+ body: getPostSummary(data),
+ icon: data.user.avatarUrl + '?thumbnail&size=64'
+ };
+
+ case 'quote':
+ return {
+ title: `${data.user.name}さんが引用:`,
+ body: getPostSummary(data),
+ icon: data.user.avatarUrl + '?thumbnail&size=64'
+ };
+
+ case 'reaction':
+ return {
+ title: `${data.user.name}: ${getReactionEmoji(data.reaction)}:`,
+ body: getPostSummary(data.post),
+ icon: data.user.avatarUrl + '?thumbnail&size=64'
+ };
+
+ case 'unread_messaging_message':
+ return {
+ title: `${data.user.name}さんからメッセージ:`,
+ body: data.text, // TODO: getMessagingMessageSummary(data),
+ icon: data.user.avatarUrl + '?thumbnail&size=64'
+ };
+
+ case 'othello_invited':
+ return {
+ title: '対局への招待があります',
+ body: `${data.parent.name}さんから`,
+ icon: data.parent.avatarUrl + '?thumbnail&size=64'
+ };
+
+ default:
+ return null;
+ }
+}
diff --git a/src/client/app/common/scripts/contains.ts b/src/client/app/common/scripts/contains.ts
new file mode 100644
index 0000000000..a5071b3f25
--- /dev/null
+++ b/src/client/app/common/scripts/contains.ts
@@ -0,0 +1,8 @@
+export default (parent, child) => {
+ let node = child.parentNode;
+ while (node) {
+ if (node == parent) return true;
+ node = node.parentNode;
+ }
+ return false;
+};
diff --git a/src/client/app/common/scripts/copy-to-clipboard.ts b/src/client/app/common/scripts/copy-to-clipboard.ts
new file mode 100644
index 0000000000..3d2741f8d7
--- /dev/null
+++ b/src/client/app/common/scripts/copy-to-clipboard.ts
@@ -0,0 +1,13 @@
+/**
+ * Clipboardに値をコピー(TODO: 文字列以外も対応)
+ */
+export default val => {
+ const form = document.createElement('textarea');
+ form.textContent = val;
+ document.body.appendChild(form);
+ form.select();
+ const result = document.execCommand('copy');
+ document.body.removeChild(form);
+
+ return result;
+};
diff --git a/src/client/app/common/scripts/date-stringify.ts b/src/client/app/common/scripts/date-stringify.ts
new file mode 100644
index 0000000000..e51de8833d
--- /dev/null
+++ b/src/client/app/common/scripts/date-stringify.ts
@@ -0,0 +1,13 @@
+export default date => {
+ if (typeof date == 'string') date = new Date(date);
+ return (
+ date.getFullYear() + '年' +
+ (date.getMonth() + 1) + '月' +
+ date.getDate() + '日' +
+ ' ' +
+ date.getHours() + '時' +
+ date.getMinutes() + '分' +
+ ' ' +
+ `(${['日', '月', '火', '水', '木', '金', '土'][date.getDay()]})`
+ );
+};
diff --git a/src/client/app/common/scripts/fuck-ad-block.ts b/src/client/app/common/scripts/fuck-ad-block.ts
new file mode 100644
index 0000000000..9bcf7deeff
--- /dev/null
+++ b/src/client/app/common/scripts/fuck-ad-block.ts
@@ -0,0 +1,21 @@
+require('fuckadblock');
+
+declare const fuckAdBlock: any;
+
+export default (os) => {
+ function adBlockDetected() {
+ os.apis.dialog({
+ title: '%fa:exclamation-triangle%広告ブロッカーを無効にしてください',
+ text: '<strong>Misskeyは広告を掲載していません</strong>が、広告をブロックする機能が有効だと一部の機能が利用できなかったり、不具合が発生する場合があります。',
+ actins: [{
+ text: 'OK'
+ }]
+ });
+ }
+
+ if (fuckAdBlock === undefined) {
+ adBlockDetected();
+ } else {
+ fuckAdBlock.onDetected(adBlockDetected);
+ }
+};
diff --git a/src/client/app/common/scripts/gcd.ts b/src/client/app/common/scripts/gcd.ts
new file mode 100644
index 0000000000..9a19f9da66
--- /dev/null
+++ b/src/client/app/common/scripts/gcd.ts
@@ -0,0 +1,2 @@
+const gcd = (a, b) => !b ? a : gcd(b, a % b);
+export default gcd;
diff --git a/src/client/app/common/scripts/get-kao.ts b/src/client/app/common/scripts/get-kao.ts
new file mode 100644
index 0000000000..2168c5be88
--- /dev/null
+++ b/src/client/app/common/scripts/get-kao.ts
@@ -0,0 +1,5 @@
+export default () => [
+ '(=^・・^=)',
+ 'v(‘ω’)v',
+ '🐡( \'-\' 🐡 )フグパンチ!!!!'
+][Math.floor(Math.random() * 3)];
diff --git a/src/client/app/common/scripts/get-median.ts b/src/client/app/common/scripts/get-median.ts
new file mode 100644
index 0000000000..91a415d5b2
--- /dev/null
+++ b/src/client/app/common/scripts/get-median.ts
@@ -0,0 +1,11 @@
+/**
+ * 中央値を求めます
+ * @param samples サンプル
+ */
+export default function(samples) {
+ if (!samples.length) return 0;
+ const numbers = samples.slice(0).sort((a, b) => a - b);
+ const middle = Math.floor(numbers.length / 2);
+ const isEven = numbers.length % 2 === 0;
+ return isEven ? (numbers[middle] + numbers[middle - 1]) / 2 : numbers[middle];
+}
diff --git a/src/client/app/common/scripts/loading.ts b/src/client/app/common/scripts/loading.ts
new file mode 100644
index 0000000000..c48e626648
--- /dev/null
+++ b/src/client/app/common/scripts/loading.ts
@@ -0,0 +1,21 @@
+const NProgress = require('nprogress');
+NProgress.configure({
+ trickleSpeed: 500,
+ showSpinner: false
+});
+
+const root = document.getElementsByTagName('html')[0];
+
+export default {
+ start: () => {
+ root.classList.add('progress');
+ NProgress.start();
+ },
+ done: () => {
+ root.classList.remove('progress');
+ NProgress.done();
+ },
+ set: val => {
+ NProgress.set(val);
+ }
+};
diff --git a/src/client/app/common/scripts/parse-search-query.ts b/src/client/app/common/scripts/parse-search-query.ts
new file mode 100644
index 0000000000..4f09d2b93f
--- /dev/null
+++ b/src/client/app/common/scripts/parse-search-query.ts
@@ -0,0 +1,53 @@
+export default function(qs: string) {
+ const q = {
+ text: ''
+ };
+
+ qs.split(' ').forEach(x => {
+ if (/^([a-z_]+?):(.+?)$/.test(x)) {
+ const [key, value] = x.split(':');
+ switch (key) {
+ case 'user':
+ q['includeUserUsernames'] = value.split(',');
+ break;
+ case 'exclude_user':
+ q['excludeUserUsernames'] = value.split(',');
+ break;
+ case 'follow':
+ q['following'] = value == 'null' ? null : value == 'true';
+ break;
+ case 'reply':
+ q['reply'] = value == 'null' ? null : value == 'true';
+ break;
+ case 'repost':
+ q['repost'] = value == 'null' ? null : value == 'true';
+ break;
+ case 'media':
+ q['media'] = value == 'null' ? null : value == 'true';
+ break;
+ case 'poll':
+ q['poll'] = value == 'null' ? null : value == 'true';
+ break;
+ case 'until':
+ case 'since':
+ // YYYY-MM-DD
+ if (/^[0-9]+\-[0-9]+\-[0-9]+$/) {
+ const [yyyy, mm, dd] = value.split('-');
+ q[`${key}_date`] = (new Date(parseInt(yyyy, 10), parseInt(mm, 10) - 1, parseInt(dd, 10))).getTime();
+ }
+ break;
+ default:
+ q[key] = value;
+ break;
+ }
+ } else {
+ q.text += x + ' ';
+ }
+ });
+
+ if (q.text) {
+ q.text = q.text.trim();
+ }
+
+ return q;
+}
diff --git a/src/client/app/common/scripts/streaming/channel.ts b/src/client/app/common/scripts/streaming/channel.ts
new file mode 100644
index 0000000000..cab5f4edb4
--- /dev/null
+++ b/src/client/app/common/scripts/streaming/channel.ts
@@ -0,0 +1,13 @@
+import Stream from './stream';
+import MiOS from '../../mios';
+
+/**
+ * Channel stream connection
+ */
+export default class Connection extends Stream {
+ constructor(os: MiOS, channelId) {
+ super(os, 'channel', {
+ channel: channelId
+ });
+ }
+}
diff --git a/src/client/app/common/scripts/streaming/drive.ts b/src/client/app/common/scripts/streaming/drive.ts
new file mode 100644
index 0000000000..f11573685e
--- /dev/null
+++ b/src/client/app/common/scripts/streaming/drive.ts
@@ -0,0 +1,34 @@
+import Stream from './stream';
+import StreamManager from './stream-manager';
+import MiOS from '../../mios';
+
+/**
+ * Drive stream connection
+ */
+export class DriveStream extends Stream {
+ constructor(os: MiOS, me) {
+ super(os, 'drive', {
+ i: me.account.token
+ });
+ }
+}
+
+export class DriveStreamManager extends StreamManager<DriveStream> {
+ private me;
+ private os: MiOS;
+
+ constructor(os: MiOS, me) {
+ super();
+
+ this.me = me;
+ this.os = os;
+ }
+
+ public getConnection() {
+ if (this.connection == null) {
+ this.connection = new DriveStream(this.os, this.me);
+ }
+
+ return this.connection;
+ }
+}
diff --git a/src/client/app/common/scripts/streaming/home.ts b/src/client/app/common/scripts/streaming/home.ts
new file mode 100644
index 0000000000..c198619400
--- /dev/null
+++ b/src/client/app/common/scripts/streaming/home.ts
@@ -0,0 +1,57 @@
+import * as merge from 'object-assign-deep';
+
+import Stream from './stream';
+import StreamManager from './stream-manager';
+import MiOS from '../../mios';
+
+/**
+ * Home stream connection
+ */
+export class HomeStream extends Stream {
+ constructor(os: MiOS, me) {
+ super(os, '', {
+ i: me.account.token
+ });
+
+ // 最終利用日時を更新するため定期的にaliveメッセージを送信
+ setInterval(() => {
+ this.send({ type: 'alive' });
+ me.account.lastUsedAt = new Date();
+ }, 1000 * 60);
+
+ // 自分の情報が更新されたとき
+ this.on('i_updated', i => {
+ if (os.debug) {
+ console.log('I updated:', i);
+ }
+ merge(me, i);
+ });
+
+ // トークンが再生成されたとき
+ // このままではAPIが利用できないので強制的にサインアウトさせる
+ this.on('my_token_regenerated', () => {
+ alert('%i18n:common.my-token-regenerated%');
+ os.signout();
+ });
+ }
+}
+
+export class HomeStreamManager extends StreamManager<HomeStream> {
+ private me;
+ private os: MiOS;
+
+ constructor(os: MiOS, me) {
+ super();
+
+ this.me = me;
+ this.os = os;
+ }
+
+ public getConnection() {
+ if (this.connection == null) {
+ this.connection = new HomeStream(this.os, this.me);
+ }
+
+ return this.connection;
+ }
+}
diff --git a/src/client/app/common/scripts/streaming/messaging-index.ts b/src/client/app/common/scripts/streaming/messaging-index.ts
new file mode 100644
index 0000000000..24f0ce0c9f
--- /dev/null
+++ b/src/client/app/common/scripts/streaming/messaging-index.ts
@@ -0,0 +1,34 @@
+import Stream from './stream';
+import StreamManager from './stream-manager';
+import MiOS from '../../mios';
+
+/**
+ * Messaging index stream connection
+ */
+export class MessagingIndexStream extends Stream {
+ constructor(os: MiOS, me) {
+ super(os, 'messaging-index', {
+ i: me.account.token
+ });
+ }
+}
+
+export class MessagingIndexStreamManager extends StreamManager<MessagingIndexStream> {
+ private me;
+ private os: MiOS;
+
+ constructor(os: MiOS, me) {
+ super();
+
+ this.me = me;
+ this.os = os;
+ }
+
+ public getConnection() {
+ if (this.connection == null) {
+ this.connection = new MessagingIndexStream(this.os, this.me);
+ }
+
+ return this.connection;
+ }
+}
diff --git a/src/client/app/common/scripts/streaming/messaging.ts b/src/client/app/common/scripts/streaming/messaging.ts
new file mode 100644
index 0000000000..4c593deb31
--- /dev/null
+++ b/src/client/app/common/scripts/streaming/messaging.ts
@@ -0,0 +1,20 @@
+import Stream from './stream';
+import MiOS from '../../mios';
+
+/**
+ * Messaging stream connection
+ */
+export class MessagingStream extends Stream {
+ constructor(os: MiOS, me, otherparty) {
+ super(os, 'messaging', {
+ i: me.account.token,
+ otherparty
+ });
+
+ (this as any).on('_connected_', () => {
+ this.send({
+ i: me.account.token
+ });
+ });
+ }
+}
diff --git a/src/client/app/common/scripts/streaming/othello-game.ts b/src/client/app/common/scripts/streaming/othello-game.ts
new file mode 100644
index 0000000000..f34ef35147
--- /dev/null
+++ b/src/client/app/common/scripts/streaming/othello-game.ts
@@ -0,0 +1,11 @@
+import Stream from './stream';
+import MiOS from '../../mios';
+
+export class OthelloGameStream extends Stream {
+ constructor(os: MiOS, me, game) {
+ super(os, 'othello-game', {
+ i: me ? me.account.token : null,
+ game: game.id
+ });
+ }
+}
diff --git a/src/client/app/common/scripts/streaming/othello.ts b/src/client/app/common/scripts/streaming/othello.ts
new file mode 100644
index 0000000000..8c6f4b9c3c
--- /dev/null
+++ b/src/client/app/common/scripts/streaming/othello.ts
@@ -0,0 +1,31 @@
+import StreamManager from './stream-manager';
+import Stream from './stream';
+import MiOS from '../../mios';
+
+export class OthelloStream extends Stream {
+ constructor(os: MiOS, me) {
+ super(os, 'othello', {
+ i: me.account.token
+ });
+ }
+}
+
+export class OthelloStreamManager extends StreamManager<OthelloStream> {
+ private me;
+ private os: MiOS;
+
+ constructor(os: MiOS, me) {
+ super();
+
+ this.me = me;
+ this.os = os;
+ }
+
+ public getConnection() {
+ if (this.connection == null) {
+ this.connection = new OthelloStream(this.os, this.me);
+ }
+
+ return this.connection;
+ }
+}
diff --git a/src/client/app/common/scripts/streaming/requests.ts b/src/client/app/common/scripts/streaming/requests.ts
new file mode 100644
index 0000000000..5bec30143f
--- /dev/null
+++ b/src/client/app/common/scripts/streaming/requests.ts
@@ -0,0 +1,30 @@
+import Stream from './stream';
+import StreamManager from './stream-manager';
+import MiOS from '../../mios';
+
+/**
+ * Requests stream connection
+ */
+export class RequestsStream extends Stream {
+ constructor(os: MiOS) {
+ super(os, 'requests');
+ }
+}
+
+export class RequestsStreamManager extends StreamManager<RequestsStream> {
+ private os: MiOS;
+
+ constructor(os: MiOS) {
+ super();
+
+ this.os = os;
+ }
+
+ public getConnection() {
+ if (this.connection == null) {
+ this.connection = new RequestsStream(this.os);
+ }
+
+ return this.connection;
+ }
+}
diff --git a/src/client/app/common/scripts/streaming/server.ts b/src/client/app/common/scripts/streaming/server.ts
new file mode 100644
index 0000000000..3d35ef4d9d
--- /dev/null
+++ b/src/client/app/common/scripts/streaming/server.ts
@@ -0,0 +1,30 @@
+import Stream from './stream';
+import StreamManager from './stream-manager';
+import MiOS from '../../mios';
+
+/**
+ * Server stream connection
+ */
+export class ServerStream extends Stream {
+ constructor(os: MiOS) {
+ super(os, 'server');
+ }
+}
+
+export class ServerStreamManager extends StreamManager<ServerStream> {
+ private os: MiOS;
+
+ constructor(os: MiOS) {
+ super();
+
+ this.os = os;
+ }
+
+ public getConnection() {
+ if (this.connection == null) {
+ this.connection = new ServerStream(this.os);
+ }
+
+ return this.connection;
+ }
+}
diff --git a/src/client/app/common/scripts/streaming/stream-manager.ts b/src/client/app/common/scripts/streaming/stream-manager.ts
new file mode 100644
index 0000000000..568b8b0372
--- /dev/null
+++ b/src/client/app/common/scripts/streaming/stream-manager.ts
@@ -0,0 +1,108 @@
+import { EventEmitter } from 'eventemitter3';
+import * as uuid from 'uuid';
+import Connection from './stream';
+
+/**
+ * ストリーム接続を管理するクラス
+ * 複数の場所から同じストリームを利用する際、接続をまとめたりする
+ */
+export default abstract class StreamManager<T extends Connection> extends EventEmitter {
+ private _connection: T = null;
+
+ private disposeTimerId: any;
+
+ /**
+ * コネクションを必要としているユーザー
+ */
+ private users = [];
+
+ protected set connection(connection: T) {
+ this._connection = connection;
+
+ if (this._connection == null) {
+ this.emit('disconnected');
+ } else {
+ this.emit('connected', this._connection);
+
+ this._connection.on('_connected_', () => {
+ this.emit('_connected_');
+ });
+
+ this._connection.on('_disconnected_', () => {
+ this.emit('_disconnected_');
+ });
+
+ this._connection.user = 'Managed';
+ }
+ }
+
+ protected get connection() {
+ return this._connection;
+ }
+
+ /**
+ * コネクションを持っているか否か
+ */
+ public get hasConnection() {
+ return this._connection != null;
+ }
+
+ public get state(): string {
+ if (!this.hasConnection) return 'no-connection';
+ return this._connection.state;
+ }
+
+ /**
+ * コネクションを要求します
+ */
+ public abstract getConnection(): T;
+
+ /**
+ * 現在接続しているコネクションを取得します
+ */
+ public borrow() {
+ return this._connection;
+ }
+
+ /**
+ * コネクションを要求するためのユーザーIDを発行します
+ */
+ public use() {
+ // タイマー解除
+ if (this.disposeTimerId) {
+ clearTimeout(this.disposeTimerId);
+ this.disposeTimerId = null;
+ }
+
+ // ユーザーID生成
+ const userId = uuid();
+
+ this.users.push(userId);
+
+ this._connection.user = `Managed (${ this.users.length })`;
+
+ return userId;
+ }
+
+ /**
+ * コネクションを利用し終わってもう必要ないことを通知します
+ * @param userId use で発行したユーザーID
+ */
+ public dispose(userId) {
+ this.users = this.users.filter(id => id != userId);
+
+ this._connection.user = `Managed (${ this.users.length })`;
+
+ // 誰もコネクションの利用者がいなくなったら
+ if (this.users.length == 0) {
+ // また直ぐに再利用される可能性があるので、一定時間待ち、
+ // 新たな利用者が現れなければコネクションを切断する
+ this.disposeTimerId = setTimeout(() => {
+ this.disposeTimerId = null;
+
+ this.connection.close();
+ this.connection = null;
+ }, 3000);
+ }
+ }
+}
diff --git a/src/client/app/common/scripts/streaming/stream.ts b/src/client/app/common/scripts/streaming/stream.ts
new file mode 100644
index 0000000000..3912186ad3
--- /dev/null
+++ b/src/client/app/common/scripts/streaming/stream.ts
@@ -0,0 +1,137 @@
+import { EventEmitter } from 'eventemitter3';
+import * as uuid from 'uuid';
+import * as ReconnectingWebsocket from 'reconnecting-websocket';
+import { wsUrl } from '../../../config';
+import MiOS from '../../mios';
+
+/**
+ * Misskey stream connection
+ */
+export default class Connection extends EventEmitter {
+ public state: string;
+ private buffer: any[];
+ 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;
+ public isSuspended = false;
+ private os: MiOS;
+
+ constructor(os: MiOS, endpoint, params?) {
+ 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);
+ //#endregion
+
+ this.id = uuid();
+ this.os = os;
+ this.name = endpoint;
+ this.state = 'initializing';
+ this.buffer = [];
+
+ const query = params
+ ? Object.keys(params)
+ .map(k => encodeURIComponent(k) + '=' + encodeURIComponent(params[k]))
+ .join('&')
+ : null;
+
+ this.socket = new ReconnectingWebsocket(`${wsUrl}/${endpoint}${query ? '?' + query : ''}`);
+ 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);
+ }
+
+ /**
+ * Callback of when open connection
+ */
+ private onOpen() {
+ this.state = 'connected';
+ this.emit('_connected_');
+
+ this.connectedAt = new Date();
+
+ // バッファーを処理
+ const _buffer = [].concat(this.buffer); // Shallow copy
+ this.buffer = []; // Clear buffer
+ _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 });
+ }
+ });
+ }
+
+ /**
+ * Callback of when close connection
+ */
+ private onClose() {
+ this.state = 'reconnecting';
+ this.emit('_disconnected_');
+ }
+
+ /**
+ * Callback of when received a message from connection
+ */
+ private onMessage(message) {
+ if (this.isSuspended) return;
+
+ 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);
+ } catch (e) {
+ // noop
+ }
+ }
+
+ /**
+ * Send a message to connection
+ */
+ public send(data) {
+ if (this.isSuspended) return;
+
+ // まだ接続が確立されていなかったらバッファリングして次に接続した時に送信する
+ if (this.state != 'connected') {
+ this.buffer.push(data);
+ return;
+ }
+
+ 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);
+ }
+}