Fixed call. Added lobby signals + book selection signals

This commit is contained in:
Sagi Dayan 2020-04-29 22:34:14 -04:00
parent 0e2fa21511
commit cef526205c
18 changed files with 192 additions and 67 deletions

View file

@ -12,6 +12,15 @@ class BookApiController {
code: 404, message: 'no file' code: 404, message: 'no file'
} }
} }
async getThumbnail({request, book, response}) {
const file = await FileUtils.getFile(`books/${book.book_folder}/1.jpg`);
if (file)
response.send(file);
else
return {
code: 404, message: 'no file'
}
}
} }
module.exports = BookApiController module.exports = BookApiController

View file

@ -4,6 +4,7 @@ const User = use('App/Models/User');
const Child = use('App/Models/Child') const Child = use('App/Models/Child')
const Link = use('App/Models/Link'); const Link = use('App/Models/Link');
const Call = use('App/Models/Call'); const Call = use('App/Models/Call');
const Book = use('App/Models/Book');
const FileUtils = use('App/Utils/FileUtils'); const FileUtils = use('App/Utils/FileUtils');
const UserChildUtils = use('App/Utils/UserChildUtils'); const UserChildUtils = use('App/Utils/UserChildUtils');
@ -13,8 +14,14 @@ class ClientApiController {
async getUser({auth}) { async getUser({auth}) {
const user = auth.user.toJSON(); const user = auth.user.toJSON();
const connections = await UserChildUtils.getUserConnections(user.id); const connections = await UserChildUtils.getUserConnections(user.id);
const booksResponse = await Book.query()
.where({user_id: null})
.orWhere({user_id: user.id})
.fetch();
let books = [];
if (booksResponse.rows.length) books = booksResponse.rows;
return { return {
...user, connections: {...connections} ...user, connections: {...connections}, books
} }
} }

View file

@ -112,6 +112,8 @@ class CallSession {
socket.on('wrtc:sdp:answer', this.onSdpAnswer.bind(this)); socket.on('wrtc:sdp:answer', this.onSdpAnswer.bind(this));
socket.on('wrtc:ice', this.onIceCandidate.bind(this)); socket.on('wrtc:ice', this.onIceCandidate.bind(this));
socket.on('call:host:changed', this.onHostChanged.bind(this)); socket.on('call:host:changed', this.onHostChanged.bind(this));
socket.on('call:view:lobby', this.onCallViewLobby.bind(this));
socket.on('call:view:book', this.onCallViewBook.bind(this));
socket.on('book:action:flip-page', this.onActionBookFlip.bind(this)); socket.on('book:action:flip-page', this.onActionBookFlip.bind(this));
await this.updateState(); await this.updateState();
@ -221,6 +223,22 @@ class CallSession {
peerId}`); peerId}`);
return true; return true;
} }
onCallViewLobby(payload) {
const {peerId, userId, direction} = payload;
this.userMap.get(peerId).socket.emit('call:view:lobby', {});
console.log(
`[Signal] [call] [view] [lobby] [${direction}] ${userId} -> ${peerId}`);
return true;
}
onCallViewBook(payload) {
const {peerId, userId, direction, bookId} = payload;
this.userMap.get(peerId).socket.emit('call:view:book', {bookId});
console.log(
`[Signal] [call] [view] [book] [${direction}] ${userId} -> ${peerId}`);
return true;
}
async onHostChanged(payload) { async onHostChanged(payload) {
const {peerId, userId} = payload; const {peerId, userId} = payload;
this.hostId = this.hostId === userId ? peerId : userId; this.hostId = this.hostId === userId ? peerId : userId;

View file

@ -0,0 +1,27 @@
'use strict'
/** @typedef {import('@adonisjs/framework/src/Request')} Request */
/** @typedef {import('@adonisjs/framework/src/Response')} Response */
/** @typedef {import('@adonisjs/framework/src/View')} View */
const Book = use('App/Models/Book');
class BookContext {
/**
* @param {object} ctx
* @param {Request} ctx.request
* @param {Function} next
*/
async handle(ctx, next) {
const {request, auth, response} = ctx;
const bookId = request.params.bookId;
const book = await Book.find(bookId);
if (!book) {
response.status(404);
response.send({code: 404, message: 'Book not found'});
return;
}
ctx.book = book;
// call next to advance the request
await next()
}
}
module.exports = BookContext

View file

@ -11,29 +11,23 @@ class BookPageAuth {
* @param {Function} next * @param {Function} next
*/ */
async handle(ctx, next) { async handle(ctx, next) {
const {request, auth, response} = ctx; const {request, auth, response, book, call} = ctx;
// call next to advance the request // call next to advance the request
const user = auth.user; const user = auth.user;
const bookId = request.params.bookId;
const book = await Book.find(bookId);
if (!book) {
response.status(404);
response.send({code: 404, message: 'Book not found'});
return;
}
ctx.book = book;
if (book.user_id) { if (book.user_id) {
// Belongs to a user. Check if the book user has a connection with this // Belongs to a user. Check if the book user has a connection with this
// user // user
if (book.user_id === user.id) if (book.user_id === user.id) {
await next(); await next();
else { } else if (call.parent_id === user.id || call.guest_id === user.id) {
const ownerConnections = await next();
await UserChildUtils.getUserConnections(book.user_id); } else {
console.log(ownerConnections); response.status(403);
response.send({code: 403, message: 'Book is private'});
} }
} else {
await next();
} }
await next();
} }
} }

View file

@ -0,0 +1,27 @@
'use strict'
/** @typedef {import('@adonisjs/framework/src/Request')} Request */
/** @typedef {import('@adonisjs/framework/src/Response')} Response */
/** @typedef {import('@adonisjs/framework/src/View')} View */
const Call = use('App/Models/Call');
class CallContext {
/**
* @param {object} ctx
* @param {Request} ctx.request
* @param {Function} next
*/
async handle(ctx, next) {
const {request, auth, response} = ctx;
const callId = request.params.callId;
const call = await Call.find(callId);
if (!call) {
response.status(404);
response.send({code: 404, message: 'Call not found'});
return;
}
ctx.call = call;
// call next to advance the request
await next()
}
}
module.exports = CallContext

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -13,7 +13,7 @@ export default class CallManager {
public isHost = false; public isHost = false;
public books = []; public books = [];
public currentActivity = null; public currentActivity = null;
constructor(private ws: WebSocketService, private callId: number, private userId: number) { constructor(private ws: WebSocketService, readonly callId: number, private userId: number) {
this.inCall = false; this.inCall = false;
this.peerId = null; this.peerId = null;
this.pc = null; this.pc = null;
@ -37,6 +37,8 @@ export default class CallManager {
signalingChannel.on('wrtc:ice', self.onRemoteIce.bind(self)); signalingChannel.on('wrtc:ice', self.onRemoteIce.bind(self));
signalingChannel.on('book:action:flip-page', self.onActionBookFlip.bind(self)); signalingChannel.on('book:action:flip-page', self.onActionBookFlip.bind(self));
signalingChannel.on('call:host:changed', self.onRemoteHostChanged.bind(self)); signalingChannel.on('call:host:changed', self.onRemoteHostChanged.bind(self));
signalingChannel.on('call:view:lobby', self.onRemoteViewLobby.bind(self));
signalingChannel.on('call:view:book', self.onRemoteViewBook.bind(self));
signalingChannel.on('error', (e) => { signalingChannel.on('error', (e) => {
console.error(e); console.error(e);
resolve(false) resolve(false)
@ -55,6 +57,7 @@ export default class CallManager {
this.emitter.emit(event, data); this.emitter.emit(event, data);
} }
private send(event: string, payload: { [key: string]: any }) { private send(event: string, payload: { [key: string]: any }) {
console.log(`Sending event: ${event}`);
this.signalingChannel.emit(event, { this.signalingChannel.emit(event, {
userId: this.userId, userId: this.userId,
peerId: this.peerId, peerId: this.peerId,
@ -105,8 +108,14 @@ export default class CallManager {
return true; return true;
} }
public selectBook(index: number) { public selectBook(index: number, remote: boolean) {
this.currentActivity = this.books[index]; this.currentActivity = this.books[index];
console.log('-------> Selected Book ', index, 'bookId:', this.currentActivity.id);
if (!remote) this.send('call:view:book', { bookId: this.currentActivity.id });
}
public backToLobby() {
console.log('-------> backToLobby');
this.send('call:view:lobby', {});
} }
setupPeerConnectionListeners() { setupPeerConnectionListeners() {
@ -189,6 +198,13 @@ export default class CallManager {
this.emit(ECallEvents.CALL_HOST_CHANGED, payload); this.emit(ECallEvents.CALL_HOST_CHANGED, payload);
} }
onRemoteViewLobby(payload) {
this.emit(ECallEvents.CALL_VIEW_LOBBY, null);
}
onRemoteViewBook(payload) {
this.emit(ECallEvents.CALL_VIEW_BOOK, payload);
}
close() { close() {
console.log('Closing...'); console.log('Closing...');
if (!this.inCall) return; if (!this.inCall) return;
@ -209,4 +225,6 @@ export enum ECallEvents {
REMOTE_STREAM = 'REMOTE_STREAM', REMOTE_STREAM = 'REMOTE_STREAM',
ACTION_BOOK_FLIP = 'ACTION_BOOK_FLIP', ACTION_BOOK_FLIP = 'ACTION_BOOK_FLIP',
CALL_HOST_CHANGED = "CALL_HOST_CHANGED", CALL_HOST_CHANGED = "CALL_HOST_CHANGED",
CALL_VIEW_LOBBY = 'CALL_VIEW_LOBBY',
CALL_VIEW_BOOK = 'CALL_VIEW_BOOK',
} }

View file

@ -10,7 +10,7 @@ const store = new Store({
state: { state: {
inCall: false, inCall: false,
callManager: null as CallManager, callManager: null as CallManager,
user: { name: 'loading...', is_admin: false, id: null }, user: { name: 'loading...', is_admin: false, id: null, books: [] },
notifications: [] notifications: []
}, },
getters: { getters: {

View file

@ -53,7 +53,10 @@ export default {
name: "CallBook", name: "CallBook",
components: { Flipbook }, components: { Flipbook },
created() { created() {
this.callManager.on(ECallEvents.ACTION_BOOK_FLIP, this.onRemoteFlip); this.callManager.on(
ECallEvents.ACTION_BOOK_FLIP,
this.onRemoteFlip.bind(this)
);
}, },
data() { data() {
return { return {
@ -66,7 +69,7 @@ export default {
}, },
methods: { methods: {
onFlip(direction: "left" | "right") { onFlip(direction: "left" | "right") {
if (this.isHost) if (this.callManager.isHost)
this.callManager.send(`book:action:flip-page`, { direction }); this.callManager.send(`book:action:flip-page`, { direction });
}, },
onLeftClicked() { onLeftClicked() {
@ -88,7 +91,9 @@ export default {
createPages(book) { createPages(book) {
const pages = [null]; const pages = [null];
for (let i = 1; i < book.pages + 1; i++) { for (let i = 1; i < book.pages + 1; i++) {
pages.push(`/u/books/${book.id}/page/${i}`); pages.push(
`/u/call/${this.callManager.callId}/books/${book.id}/page/${i}`
);
} }
return pages; return pages;
} }

View file

@ -9,12 +9,12 @@
class="book-thumb m-l-md" class="book-thumb m-l-md"
v-for="(book, index) in callManager.books" v-for="(book, index) in callManager.books"
:key="index" :key="index"
@click="goToBook(book, index)" @click="goToBook(book, index, false)"
:disabled="!callManager.isHost" :disabled="!callManager.isHost"
> >
<div class="book-cover"> <div class="book-cover">
<figure class="image is-2by3 m-a"> <figure class="image is-2by3 m-a">
<img :src="`/u/books/${book.id}/page/1`" /> <img :src="`/u/books/${book.id}/thumbnail`" />
</figure> </figure>
</div> </div>
<div class="book-text"> <div class="book-text">
@ -28,12 +28,30 @@
<script lang="ts"> <script lang="ts">
import { mapGetters } from "vuex"; import { mapGetters } from "vuex";
import { ECallEvents } from "../../classes/call.manager";
export default { export default {
name: "CallLobby", name: "CallLobby",
created() {
this.callManager.on(
ECallEvents.CALL_VIEW_BOOK,
this.remoteBookSelected.bind(this)
);
},
methods: { methods: {
goToBook(book, index) { goToBook(book, index, remote) {
this.callManager.selectBook(index); if (remote || this.callManager.isHost) {
this.$router.replace({ name: "book" }); this.callManager.selectBook(index, remote);
this.$router.replace({ name: "book" });
}
},
remoteBookSelected({ bookId }) {
for (let index = 0; index < this.callManager.books.length; index++) {
const book = this.callManager.books[index];
if (book.id === bookId) {
this.goToBook(book, index, true);
return;
}
}
} }
}, },
computed: { computed: {

View file

@ -72,41 +72,19 @@
</div> </div>
</div> </div>
</nav> </nav>
<div class="Games">
<h2 class="subtitle">
<i class="fa fa-fw fa-puzzle-piece"></i> My Games
</h2>
<div class="is-flex m-b-md">
<div class="game m-l-md" v-for="i of [1, 2, 3, 4]" :key="i">
<div class="game-cover">
<figure class="image is-2by3 m-a">
<img
src="https://external-content.duckduckgo.com/iu/?u=http%3A%2F%2Fblog.springshare.com%2Fwp-content%2Fuploads%2F2010%2F02%2Fnc-md.gif&f=1&nofb=1"
/>
</figure>
</div>
<div class="game-text">
<div>Name</div>
<div>Type</div>
</div>
</div>
</div>
</div>
<div class="Books"> <div class="Books">
<h2 class="subtitle"> <h2 class="subtitle">
<i class="fa fa-fw fa-book"></i> My Books <i class="fa fa-fw fa-book"></i> My Books
</h2> </h2>
<div class="is-flex m-b-md"> <div class="is-flex m-b-md">
<div class="book-thumb m-l-md" v-for="i of [1, 2, 3, 4]" :key="i"> <div class="book-thumb m-l-md" v-for="book in user.books" :key="book.id">
<div class="book-cover"> <div class="book-cover">
<figure class="image is-2by3 m-a"> <figure class="image is-2by3 m-a">
<img <img :src="`/u/books/${book.id}/thumbnail`" />
src="https://external-content.duckduckgo.com/iu/?u=http%3A%2F%2Fwww.sylviaday.com%2FWP%2Fwp-content%2Fthemes%2Fsylviaday%2Fimages%2Fcovers%2Ftemp-covers%2Fplaceholder-cover.jpg&f=1&nofb=1"
/>
</figure> </figure>
</div> </div>
<div class="book-text"> <div class="book-text">
<div>book_name</div> <div>{{book.title}}</div>
</div> </div>
</div> </div>
</div> </div>

View file

@ -48,15 +48,16 @@
</router-link> </router-link>
</p> </p>
<p class="control"> <p class="control">
<router-link <button
:to="{path:'..'}"
class="button is-info" class="button is-info"
append append
replace replace
v-if="inCall && $route.name==='book'" v-if="inCall && $route.name==='book'"
:disabled="!callManager.isHost"
@click="backToLobby(true)"
> >
<i class="fa fa-fw fa-arrow-circle-o-left"></i> Back <i class="fa fa-fw fa-arrow-circle-o-left"></i> Back
</router-link> </button>
</p> </p>
</div> </div>
</div> </div>
@ -88,7 +89,10 @@
<script lang="ts"> <script lang="ts">
import { mapGetters, mapActions } from "vuex"; import { mapGetters, mapActions } from "vuex";
import { ECallEvents } from "../../home/classes/call.manager";
export default { export default {
name: "TobNavbar",
created() {},
data() { data() {
return { return {
showMenu: false showMenu: false
@ -97,6 +101,10 @@ export default {
computed: { computed: {
host() { host() {
if (this.inCall) { if (this.inCall) {
this.callManager.on(
ECallEvents.CALL_VIEW_LOBBY,
this.remoteBackToLobby.bind(this)
);
if (this.callManager.isHost) return this.user; if (this.callManager.isHost) return this.user;
return this.callManager.peer; return this.callManager.peer;
} }
@ -108,6 +116,13 @@ export default {
changeHost() { changeHost() {
this.callManager.changeHost(); this.callManager.changeHost();
}, },
backToLobby(payload) {
this.callManager.backToLobby();
this.$router.replace({ path: `/call/${this.callManager.callId}` });
},
remoteBackToLobby(payload) {
this.$router.replace({ path: `/call/${this.callManager.callId}` });
},
...mapActions([]) ...mapActions([])
} }
}; };

View file

@ -42,7 +42,9 @@ const globalMiddleware =
auth: 'Adonis/Middleware/Auth', auth: 'Adonis/Middleware/Auth',
guest: 'Adonis/Middleware/AllowGuestOnly', guest: 'Adonis/Middleware/AllowGuestOnly',
adminAuth: 'App/Middleware/AdminAuth', adminAuth: 'App/Middleware/AdminAuth',
BookPageAuth: 'App/Middleware/BookPageAuth' BookPageAuth: 'App/Middleware/BookPageAuth',
BookContext: 'App/Middleware/BookContext',
CallContext: 'App/Middleware/CallContext',
} }
/* /*

View file

@ -47,9 +47,16 @@ Route
/** /**
* Private book assets * Private book assets
*/ */
Route.get('/u/books/:bookId/page/:pageNumber', 'BookApiController.getPage') Route
.middleware(['auth', 'BookPageAuth']); .get(
'/u/call/:callId/books/:bookId/page/:pageNumber',
'BookApiController.getPage')
.middleware(['auth', 'BookContext', 'CallContext', 'BookPageAuth']);
/**
* Public book thumbnail
*/
Route.get('/u/books/:bookId/thumbnail', 'BookApiController.getThumbnail')
.middleware(['auth', 'BookContext'])
/* /*
/ Pubic CDN Images / Pubic CDN Images
*/ */