diff --git a/client/secureChatIonic/app/app.ts b/client/secureChatIonic/app/app.ts index cd8efbe..9f914a8 100644 --- a/client/secureChatIonic/app/app.ts +++ b/client/secureChatIonic/app/app.ts @@ -1,21 +1,24 @@ -import { Component, ViewChild } from '@angular/core'; +import { Component, ViewChild, ChangeDetectionStrategy } from '@angular/core'; import { ionicBootstrap, Platform, Nav } from 'ionic-angular'; import { StatusBar } from 'ionic-native'; -//Import our pages -import { Home } from './pages/home/home'; -import { AllMessagesPage } from './pages/all-messages/all-messages'; -import { AuthLoginPage } from './pages/auth-login/auth-login'; - //Import our providers (services) import { AppSettings } from './providers/app-settings/app-settings'; +import { AppNotify } from './providers/app-notify/app-notify'; import { AppAuth } from './providers/app-auth/app-auth'; -import { AppNotification } from './providers/app-notification/app-notification'; -import { AppLoading } from './providers/app-loading/app-loading'; +import { AppMessaging } from './providers/app-messaging/app-messaging'; + +//Import our pages +import { Home } from './pages/home/home'; +import { AuthLoginPage } from './pages/auth-login/auth-login'; +import { AllConversationsPage } from './pages/all-conversations/all-conversations'; +import { ConversationPage } from './pages/conversation/conversation'; +//Change detection needed for updating "this" AKA $scope @Component({ templateUrl: 'build/app.html', - providers: [AppAuth, AppSettings, AppNotification, AppLoading] + providers: [AppSettings, AppAuth, AppMessaging, AppNotify], + changeDetection: ChangeDetectionStrategy.OnPush }) class MyApp { @ViewChild(Nav) nav: Nav; @@ -28,7 +31,7 @@ class MyApp { noAuthPages: Array<{ title: string, component: any }>; authPages: Array<{ title: string, component: any }>; - constructor(public platform: Platform, public authProvider: AppAuth) { + constructor(public platform: Platform, private authProvider: AppAuth, private appNotify: AppNotify) { this.initializeApp(); // used for an example of ngFor and navigation @@ -39,11 +42,11 @@ class MyApp { { title: 'Login', component: AuthLoginPage } ]; this.authPages = [ - { title: 'Messages', component: AllMessagesPage } + { title: 'Messages', component: AllConversationsPage } ]; //Set our root page - if (this.isLoggedIn()) this.rootPage = AllMessagesPage; + if (this.isLoggedIn()) this.rootPage = AllConversationsPage; else this.rootPage = Home; } @@ -67,12 +70,34 @@ class MyApp { //Check if we are logged in isLoggedIn() { - return this.authProvider.authStatus(); + + //Get the auth Status + return this.authProvider.authStatus; } //Logout the user logout() { + //Start Loading + this.appNotify.startLoading('Logging out...'); + this.authProvider.logout(); + + //Store reference to this for timeout + let self = this; + + //Stop Loading + this.appNotify.stopLoading().then(function() { + //Toast What Happened + //In a timeout to avoid colliding with loading + setTimeout(function() { + + //Go back home + self.rootPage = Home; + self.nav.setRoot(Home); + + self.appNotify.showToast('Logout Successful!'); + }, 250) + }); } } diff --git a/client/secureChatIonic/app/pages/all-conversations/all-conversations.html b/client/secureChatIonic/app/pages/all-conversations/all-conversations.html new file mode 100644 index 0000000..d7db6f8 --- /dev/null +++ b/client/secureChatIonic/app/pages/all-conversations/all-conversations.html @@ -0,0 +1,40 @@ + + + + + + + + Messages + + + + + + + + + + + + No Conversations Found! + (Add some friends using the side menu) + + + + + + + {{getConvoMembers(conversation)}} + + {{getConvoLatestText(conversation)}} + + + + + diff --git a/client/secureChatIonic/app/pages/all-messages/all-messages.scss b/client/secureChatIonic/app/pages/all-conversations/all-conversations.scss similarity index 93% rename from client/secureChatIonic/app/pages/all-messages/all-messages.scss rename to client/secureChatIonic/app/pages/all-conversations/all-conversations.scss index a951b91..0a0d172 100644 --- a/client/secureChatIonic/app/pages/all-messages/all-messages.scss +++ b/client/secureChatIonic/app/pages/all-conversations/all-conversations.scss @@ -1,7 +1,3 @@ -.all-messages { - -} - .recentMessagesList { width: 100%; } diff --git a/client/secureChatIonic/app/pages/all-conversations/all-conversations.ts b/client/secureChatIonic/app/pages/all-conversations/all-conversations.ts new file mode 100644 index 0000000..3d968ef --- /dev/null +++ b/client/secureChatIonic/app/pages/all-conversations/all-conversations.ts @@ -0,0 +1,166 @@ +import { Component, ChangeDetectorRef } from '@angular/core'; +import { NavController } from 'ionic-angular'; + +//Import to conversation view +import { ConversationPage } from '../../pages/conversation/conversation'; + +//Import our providers +import { AppSettings } from '../../providers/app-settings/app-settings'; +import { AppNotify } from '../../providers/app-notify/app-notify'; +import { AppMessaging } from '../../providers/app-messaging/app-messaging'; + +@Component({ + templateUrl: 'build/pages/all-conversations/all-conversations.html' +}) +export class AllConversationsPage { + + //Our recent conversations + convoList: any; + + //Our message Polling + pollingRequest: any; + + constructor(private changeDetector: ChangeDetectorRef, private navCtrl: NavController, private appNotify: AppNotify, private appMessaging: AppMessaging) { + + //polling done and requests done on very request + } + + //Function called once the view is full loaded + ionViewDidEnter() { + + //Start Loading + this.appNotify.startLoading('Getting Messages...'); + + //Get the messages on view load, and start a polling request + + //Grab our user from localstorage + let user = JSON.parse(localStorage.getItem(AppSettings.shushItemName)) + + //Start polling to get messages + let request = this.appMessaging.conversationRequest(user.access_token); + + //Get a reference to this + let self = this; + + //Get our current conversation + request.subscribe(function(success) { + //Success! + //Stop loading + self.appNotify.stopLoading().then(function() { + self.messageGetSuccess(success); + }); + }, function(error) { + //Error! + //Stop Loading + self.appNotify.stopLoading().then(function() { + self.messageGetError(error); + }); + + }, function() { + //Completed + }) + + //Start polling to get messages + let poll = this.appMessaging.conversationRequestPoll(user.access_token); + + this.pollingRequest = poll.subscribe(function(success) { + //Success! + self.messageGetSuccess(success); + }, function(error) { + //Error! + self.messageGetError(error); + }, function() { + //Completed + }); + } + + //Functions to handle observable responses + messageGetSuccess(success) { + //Add our messages + this.appMessaging.conversations = success + this.convoList = this.appMessaging.conversations; + + //Update the UI + this.changeDetector.detectChanges(); + } + + messageGetError(error) { + + //reference to this + let self = this; + + //Pass to Error Handler + this.appNotify.handleError(error, [{ + status: 404, + callback: function() { + //Simply set all conversations to an empty array + self.convoList = []; + + //Update the UI + self.changeDetector.detectChanges(); + } + }]); + } + + //Function to return if we have conversations + hasConversations() { + if (this.convoList && this.convoList.length == 0) return true; + else return true; + } + + //Function to reutn the users in a conversations + getConvoMembers(convo: any) { + + //Get the last message sender + + //Get the names of the members spilt by spaces + let members = ''; + for (let i = 0; i < convo.memberNames.length; ++i) { + members += convo.memberNames[i].split(' ')[0]; + if (i < convo.memberNames.length - 1) members += ', '; + } + + return this.shortenText(members, 20); + + } + + getConvoLatestText(convo: any) { + + //Get the last message sender + let lastMessage = convo.message[convo.message.length - 1]; + + let lastSender = lastMessage.from.split(' ')[0]; + let lastText = lastMessage.message; + + return this.shortenText(lastSender + ': ' + lastText, 35) + } + + //Function to return + //Get shortened text with elipses + shortenText(text: string, textMax) { + + //First check if the text is already short + if (text.length < textMax) return text; + else { + //Get a substring of text + text = text.substring(0, (textMax - 3)); + text = text + '...'; + return text; + } + } + + //Fucntion to run when an item is clicked + convoClick(convo) { + //Go to the conversation page, and pass the conversation id + this.navCtrl.push(ConversationPage, { + conversation: convo + }); + } + + //Run on page leave + ionViewWillLeave() { + //Stop + this.pollingRequest.unsubscribe(); + } + +} diff --git a/client/secureChatIonic/app/pages/all-messages/all-messages.html b/client/secureChatIonic/app/pages/all-messages/all-messages.html deleted file mode 100644 index 7dfa91d..0000000 --- a/client/secureChatIonic/app/pages/all-messages/all-messages.html +++ /dev/null @@ -1,29 +0,0 @@ - - - - - - - - Messages - - - - - - - - - - - {{message.user}} - - {{shortenText(message.text)}} - - - diff --git a/client/secureChatIonic/app/pages/all-messages/all-messages.ts b/client/secureChatIonic/app/pages/all-messages/all-messages.ts deleted file mode 100644 index 5516871..0000000 --- a/client/secureChatIonic/app/pages/all-messages/all-messages.ts +++ /dev/null @@ -1,64 +0,0 @@ -import { Component } from '@angular/core'; -import { NavController } from 'ionic-angular'; - -//Import to conversation view -import { ConversationPage } from '../../pages/conversation/conversation'; - -/* - Generated class for the AllMessagesPage page. - - See http://ionicframework.com/docs/v2/components/#navigation for more info on - Ionic pages and navigation. -*/ -@Component({ - templateUrl: 'build/pages/all-messages/all-messages.html', -}) -export class AllMessagesPage { - - //Our NavController - location: NavController; - - //Our recent conversations - recentMessages: Array; - - constructor(private navCtrl: NavController) { - - //Set our nav controller - this.location = navCtrl; - - //Recent messages from all conversations (template for now) - this.recentMessages = [ - { - user: "Kumin In", - text: "Sup dude!", - conversationId: "1457" - }, - { - user: "Bob Smith", - text: "What's the homework?", - conversationId: "1243" - } - ]; - } - - //Get shortened text with elipses - shortenText(text: string) { - //First check if the text is already short - if (text.length < 21) return text; - else { - //Get a substring of text - text = text.substring(0, 20); - text = text + '...'; - return text; - } - } - - //Fucntion to run when an item is clicked - convoClick(id) { - //Go to the conversation page, and pass the conversation id - this.navCtrl.push(ConversationPage, { - conversationId: id - }); - } - -} diff --git a/client/secureChatIonic/app/pages/auth-login/auth-login.ts b/client/secureChatIonic/app/pages/auth-login/auth-login.ts index aa097e7..32cd307 100644 --- a/client/secureChatIonic/app/pages/auth-login/auth-login.ts +++ b/client/secureChatIonic/app/pages/auth-login/auth-login.ts @@ -1,7 +1,12 @@ import { Component } from '@angular/core'; import { NavController } from 'ionic-angular'; +//Pages +import { AllConversationsPage } from '../../pages/all-conversations/all-conversations'; + //Import our providers (services) +import { AppSettings } from '../../providers/app-settings/app-settings'; +import { AppNotify } from '../../providers/app-notify/app-notify'; import { AppAuth } from '../../providers/app-auth/app-auth'; /* @@ -16,11 +21,65 @@ import { AppAuth } from '../../providers/app-auth/app-auth'; export class AuthLoginPage { - constructor(private navCtrl: NavController, private authProvider: AppAuth) { } + constructor(private navCtrl: NavController, private appNotify: AppNotify, private appAuth: AppAuth) { } //Call our log in function from our auth service login() { - this.authProvider.login(); + + //Start Loading + this.appNotify.startLoading('Logging in...'); + + //Save a reference to this + let self = this; + + let response = this.appAuth.login(); + + //Respond to the callback + response.subscribe(function(success: any) { + //Success! + + //Create our new user + let userJson = { + user: {}, + access_token: '', + keys: {} + }; + //Get the neccesary info from the user object + userJson.user = success.user; + userJson.access_token = success.access_token; + //TODO: Generate Encryption keys for the user if none + userJson.keys = {}; + + //Save the user info + localStorage.setItem(AppSettings.shushItemName, JSON.stringify(userJson)); + + //Update the auth status + self.appAuth.updateAuthStatus(true); + + //Stop Loading + self.appNotify.stopLoading().then(function() { + //Toast What Happened + //In a timeout to avoid colliding with loading + setTimeout(function() { + //Show Toast + self.appNotify.showToast('Login Successful!'); + + //Redirect to messages page + self.navCtrl.setRoot(AllConversationsPage); + }, 250) + }); + }, function(error) { + + //Error + //Stop Loading + self.appNotify.stopLoading().then(function() { + + //There was an error connecting to facebook + self.appNotify.showToast('Error, Facebook did not return your credentials.') + }); + }, function() { + //Subscription has completed + }) } } diff --git a/client/secureChatIonic/app/pages/conversation/conversation.html b/client/secureChatIonic/app/pages/conversation/conversation.html index 3a526bc..44c5931 100644 --- a/client/secureChatIonic/app/pages/conversation/conversation.html +++ b/client/secureChatIonic/app/pages/conversation/conversation.html @@ -16,7 +16,7 @@ - + - {{convoMessage.sender}} + {{convoMessage.from}} @@ -34,7 +34,7 @@ - 12:2{{index}}PM + {{prettyDate(convoMessage.date)}} diff --git a/client/secureChatIonic/app/pages/conversation/conversation.ts b/client/secureChatIonic/app/pages/conversation/conversation.ts index 0e8f1ba..5fb50b2 100644 --- a/client/secureChatIonic/app/pages/conversation/conversation.ts +++ b/client/secureChatIonic/app/pages/conversation/conversation.ts @@ -1,6 +1,11 @@ -import { Component, ViewChild } from '@angular/core'; +import { Component, ViewChild, ChangeDetectorRef } from '@angular/core'; import { Content, NavController, NavParams } from 'ionic-angular'; +//Import our providers +import { AppSettings } from '../../providers/app-settings/app-settings'; +import { AppNotify } from '../../providers/app-notify/app-notify'; +import { AppMessaging } from '../../providers/app-messaging/app-messaging'; + /* Generated class for the ConversationPage page. @@ -14,14 +19,14 @@ export class ConversationPage { //Get reference to our ion-content @ViewChild(Content) content: Content; - //The id of the conversation - convoId: string; + //Our Current Conversation + convo: any; - //The title of the conversation - convoTitle: string; + //The Id of our conversation + convoId: any; - //Our array of messages of the conversation - conversation: Array; + //Our conversation title + convoTitle: any; //Our reply ng-model data replyMessage: string; @@ -29,96 +34,111 @@ export class ConversationPage { //Our scroll duration for auto-scrolling through the messages scrollDuration: number; - constructor(private navCtrl: NavController, private navParams: NavParams) { - - //Get the conversation ID passed from the last page - this.convoId = this.navParams.get('conversationId'); - - //Get our conversation (Template for now) - this.conversation = [ - { - senderId: "1034", - sender: "kumin", - message: "hey there!" - }, - { - senderId: "1034", - sender: "kumin", - message: "dude?" - }, - { - senderId: "2424", - sender: "aaron", - message: "sup dude! Cool to work with you!" - }, - { - senderId: "2424", - sender: "aaron", - message: "sup dude! Cool to work with you!" - }, - { - senderId: "2424", - sender: "aaron", - message: "sup dude! Cool to work with you!" - }, - { - senderId: "2424", - sender: "aaron", - message: "sup dude! Cool to work with you!" - }, - { - senderId: "2424", - sender: "aaron", - message: "sup dude! Cool to work with you!" - }, - { - senderId: "2424", - sender: "aaron", - message: "sup dude! Cool to work with you!" - } - ]; - - //Get the conversation title, function being called, but not returning? - this.convoTitle = this.getConvoTitle(this.conversation); + //Our message Polling + pollingRequest: any; - //Tag which messages were sent by the user - this.conversation = this.findUserMessages(this.conversation); + constructor(private changeDetector: ChangeDetectorRef, private navCtrl: NavController, private navParams: NavParams, private appNotify: AppNotify, private appMessaging: AppMessaging) { //Initialize our reply message to an empty string this.replyMessage = ''; //Initialize our scroll duration this.scrollDuration = 350; + + //Get the conversation passed from the last page + this.convo = this.navParams.get('conversation'); + this.convoId = this.convo._id; + + //Get the conversation title + this.convoTitle = this.getConvoTitle(this.convo); + + //User messages can be tagged in HTML, by comparing user ids in the ngFor } //Function called once the view is full loaded ionViewDidEnter() { + + //Scroll to the bottom of the messages (Request below is for polling, not getting messages) + this.content.scrollToBottom(this.scrollDuration); + + //Grab our user from localstorage + let user = JSON.parse(localStorage.getItem(AppSettings.shushItemName)); + + //Start polling to get messages + let poll = this.appMessaging.conversationRequestPoll(user.access_token); + + //Get a reference to this + let self = this; + + this.pollingRequest = poll.subscribe(function(success) { + //Success! + + //Stop loading + self.appNotify.stopLoading().then(function() { + + //Add our messages/Get our conversation + self.appMessaging.conversations = success; + + //Update our conversations + self.updateConversation(); + }); + }, function(error) { + //Error! + + //Stop Loading + self.appNotify.stopLoading().then(function() { + //Pass to Error Handler + self.appNotify.handleError(error, [{ + status: 404, + callback: function() { + //Pop back to the All conversations view + + self.navCtrl.pop(); + } + }]); + }); + + }, function() { + //Completed + }) + } + + //Function to update our conversation + updateConversation() { + + //Find and update our current conversation + for (let i = 0; i < this.appMessaging.conversations.length; ++i) { + if (this.convoId == this.appMessaging.conversations[i]._id) { + + //Update the conversation + this.convo = this.appMessaging.conversations[i]; + + //Break from the loop + i = this.appMessaging.conversations.length; + } + } + + //Update the UI + this.changeDetector.detectChanges(); + //Scroll to the bottom of the messages this.content.scrollToBottom(this.scrollDuration); } //Function to get the title of the conversationId - getConvoTitle(messages: Array) { + getConvoTitle(conversation) { //Return if no senders - if (messages.length < 1) return 'Conversation'; + if (conversation.length < 1) return 'Conversation'; //Initialize our variables var convoTitle = ''; - var convoMembers = []; - - //Find all unique senders in the conversation - for (let i = 0; i < this.conversation.length; i++) { - if (convoMembers.indexOf(this.conversation[i].sender) < 0) { - convoMembers.push(this.conversation[i].sender); - } - } //Add all the senders to the conversation title - for (let i = 0; i < convoMembers.length; i++) { + for (let i = 0; i < conversation.memberNames.length; i++) { //Also, add the needed commas - if (i >= convoMembers.length - 1) convoTitle = convoTitle + convoMembers[i]; - else convoTitle = convoTitle + convoMembers[i] + ", "; + if (i >= conversation.memberNames.length - 1) convoTitle = convoTitle + conversation.memberNames[i]; + else convoTitle = convoTitle + conversation.memberNames[i] + ", "; } //Shorten the conversation title to 30 characters @@ -128,20 +148,6 @@ export class ConversationPage { return convoTitle; } - //Function to tag the messages that were sent by this user - findUserMessages(messages: Array) { - - //Get out user settings from localStorage - var userToken = JSON.parse(localStorage.getItem("shushUser")); - - //Simply iterate through the array - for (let i = 0; i < messages.length; ++i) { - if (messages[i].senderId = userToken.id) messages[i].isUser = true; - } - - return messages; - } - //Function to send a message (Done from click) sendReply(keyCode) { @@ -151,21 +157,94 @@ export class ConversationPage { //Check if the reply text is empty if (this.replyMessage.length < 1) return false; - //TODO: Connect message sending to the backend - this.conversation.push({ - senderId: "2424", - sender: "aaron", - message: this.replyMessage - }); + //Start Loading + this.appNotify.startLoading('Sending Message...'); - //Empty the reply message - this.replyMessage = ''; + //Grab our user from localstorage + let user = JSON.parse(localStorage.getItem(AppSettings.shushItemName)); - //Scroll to the bottom of the messages - setTimeout(() => { - this.content.scrollToBottom(this.scrollDuration);//300ms animation speed + //Create our payload + var payload = { + access_token: user.access_token, + message: this.replyMessage, + conversationID: this.convoId + } + + //Get a reference to this + let self = this; + + //Make our request + this.appMessaging.conversationReply(payload).subscribe(function(success) { + //Success + + //Stop Loading + self.appNotify.stopLoading().then(function() { + + //Set our convo to the the success result + self.convo = success; + + //Empty the reply message + self.replyMessage = ''; + + //Update the UI + self.changeDetector.detectChanges(); + + //Scroll to the bottom of the messages + self.content.scrollToBottom(self.scrollDuration); + + //Toast the user + self.appNotify.showToast('Message Sent!'); + }); + + }, function(error) { + //Error + //Stop Loading + self.appNotify.stopLoading().then(function() { + //Pass to Error Handler + self.appNotify.handleError(error, [{ + status: 404, + callback: function() { + //Pop back to the All conversations view + + self.navCtrl.pop(); + } + }]); + }); + }, function() { + //Completed }); + } + + //Run on page leave + ionViewWillLeave() { + //Stop + this.pollingRequest.unsubscribe(); + } + + /* + * JavaScript Pretty Date + * Copyright (c) 2011 John Resig (ejohn.org) + * Licensed under the MIT and GPL licenses. + */ + //Using preety date for our message date strings + prettyDate(time) { + let date = new Date((time || "").replace(/-/g, "/").replace(/[TZ]/g, " ")), + diff = (((new Date()).getTime() - date.getTime()) / 1000), + day_diff = Math.floor(diff / 86400); + + if (isNaN(day_diff) || day_diff < 0 || day_diff >= 31) + return; + + return day_diff == 0 && ( + diff < 60 && "just now" || + diff < 120 && "1 minute ago" || + diff < 3600 && Math.floor(diff / 60) + " minutes ago" || + diff < 7200 && "1 hour ago" || + diff < 86400 && Math.floor(diff / 3600) + " hours ago") || + day_diff == 1 && "Yesterday" || + day_diff < 7 && day_diff + " days ago" || + day_diff < 31 && Math.ceil(day_diff / 7) + " weeks ago"; } } diff --git a/client/secureChatIonic/app/providers/app-auth/app-auth.ts b/client/secureChatIonic/app/providers/app-auth/app-auth.ts index 30c532f..a0364ca 100644 --- a/client/secureChatIonic/app/providers/app-auth/app-auth.ts +++ b/client/secureChatIonic/app/providers/app-auth/app-auth.ts @@ -1,61 +1,34 @@ import { Injectable } from '@angular/core'; import { Http } from '@angular/http'; -import { App } from 'ionic-angular'; +import { Observable } from 'rxjs/Observable'; import 'rxjs/add/operator/map'; -//Import Pages we navigate to +//pages import { Home } from '../../pages/home/home'; -import { AllMessagesPage } from '../../pages/all-messages/all-messages'; //Import our providers (services) import { AppSettings } from '../../providers/app-settings/app-settings'; -import { AppNotification } from '../../providers/app-notification/app-notification'; -import { AppLoading } from '../../providers/app-loading/app-loading'; -/* - Generated class for the AppAuth provider. - - See https://angular.io/docs/ts/latest/guide/dependency-injection.html - for more info on providers and Angular 2 DI -*/ @Injectable() export class AppAuth { - //Our user accesToken - /* - User schema - { - user: { - name:"" - email:"" - etc.... - } - keys: { - public: 'sdjlaksjda' - private: 'askjdklsjd' - }, - access_token: 'token' - } - */ - user: any; + //Our auth status + authStatus: boolean; //Class constructor - constructor(private app: App, private http: Http, private appNotification: AppNotification, private appLoading: AppLoading) { - //Initialize the user + constructor(private http: Http) { //Grab our user from localstorage - if (localStorage.getItem(AppSettings.shushItemName)) { - this.user = JSON.parse(localStorage.getItem(AppSettings.shushItemName)); + let user = JSON.parse(localStorage.getItem(AppSettings.shushItemName)) + if (user && user.access_token) { + this.updateAuthStatus(true); } else { - this.user = { - access_token: false - }; + this.updateAuthStatus(false); } } //Return our Login Status - authStatus() { - if (this.user.access_token) return true; - return false; + updateAuthStatus(status) { + this.authStatus = status; } //Initialize facebook @@ -70,114 +43,64 @@ export class AppAuth { //Login //Scope asks for permissions that we need to create/identify users + //How to make REST requests in Angular 2: http://stackoverflow.com/questions/34671715/angular2-http-get-map-subscribe-and-observable-pattern-basic-understan/34672550 login() { - //Make a reference to 'this' to avoid scoping this issue + //Get a reference to self let self = this; - FB.login(function(response) { - //Response from facebook on function call - let jsonResponse = response.authResponse; + //Create a new observable + //https://medium.com/@benlesh/learning-observable-by-building-observable-d5da57405d87#.g2xfbgf3h + //observer.next => success, observer.error => error, observer.complete => complete + return new Observable(function(observer) { - //Pass the access token to our server login - let payload = { - access_token: jsonResponse.accessToken - } - self.serverLogin(payload); + FB.login(function(response) { - }, { - scope: 'email' - }); - } - - //Logout - logout() { - let payload = { - access_token: this.user.access_token - } - this.serverLogout(payload); - } + //Response from facebook on function call + let jsonResponse = response.authResponse; - //Private functions for server requests - //How to make REST requests in Angular 2: http://stackoverflow.com/questions/34671715/angular2-http-get-map-subscribe-and-observable-pattern-basic-understan/34672550 - private serverLogin(payload) { + //Check for an error + if (!jsonResponse) { + observer.error(response); + return; + } - //Start Loading - this.appLoading.startLoading('Logging in...'); + //Pass the access token to our server login + let payload = { + access_token: jsonResponse.accessToken + } - //Save a reference to this - let self = this; + let request = self.http.post(AppSettings.serverUrl + 'login', payload).map(res => res.json()); - //Send the request with the payload to the server - var response = this.http.post(AppSettings.serverUrl + 'login', payload).map(res => res.json()); - - //Respond to the callback - response.subscribe(function(success) { - //Success! - - //Get the neccesary info from the user object - self.user.user = success.user - self.user.access_token = payload.access_token; - //TODO: Generate Encryption keys for the user if none - self.user.keys = {}; - - //Save the user info - localStorage.setItem(AppSettings.shushItemName, JSON.stringify(self.user)); - - //Stop Loading - self.appLoading.stopLoading().then(function() { - //Toast What Happened - //In a timeout to avoid colliding with loading - setTimeout(function() { - self.appNotification.showToast('Login Successful!'); - }, 250) - }); - - //Redirect to messages page - let nav = self.app.getRootNav(); - nav.setRoot(AllMessagesPage); - }, function(error) { - - //Error - //Stop Loading - self.appLoading.stopLoading().then(function() { - //Pass to Error Handler - self.appLoading.handleError(error); - }); - }, function() { - //Subscription has completed - }) - } + observer.next({ + request: request, + access_token: jsonResponse.accessToken + }); - private serverLogout(payload) { + }, { + scope: 'email' + }); - //Start Loading - this.appLoading.startLoading('Logging out...'); + }) + } + //Logout + logout() { //No work is needed by the server, since the token will invalidate itself in OAuth - //Set the user to false - this.user.access_token = false; - this.user.user = {}; - localStorage.setItem(AppSettings.shushItemName, JSON.stringify(this.user)); - - //Store reference to this for timeout - let self = this; - - //Stop Loading - this.appLoading.stopLoading().then(function() { - //Toast What Happened - //In a timeout to avoid colliding with loading - setTimeout(function() { - self.appNotification.showToast('Logout Successful!'); - }, 250) - }); + //Get our stored user + //Grab our user from localstorage + let user = JSON.parse(localStorage.getItem(AppSettings.shushItemName)) + if (user && user.access_token) user.access_token = false;; + if (user && user.user) user.user = {}; - //Redirect to messages page - let nav = this.app.getRootNav(); - nav.setRoot(Home); + //Set the user to false + localStorage.setItem(AppSettings.shushItemName, JSON.stringify(user)); + //Update the auth status + this.updateAuthStatus(false); } + } diff --git a/client/secureChatIonic/app/providers/app-loading/app-loading.ts b/client/secureChatIonic/app/providers/app-loading/app-loading.ts deleted file mode 100644 index c0f2af7..0000000 --- a/client/secureChatIonic/app/providers/app-loading/app-loading.ts +++ /dev/null @@ -1,92 +0,0 @@ -import { Injectable } from '@angular/core'; -import { App, LoadingController } from 'ionic-angular'; - -//Pages -import { AuthLoginPage } from '../../pages/auth-login/auth-login'; - -//Our Providers -import { AppSettings } from '../../providers/app-settings/app-settings'; -import { AppNotification } from '../../providers/app-notification/app-notification'; - - -/* - Generated class for the AppLoading provider. - - See https://angular.io/docs/ts/latest/guide/dependency-injection.html - for more info on providers and Angular 2 DI. -*/ - -//Handle Async requests, loading spinners, and general errors -@Injectable() -export class AppLoading { - - //Our Loader - loader: any; - - //Our default loading string - defaultMessage: 'Loading, please wait...'; - - constructor(private app: App, private loadingCtrl: LoadingController, private appNotification: AppNotification) { - } - - //Function to start loading - startLoading(loadingString) { - //First make sure we stop loading - if (!loadingString) loadingString = this.defaultMessage; - this.loader = this.loadingCtrl.create({ - content: loadingString - }); - return this.loader.present(); - } - - //Function to stop Loading - stopLoading() { - return this.loader.dismiss(); - } - - //Function to handle Errors - handleError(error) { - - //TODO: Allow for overiding error codes, and using custom callbacks - - //Log the error - console.log(error); - - //Get our status - let status = error.status; - - if (status == 400) { - //400 Bad Request - this.appNotification.showToast('Bad Request. Please ensure your input is correct.'); - } else if (status == 401) { - //401 Unauthorized - - //Set the access token to false - let user = JSON.parse(localStorage.getItem(AppSettings.shushItemName)); - user.access_token = false; - localStorage.setItem(AppSettings.shushItemName, JSON.stringify(user)) - - //Force the user to the login page - let nav = this.app.getRootNav(); - nav.setRoot(AuthLoginPage); - - //Toast the user - this.appNotification.showToast('Unauthorized. Please log back in.'); - } else if (status == 404) { - - //Toast the user - this.appNotification.showToast('Could not be found. Please ensure your input is complete and correct.'); - - } else if (status == 500) { - //Internal Server Error - - //Toast the user - this.appNotification.showToast('Internal Server Error. Please try making the request again, or at a later time.'); - } else { - this.appNotification.showToast('Error ' + status + ': Please Contact Developers for help.'); - } - - - } - -} diff --git a/client/secureChatIonic/app/providers/app-messaging/app-messaging.ts b/client/secureChatIonic/app/providers/app-messaging/app-messaging.ts new file mode 100644 index 0000000..cd3d7b8 --- /dev/null +++ b/client/secureChatIonic/app/providers/app-messaging/app-messaging.ts @@ -0,0 +1,58 @@ +import { Injectable } from '@angular/core'; +import { Http, RequestOptions, Headers } from '@angular/http'; +import { Observable } from "rxjs/Observable"; +import 'rxjs/add/observable/interval'; +import 'rxjs/add/operator/switchMap'; +import 'rxjs/add/operator/map'; + + +//Import our providers +import { AppSettings } from '../../providers/app-settings/app-settings'; + +@Injectable() +export class AppMessaging { + + constructor(private http: Http, private appSettings: AppSettings) { } + + //Our conversations + conversations: any; + + //Return all the conversations for a user, for server polling + conversationRequest(token) { + + //Our headers + let headers = new Headers({ + access_token: token + }); + let options = new RequestOptions({ headers: headers }); + + //Continually poll the server in an interval to get messages + //Poll Interval from the App Settings + return this.http.get(AppSettings.serverUrl + 'conversation', options).map(res => res.json()); + } + + //Return all the conversations for a user, for server polling + conversationRequestPoll(token) { + + //Our headers + let headers = new Headers({ + access_token: token + }); + let options = new RequestOptions({ headers: headers }); + + //Continually poll the server in an interval to get messages + //Poll Interval from the App Settings + return Observable.interval(AppSettings.pollInterval) + .switchMap(() => this.http.get(AppSettings.serverUrl + 'conversation', options)) + .map(res => res.json()); + } + + //POST to the server a new message + conversationReply(payload) { + + //Update the conversation on the server + return this.http.put(AppSettings.serverUrl + 'conversation', payload).map(res => res.json()); + } + + +} diff --git a/client/secureChatIonic/app/providers/app-notification/app-notification.ts b/client/secureChatIonic/app/providers/app-notification/app-notification.ts deleted file mode 100644 index 6b7e0e1..0000000 --- a/client/secureChatIonic/app/providers/app-notification/app-notification.ts +++ /dev/null @@ -1,32 +0,0 @@ -import { Injectable } from '@angular/core'; -import { ToastController } from 'ionic-angular'; - -/* - Generated class for the AppNotification provider. - - See https://angular.io/docs/ts/latest/guide/dependency-injection.html - for more info on providers and Angular 2 DI. -*/ - -//Handle All Notifications to be shown to the user -@Injectable() -export class AppNotification { - - constructor(private toastCtrl: ToastController) { } - - //Show a Static toast - showToast(toastContent) { - //Ensure we aren't toasting nothing - if (!toastContent) return; - - let toast = this.toastCtrl.create({ - message: toastContent, - showCloseButton: true, - position: 'bottom', - duration: 2000, - closeButtonText: 'Ok' - }); - toast.present(); - } - -} diff --git a/client/secureChatIonic/app/providers/app-notify/app-notify.ts b/client/secureChatIonic/app/providers/app-notify/app-notify.ts new file mode 100644 index 0000000..3378ce0 --- /dev/null +++ b/client/secureChatIonic/app/providers/app-notify/app-notify.ts @@ -0,0 +1,116 @@ +import { Injectable, ViewChild } from '@angular/core'; +import { App, Nav, LoadingController, ToastController } from 'ionic-angular'; + +//Our Providers +import { AppSettings } from '../../providers/app-settings/app-settings'; +import { AppAuth } from '../../providers/app-auth/app-auth'; + +//Page to redirect to on 401 +import { Home } from '../../pages/home/home'; + +//Handle Async requests, loading spinners, Toasts, and general errors +@Injectable() +export class AppNotify { + + //Our Nav + @ViewChild(Nav) nav: Nav; + + //Our login pages + loginPage: any; + + //Our Loader + loader: any; + + //Our default loading string + defaultMessage: 'Loading, please wait...'; + + constructor(private app: App, private appAuth: AppAuth, private loadingCtrl: LoadingController, private toastCtrl: ToastController) { + } + + //Show a Static toast + showToast(toastContent) { + //Ensure we aren't toasting nothing + if (!toastContent) return; + + let toast = this.toastCtrl.create({ + message: toastContent, + showCloseButton: true, + position: 'bottom', + duration: 2000, + closeButtonText: 'Ok' + }); + toast.present(); + } + + //Function to start loading + startLoading(loadingString) { + //First make sure we stop loading + if (!loadingString) loadingString = this.defaultMessage; + this.loader = this.loadingCtrl.create({ + content: loadingString + }); + return this.loader.present(); + } + + //Function to stop Loading + stopLoading() { + return this.loader.dismiss(); + } + + //Function to handle Errors + handleError(error, expected?) { + + //TODO: Allow for overiding error codes, and using custom callbacks + + //Log the error + console.log(error); + + //Get our status + let status = error.status; + + //Check if we have any callbacks for specific error codes + if (expected) { + for (let i = 0; i < expected.length; ++i) { + if (expected[i].status == status) { + + //Launch the call abck and return + expected[i].callback(); + return; + } + + } + } + + if (status == 400) { + //400 Bad Request + this.showToast('Bad Request. Please ensure your input is correct.'); + } else if (status == 401) { + //401 Unauthorized + + //Logout + this.appAuth.logout(); + + //Redirect to home + let nav = this.app.getActiveNav(); + nav.setRoot(Home); + + //Toast the user + this.showToast('Unauthorized. Please log back in.'); + } else if (status == 404) { + + //Toast the user + this.showToast('Could not be found. Please ensure your input is complete and correct.'); + + } else if (status == 500) { + //Internal Server Error + + //Toast the user + this.showToast('Internal Server Error. Please try making the request again, or at a later time.'); + } else { + this.showToast('Error ' + status + ': Please Contact Developers for help.'); + } + + + } + +} diff --git a/client/secureChatIonic/app/providers/app-settings/app-settings.ts b/client/secureChatIonic/app/providers/app-settings/app-settings.ts index e354a1c..efc4be1 100644 --- a/client/secureChatIonic/app/providers/app-settings/app-settings.ts +++ b/client/secureChatIonic/app/providers/app-settings/app-settings.ts @@ -1,17 +1,34 @@ //Class to simply store app keys export class AppSettings { - //How to ignore changes on this file: http://stackoverflow.com/a/17410119 - //DO NOT UPLOAD ACTUAL KEYS TO GITHUB + //How to ignore changes on this file: http://stackoverflow.com/a/17410119 (If desired) - //Declare our keys - static testing = 'testing test'; - - //Fake key for now, to get the dialog working //Can be stored on the client: http://stackoverflow.com/questions/6709883/facebook-app-security-what-if-someone-uses-my-appid - static facebookAppId = '2867455650741445'; + static facebookAppId = '1867451080142485'; + + //Poll interval for things like settings + static pollInterval = 30000; + + //The name of the json object stored on the device + /* + schema + { + user: { + name:"" + email:"" + etc.... + } + keys: { + public: 'sdjlaksjda' + private: 'askjdklsjd' + }, + access_token: 'token' + } + */ + static shushItemName = 'shushUser'; - //Add our server URL - static serverUrl = 'http://localhost:4780'; + //Add our server URL (Local testing not actual) + //Must end with a slash + static serverUrl = 'http://localhost:4780/api/v1/'; } diff --git a/client/secureChatIonic/app/theme/app.core.scss b/client/secureChatIonic/app/theme/app.core.scss index f252325..825d28f 100644 --- a/client/secureChatIonic/app/theme/app.core.scss +++ b/client/secureChatIonic/app/theme/app.core.scss @@ -9,7 +9,7 @@ //Import our page scss @import "../pages/home/home"; -@import "../pages/all-messages/all-messages"; +@import "../pages/all-conversations/all-conversations"; @import "../pages/conversation/conversation"; @import "../pages/auth-login/auth-login";
- 12:2{{index}}PM + {{prettyDate(convoMessage.date)}}