DB changes and namings. mainly to start working on the activity and call/sync. Still WIP

This commit is contained in:
Sagi Dayan 2020-04-27 23:04:09 -04:00
parent 42a7f567c8
commit 04d7211de1
16 changed files with 156 additions and 102 deletions

View file

@ -76,8 +76,8 @@ class ClientApiController {
}
const call = await Call.create({
state: 'NEW',
user_1: user.id,
user_2: body.connection_id,
parent_id: user.id,
guest_id: body.connection_id,
child_id: body.child_id
});
return {

View file

@ -49,11 +49,23 @@ class CallSession {
this.callModel = callModel;
this.hostId = 2;
this.state = callModel.state;
this.user_1 = {id: callModel.user_1, socket: null, userModel: null};
this.user_2 = {id: callModel.user_2, socket: null, userModel: null};
this.sessionState = {page: 'lobby', activity: {type: null, model: null}};
this.parent = {
id: callModel.parent_id,
socket: null,
userModel: null,
isParent: true
};
this.guest = {
id: callModel.guest_id,
socket: null,
userModel: null,
isParent: false
};
this.startTime = Date.now();
this.heartbeat =
setInterval(this.onHeartbeat.bind(this), 1000); // Every second
this.userMap = new Map(); // Reference to this.parent/guest by userId;
}
onHeartbeat() {
const now = Date.now();
@ -69,51 +81,50 @@ class CallSession {
return this.endCall();
}
removeUser(user) {
let userIndex = -1;
if (this.user_1.id === user.id)
userIndex = 1;
else if (this.user_2.id === user.id)
userIndex = 2;
if (userIndex < 0) return false;
this[`user_${userIndex}`].userModel = null;
this[`user_${userIndex}`].socket = null;
let userToRemove = this.userMap.get(user.id);
userToRemove.userModel = null;
userToRemove.socket = null;
this.userMap.delete(user.id);
this.updateState();
if (this.state === 'ENDED') this.endCall();
}
async registerUser(user, socket) {
if (!this.child) this.child = await Child.find(this.callModel.child_id);
let userIndex = -1;
if (this.user_1.id === user.id)
userIndex = 1;
else if (this.user_2.id === user.id)
userIndex = 2;
if (userIndex < 0) return false;
const otherUser = userIndex === 1 ? 2 : 1;
this[`user_${userIndex}`].userModel = user;
this[`user_${userIndex}`].socket = socket;
if (!this.child) this.child = await this.callModel.child().fetch();
let isParent = this.parent.id === user.id;
let peerId = isParent ? this.guest.id : this.parent.id;
if (isParent) {
this.parent.userModel = user;
this.parent.socket = socket;
this.userMap.set(user.id, this.parent);
} else {
this.guest.userModel = user;
this.guest.socket = socket;
this.userMap.set(user.id, this.guest);
peerId = this.parent.id;
}
socket.on('wrtc:sdp:offer', this.onSdpOffer.bind(this));
socket.on('wrtc:sdp:answer', this.onSdpAnswer.bind(this));
socket.on('wrtc:ice', this.onIceCandidate.bind(this));
socket.on('call:host:changed', this.onHostChanged.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();
if (this.state === 'STARTED') {
await this.sendStandby(socket, userIndex);
await this.sendStandby(socket, user.id, peerId);
// Send event to other user about the call
console.log(
`trying to find user ${this[`user_${otherUser}`].id} channel...`);
const otherUserChannel =
UserChannel.getUserChannel(this[`user_${otherUser}`].id);
console.log(`trying to find peer's ${peerId} channel...`);
const otherUserChannel = UserChannel.getUserChannel(peerId);
if (otherUserChannel) {
// console.log(otherUserChannel);
console.log('Sending notification to other user');
console.log(`Sending notification to peer ${peerId}`);
const payload = {callId: this.callId, child: this.child.toJSON()};
console.dir(payload);
otherUserChannel.emit('call:incoming', payload);
}
} else if (this.state === 'IN_PROGRESS')
await this.sendStart(socket, userIndex);
} else if (this.state === 'IN_PROGRESS') {
await this.sendStart(socket, user.id, peerId);
}
return true;
}
endCall() {
@ -122,31 +133,34 @@ class CallSession {
this.callModel.state = this.state;
this.callModel.save();
}
if (this.user_1.socket) this.user_1.socket.close();
if (this.user_2.socket) this.user_2.socket.close();
if (this.parent.socket) this.parent.socket.close();
if (this.guest.socket) this.guest.socket.close();
clearInterval(this.heartbeat);
this.onCallEndedCallback(this.callId);
}
async sendStandby(socket, userIndex) {
console.log(`Call #${this.callId} sendStandby -> ${userIndex}`);
async sendStandby(socket, userId, peerId) {
console.log(`Call #${this.callId} sendStandby -> ${userId}`);
const iceServers = (await IceServer.all()).rows.map(i => i.toJSON());
console.log(await this.callModel.parent().fetch());
socket.emit('call:standby', {
iceServers,
id: userIndex,
peerId,
child: this.child.toJSON(),
users: await this.callModel.getUsers(),
users: await Promise.all(
[this.callModel.parent().fetch(), this.callModel.guest().fetch()]),
hostId: this.hostId
});
}
async sendStart(socket, userIndex) {
console.log(`Call #${this.callId} sendStart -> ${userIndex}`);
async sendStart(socket, userId, peerId) {
console.log(`Call #${this.callId} sendStart -> ${userId}`);
const iceServers = (await IceServer.all()).rows.map(i => i.toJSON());
socket.emit('call:start', {
iceServers,
id: userIndex,
peerId,
child: this.child.toJSON(),
users: await this.callModel.getUsers(),
users: await Promise.all(
[this.callModel.parent().fetch(), this.callModel.guest().fetch()]),
hostId: this.hostId
});
}
@ -171,44 +185,39 @@ class CallSession {
console.log(`Call #${this.callId} state=${this.state}`);
}
areAllPartnersConnected() {
return !!this.user_1.socket && !!this.user_2.socket;
return !!this.parent.socket && !!this.guest.socket;
}
async onIceCandidate(payload) {
const {from = payload.id, ice} = payload;
const to = from === 1 ? 2 : 1;
this[`user_${to}`].socket.emit('wrtc:ice', {ice});
console.log(`[Signal] [onIceCandidate] ${from} -> ${to}`);
const {peerId, userId, ice} = payload;
this.userMap.get(peerId).socket.emit('wrtc:ice', {ice});
console.log(`[Signal] [onIceCandidate] ${userId} -> ${peerId}`);
return true;
}
async onSdpOffer(payload) {
const {from = payload.id, sdp} = payload;
const to = from === 1 ? 2 : 1;
this[`user_${to}`].socket.emit('wrtc:sdp:offer', {sdp});
console.log(`[Signal] [onSdpOffer] ${from} -> ${to}`);
const {peerId, userId, sdp} = payload;
this.userMap.get(peerId).socket.emit('wrtc:sdp:offer', {sdp});
console.log(`[Signal] [onSdpOffer] ${userId} -> ${peerId}`);
return true;
}
async onSdpAnswer(payload) {
const {from = payload.id, sdp} = payload;
const to = from === 1 ? 2 : 1;
this[`user_${to}`].socket.emit('wrtc:sdp:answer', {sdp});
console.log(`[Signal] [onSdpAnswer] ${from} -> ${to}`);
const {peerId, userId, sdp} = payload;
this.userMap.get(peerId).socket.emit('wrtc:sdp:answer', {sdp});
console.log(`[Signal] [onSdpAnswer] ${userId} -> ${peerId}`);
return true;
}
onActionBookFlip(payload) {
const {from = payload.id, direction} = payload;
const to = from === 1 ? 2 : 1;
this[`user_${to}`].socket.emit('book:action:flip-page', {direction});
console.log(
`[Signal] [book] [action] [flip] [${direction}] ${from} -> ${to}`);
const {peerId, userId, direction} = payload;
this.userMap.get(peerId).socket.emit('book:action:flip-page', {direction});
console.log(`[Signal] [book] [action] [flip] [${direction}] ${userId} -> ${
peerId}`);
return true;
}
async onHostChanged(payload) {
const {from = payload.id, hostId} = payload;
const to = from === 1 ? 2 : 1;
const {peerId, hostId} = payload;
this.hostId = hostId;
this[`user_${to}`].socket.emit('call:host:changed', {hostId});
this.userMap.get(peerId).socket.emit('call:host:changed', {hostId});
console.log(
`[Signal] [host] [changed] [hostId=${hostId}] ${from} -> ${to}`);
return true;

View file

@ -20,7 +20,7 @@ class WsCallAuth {
throw new Error('Call not found');
}
if (call.state === 'ENDED') throw new Error('This call has ended');
if (user.id === call.user_1 || user.id === call.user_2) {
if (user.id === call.parent_id || user.id === call.guest_id) {
ctx.call = call;
await next()
}

20
app/Models/ActivityLog.js Normal file
View file

@ -0,0 +1,20 @@
'use strict'
/** @type {typeof import('@adonisjs/lucid/src/Lucid/Model')} */
const Model = use('Model')
class ActivityLog extends Model {
async call() {
return this.belongsTo('App/Models/Call');
}
async resource() {
switch (this.type) {
case 'BOOK':
return this.belongsTo('App/Models/Book');
default:
throw new Error('Unknown Resource Type');
}
}
}
module.exports = ActivityLog

View file

@ -5,6 +5,7 @@ const Model = use('Model')
class Book extends Model {
user() {
if (!this.user_id) return null;
return this.belongsTo('App/Models/User');
}
}

View file

@ -5,13 +5,14 @@ const Model = use('Model')
const User = use('App/Models/User');
class Call extends Model {
async getUsers() {
const users = [
(await User.find(this.user_1)).toJSON(),
(await User.find(this.user_2)).toJSON()
];
return users;
parent() {
return this.belongsTo('App/Models/User', 'parent_id');
}
guest() {
return this.belongsTo('App/Models/User', 'guest_id');
}
child() {
return this.belongsTo('App/Models/Child');
}
}

View file

@ -8,13 +8,13 @@ class CallSchema extends Schema {
this.create('calls', (table) => {
table.increments();
table.string('state').notNullable();
table.bigInteger('user_1').notNullable();
table.bigInteger('user_2').notNullable();
table.bigInteger('parent_id').notNullable();
table.bigInteger('guest_id').notNullable();
table.bigInteger('child_id').notNullable();
table.timestamps();
table.index(['child_id']);
table.index(['user_1']);
table.index(['user_2']);
table.index(['parent_id']);
table.index(['guest_id']);
})
}

View file

@ -7,9 +7,10 @@ class BookSchema extends Schema {
up() {
this.create('books', (table) => {
table.increments()
table.bigInteger('user_id').notNullable();
table.bigInteger('user_id');
table.integer('pages').notNullable();
table.string('book_folder').notNullable();
table.boolean('ltr').default(true);
table.timestamps()
})
}

View file

@ -0,0 +1,22 @@
'use strict'
/** @type {import('@adonisjs/lucid/src/Schema')} */
const Schema = use('Schema')
class ActivityLogSchema extends Schema {
up() {
this.create('activity_logs', (table) => {
table.increments();
table.bigInteger('call_id').notNullable();
table.string('type', 60).notNullable();
table.bigInteger('resource_id').notNullable();
table.timestamps(); // created_at = start time, updated_at = end time
});
}
down() {
this.drop('activity_logs');
}
}
module.exports = ActivityLogSchema

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

@ -10,11 +10,11 @@ export default class CallManager {
private emitter = new EventEmitter();
private pc: RTCPeerConnection;
public child;
public guest = { avatar: '' };
public peer = { avatar: '' };
public isHost = false;
constructor(private ws: WebSocketService, private callId: number, private userId: number) {
this.inCall = false;
this.peerId = -1;
this.peerId = null;
this.pc = null;
this.remoteStream = new MediaStream();
}
@ -55,19 +55,20 @@ export default class CallManager {
}
private send(event: string, payload: { [key: string]: any }) {
this.signalingChannel.emit(event, {
id: this.peerId,
userId: this.userId,
peerId: this.peerId,
...payload
})
}
async onCallStart(payload: { iceServers: RTCIceServer[], id: number, users: any[], child: any, hostId: number }) {
async onCallStart(payload: { iceServers: RTCIceServer[], peerId: number, users: any[], child: any, hostId: number }) {
console.log('onCallStart');
console.log(payload);
this.peerId = payload.id;
this.peerId = payload.peerId;
this.isHost = this.peerId === payload.hostId;
this.pc = new RTCPeerConnection({ iceServers: payload.iceServers });
this.child = payload.child;
payload.users.forEach(u => {
if (u.id !== this.userId) this.guest = u;
if (u.id === this.peerId) this.peer = u;
});
this.emit(ECallEvents.CALL_HOST_CHANGED, { hostId: payload.hostId });
console.log('Created PeerConnection');
@ -83,15 +84,15 @@ export default class CallManager {
return true;
}
async onCallStandby(payload: { iceServers: RTCIceServer[], id: number, users: any[], child: any, hostId: number }) {
async onCallStandby(payload: { iceServers: RTCIceServer[], peerId: number, users: any[], child: any, hostId: number }) {
console.log('onCallStandby');
console.log(payload);
this.peerId = payload.id;
this.peerId = payload.peerId;
this.isHost = this.peerId === payload.hostId;
this.pc = new RTCPeerConnection({ iceServers: payload.iceServers });
this.child = payload.child;
payload.users.forEach(u => {
if (u.id !== this.userId) this.guest = u;
if (u.id === this.peerId) this.peer = u;
});
this.emit(ECallEvents.CALL_HOST_CHANGED, { hostId: payload.hostId });
console.log('Created PeerConnection');
@ -174,8 +175,7 @@ export default class CallManager {
changeHost() {
this.isHost = !this.isHost;
const guestPeerId = this.peerId === 1 ? 2 : 1;
const hostId = this.isHost ? this.peerId : guestPeerId;
const hostId = this.isHost ? this.userId : this.peerId;
this.emit(ECallEvents.CALL_HOST_CHANGED, { hostId });
this.send('call:host:changed', { hostId });
}

View file

@ -9,7 +9,7 @@
<div class="me">
<figure class="image is-24x24">
<img
:src="isHost? user.avatar : callManager.guest.avatar"
:src="isHost? user.avatar : callManager.peer.avatar"
class="is-rounded is-avatar"
@click="changeHost()"
/>
@ -150,7 +150,7 @@ export default {
onRemoteHostChanged(payload) {
console.log("-----------");
console.log(payload);
this.guest = this.callManager.guest;
this.peer = this.callManager.peer;
this.isHost = this.callManager.isHost;
},
changeHost() {

View file

@ -44,7 +44,7 @@ Seepur | Our Time together
<p>
With Seepur you can simply choose your grandchild/niece/nephew you wish to connect with, click on your chosen activity for the day, and share a fun experience together.
</p>
<a href="#" class="button m-t-md">More...</a>
{{-- <a href="#" class="button m-t-md">More...</a> --}}
</div>
</section>
<section>
@ -108,7 +108,7 @@ Seepur | Our Time together
Make an actual impact on the child by creating shared memories. You will be surprised how engaged and comunicative a little child could be during active sessions.
With the great combination of video calling and your shared view of the book pages, you can make the story come alive by using different voices for different characters, and even acting out parts of the story, as if youre right there by their side.
</p>
<a href="#" class="button m-t-md">More...</a>
{{-- <a href="#" class="button m-t-md">More...</a> --}}
</div>
</div>
</section>
@ -120,7 +120,7 @@ Seepur | Our Time together
<p>
Seepur is all about you and the child. You can upload your own books and read together, create puzzles with your own photos and more!
</p>
<a href="#" class="button m-t-md">More...</a>
{{-- <a href="#" class="button m-t-md">More...</a> --}}
</div>
<div class="column">
<figure class="image is-5by4">