diff --git a/src/api/endpoints/othello/match.ts b/src/api/endpoints/othello/match.ts index 640be9cb57..b73e105ef0 100644 --- a/src/api/endpoints/othello/match.ts +++ b/src/api/endpoints/othello/match.ts @@ -2,7 +2,7 @@ import $ from 'cafy'; import Matching, { pack as packMatching } from '../../models/othello-matching'; import Game, { pack as packGame } from '../../models/othello-game'; import User from '../../models/user'; -import { publishOthelloStream } from '../../event'; +import publishUserStream, { publishOthelloStream } from '../../event'; import { eighteight } from '../../../common/othello/maps'; module.exports = (params, user) => new Promise(async (res, rej) => { @@ -48,6 +48,14 @@ module.exports = (params, user) => new Promise(async (res, rej) => { res(await packGame(game, user)); publishOthelloStream(exist.parent_id, 'matched', await packGame(game, exist.parent_id)); + + const other = await Matching.count({ + child_id: user._id + }); + + if (other == 0) { + publishUserStream(user._id, 'othello_no_invites'); + } } else { // Fetch child const child = await User.findOne({ @@ -77,7 +85,11 @@ module.exports = (params, user) => new Promise(async (res, rej) => { // Reponse res(); + const packed = await packMatching(matching, child); + // 招待 - publishOthelloStream(child._id, 'invited', await packMatching(matching, child)); + publishOthelloStream(child._id, 'invited', packed); + + publishUserStream(child._id, 'othello_invited', packed); } }); diff --git a/src/api/models/othello-matching.ts b/src/api/models/othello-matching.ts index 89fcd6df6a..5cc39cae13 100644 --- a/src/api/models/othello-matching.ts +++ b/src/api/models/othello-matching.ts @@ -32,6 +32,8 @@ export const pack = ( const _matching = deepcopy(matching); + // Rename _id to id + _matching.id = _matching._id; delete _matching._id; // Populate user diff --git a/src/api/stream/othello.ts b/src/api/stream/othello.ts index 5056eb535c..bd3b4a7637 100644 --- a/src/api/stream/othello.ts +++ b/src/api/stream/othello.ts @@ -1,5 +1,8 @@ +import * as mongo from 'mongodb'; import * as websocket from 'websocket'; import * as redis from 'redis'; +import Matching, { pack } from '../models/othello-matching'; +import publishUserStream from '../event'; export default function(request: websocket.request, connection: websocket.connection, subscriber: redis.RedisClient, user: any): void { // Subscribe othello stream @@ -7,4 +10,20 @@ export default function(request: websocket.request, connection: websocket.connec subscriber.on('message', (_, data) => { connection.send(data); }); + + connection.on('message', async (data) => { + const msg = JSON.parse(data.utf8Data); + + switch (msg.type) { + case 'ping': + if (msg.id == null) return; + const matching = await Matching.findOne({ + parent_id: user._id, + child_id: new mongo.ObjectID(msg.id) + }); + if (matching == null) return; + publishUserStream(matching.child_id, 'othello_invited', await pack(matching, matching.child_id)); + break; + } + }); } diff --git a/src/web/app/common/views/components/othello.vue b/src/web/app/common/views/components/othello.vue index 81da02d1c4..d650322341 100644 --- a/src/web/app/common/views/components/othello.vue +++ b/src/web/app/common/views/components/othello.vue @@ -78,7 +78,8 @@ export default Vue.extend({ matching: null, invitations: [], connection: null, - connectionId: null + connectionId: null, + pingClock: null }; }, watch: { @@ -112,17 +113,29 @@ export default Vue.extend({ (this as any).api('othello/invitations').then(invitations => { this.invitations = this.invitations.concat(invitations); }); + + this.pingClock = setInterval(() => { + if (this.matching) { + this.connection.send({ + type: 'ping', + id: this.matching.id + }); + } + }, 3000); }, beforeDestroy() { this.connection.off('matched', this.onMatched); this.connection.off('invited', this.onInvited); (this as any).os.streams.othelloStream.dispose(this.connectionId); + + clearInterval(this.pingClock); }, methods: { go(game) { (this as any).api('othello/games/show', { game_id: game.id }).then(game => { + this.matching = null; this.game = game; }); }, @@ -154,11 +167,13 @@ export default Vue.extend({ user_id: invitation.parent.id }).then(game => { if (game) { + this.matching = null; this.game = game; } }); }, onMatched(game) { + this.matching = null; this.game = game; }, onInvited(invite) { diff --git a/src/web/app/desktop/views/components/ui.header.nav.vue b/src/web/app/desktop/views/components/ui.header.nav.vue index 54045db8d4..7582e8afce 100644 --- a/src/web/app/desktop/views/components/ui.header.nav.vue +++ b/src/web/app/desktop/views/components/ui.header.nav.vue @@ -56,6 +56,8 @@ export default Vue.extend({ this.connection.on('read_all_messaging_messages', this.onReadAllMessagingMessages); this.connection.on('unread_messaging_message', this.onUnreadMessagingMessage); + this.connection.on('othello_invited', this.onOthelloInvited); + this.connection.on('othello_no_invites', this.onOthelloNoInvites); // Fetch count of unread messaging messages (this as any).api('messaging/unread').then(res => { @@ -69,16 +71,26 @@ export default Vue.extend({ if ((this as any).os.isSignedIn) { this.connection.off('read_all_messaging_messages', this.onReadAllMessagingMessages); this.connection.off('unread_messaging_message', this.onUnreadMessagingMessage); + this.connection.off('othello_invited', this.onOthelloInvited); + this.connection.off('othello_no_invites', this.onOthelloNoInvites); (this as any).os.stream.dispose(this.connectionId); } }, methods: { + onUnreadMessagingMessage() { + this.hasUnreadMessagingMessages = true; + }, + onReadAllMessagingMessages() { this.hasUnreadMessagingMessages = false; }, - onUnreadMessagingMessage() { - this.hasUnreadMessagingMessages = true; + onOthelloInvited() { + this.hasGameInvitations = true; + }, + + onOthelloNoInvites() { + this.hasGameInvitations = false; }, messaging() { diff --git a/src/web/app/mobile/views/components/ui.header.vue b/src/web/app/mobile/views/components/ui.header.vue index f06a35fe76..1ccbd5c951 100644 --- a/src/web/app/mobile/views/components/ui.header.vue +++ b/src/web/app/mobile/views/components/ui.header.vue @@ -6,7 +6,7 @@

おかえりなさい、{{ os.i.name }}さん

- +

Misskey

@@ -26,6 +26,7 @@ export default Vue.extend({ return { hasUnreadNotifications: false, hasUnreadMessagingMessages: false, + hasGameInvitations: false, connection: null, connectionId: null }; @@ -39,6 +40,8 @@ export default Vue.extend({ this.connection.on('unread_notification', this.onUnreadNotification); this.connection.on('read_all_messaging_messages', this.onReadAllMessagingMessages); this.connection.on('unread_messaging_message', this.onUnreadMessagingMessage); + this.connection.on('othello_invited', this.onOthelloInvited); + this.connection.on('othello_no_invites', this.onOthelloNoInvites); // Fetch count of unread notifications (this as any).api('notifications/get_unread_count').then(res => { @@ -107,6 +110,8 @@ export default Vue.extend({ this.connection.off('unread_notification', this.onUnreadNotification); this.connection.off('read_all_messaging_messages', this.onReadAllMessagingMessages); this.connection.off('unread_messaging_message', this.onUnreadMessagingMessage); + this.connection.off('othello_invited', this.onOthelloInvited); + this.connection.off('othello_no_invites', this.onOthelloNoInvites); (this as any).os.stream.dispose(this.connectionId); } }, @@ -122,6 +127,12 @@ export default Vue.extend({ }, onUnreadMessagingMessage() { this.hasUnreadMessagingMessages = true; + }, + onOthelloInvited() { + this.hasGameInvitations = true; + }, + onOthelloNoInvites() { + this.hasGameInvitations = false; } } }); diff --git a/src/web/app/mobile/views/components/ui.nav.vue b/src/web/app/mobile/views/components/ui.nav.vue index ba35a2783d..b8bc2fb040 100644 --- a/src/web/app/mobile/views/components/ui.nav.vue +++ b/src/web/app/mobile/views/components/ui.nav.vue @@ -18,7 +18,7 @@
  • %fa:home%%i18n:mobile.tags.mk-ui-nav.home%%fa:angle-right%
  • %fa:R bell%%i18n:mobile.tags.mk-ui-nav.notifications%%fa:angle-right%
  • %fa:R comments%%i18n:mobile.tags.mk-ui-nav.messaging%%fa:angle-right%
  • -
  • %fa:gamepad%ゲーム%fa:angle-right%
  • +
  • %fa:gamepad%ゲーム%fa:angle-right%