UNPKG

globular-mvc

Version:

Generic template to create web-application that made use of globular as backend and materialize as css (wrap in web-component's)

829 lines (703 loc) 30.9 kB
import { Model, generatePeerToken } from "./Model"; import { FindOneRqst, FindResp, FindRqst, ReplaceOneRqst, ReplaceOneRsp } from "globular-web-client/persistence/persistence_pb"; import * as ResourceService from "globular-web-client/resource/resource_pb"; import { mergeTypedArrays, uint8arrayToStringMethod } from "./Utility"; import { Group } from "./Group"; import { Session } from "./Session" import { ApplicationView } from "./ApplicationView"; import { Application } from "./Application"; import * as jwt from "jwt-decode"; import { Globular } from "globular-web-client"; /** * Basic account class that contain the user id and email. */ export class Account extends Model { private static listeners: any; private static accounts: any; private groups_: Array<any>; // keep the session information. private session_: Session; public get session(): Session { return this.session_; } public set session(value: Session) { this.session_ = value; } // The domain where the account came from. private _domain: string; public get domain(): string { return this._domain; } public set domain(value: string) { this._domain = value; } // Must be unique private _id: string; public get id(): string { return this._id; } public set id(value: string) { this._id = value; } private name_: string; public get name(): string { return this.name_; } public set name(value: string) { this.name_ = value; } // Must be unique. private email_: string; public get email(): string { return this.email_; } public set email(value: string) { this.email_ = value; } // complementary information. private hasData: boolean; // The user profile picture. private profilePicture_: string; public get profilePicture(): string { return this.profilePicture_; } public set profilePicture(value: string) { this.profilePicture_ = value; } // The ringtone for that user... private ringtone_: string; public get ringtone(): string { return this.ringtone_; } public set ringtone(value: string) { this.ringtone_ = value; } // The user firt name private firstName_: string; public get firstName(): string { return this.firstName_; } public set firstName(value: string) { this.firstName_ = value; } // The user last name private lastName_: string; public get lastName(): string { return this.lastName_; } public set lastName(value: string) { this.lastName_ = value; } // The user middle name. private middleName_: string; public get middleName(): string { return this.middleName_; } public set middleName(value: string) { this.middleName_ = value; } public get userName(): string { let name = this.firstName; if (this.middleName.length > 0) { name += " " + this.middleName; } return name + " " + this.lastName; } constructor(id: string, email: string, name: string, domain: string, firstName: string, lastName: string, middleName: string, profilePicture: string) { super(); this._id = id; this.name_ = name; this.domain = domain; this.email_ = email; this.hasData = false; this.firstName_ = firstName; this.lastName_ = lastName; this.middleName_ = middleName; this.profilePicture_ = profilePicture } /** * Get an account with a given id. * @param id The id of the account to retreive * @param successCallback Callback when succed * @param errorCallback Error Callback. */ public static getAccount(id: string, successCallback: (account: Account) => void, errorCallback: (err: any) => void) { if (id.length == 0) { errorCallback("No account id given to getAccount function!") return } if (Account.accounts == null) { // Set the accouts map Account.accounts = {} } let accountId = id let domain = Model.domain if (accountId.indexOf("@") == -1) { accountId = id + "@" + domain } else { domain = accountId.split("@")[1] id = accountId.split("@")[0] } if (Account.accounts[accountId] != null) { if (Account.accounts[accountId].session != null) { successCallback(Account.accounts[accountId]); return } } let globule = Model.getGlobule(domain) if (!globule) { let jsonStr = localStorage.getItem(accountId) if(jsonStr){ let account = Account.fromString(jsonStr) successCallback(account) }else{ errorCallback("no globule was found at domain " + domain) } return } generatePeerToken(globule, token => { let rqst = new ResourceService.GetAccountsRqst rqst.setQuery(`{"$or":[{"_id":"${id}"},{"name":"${id}"} ]}`); // search by name and not id... the id will be retreived. rqst.setOptions(`[{"Projection":{"_id":1, "email":1, "name":1, "groups":1, "organizations":1, "roles":1, "domain":1}}]`); let globule = Model.getGlobule(domain) if (globule) { let stream = globule.resourceService.getAccounts(rqst, { domain: domain, application: Model.application, token: token }) let data: ResourceService.Account; stream.on("data", (rsp) => { if (!data) { data = rsp.getAccountsList().pop() } }); stream.on("status", (status) => { if (status.code == 0) { // so here I will get the session for the account... if (Account.accounts[accountId] != null) { if (Account.accounts[accountId].session != null) { successCallback(Account.accounts[accountId]); return } } // Initialyse the data... if (!data) { errorCallback("no account found with id " + accountId) return } let account = new Account(data.getId(), data.getEmail(), data.getName(), data.getDomain(), data.getFirstname(), data.getLastname(), data.getMiddle(), data.getProfilepicture()) account.session = new Session(account) Account.accounts[accountId] = account; account.session.initData(() => { // here I will initialyse groups... account.groups_ = data.getGroupsList(); if(!Application.account){ successCallback(account) return } if (account.id == Application.account.id) { account.initData(() => { successCallback(account) }, errorCallback) } else { // I will keep the account in the cache... localStorage.setItem(account.id + "@" + account.domain, account.toString()) successCallback(account) } }, errorCallback) } else { let jsonStr = localStorage.getItem(accountId) if(jsonStr){ let account = Account.fromString(jsonStr) successCallback(account) }else{ errorCallback(status.details); } } }) } }, err => ApplicationView.displayMessage(err, 3000)) } /** * Initialyse account groups. * @param obj The account data from the persistence store. * @param successCallback * @param errorCallback */ public getGroups(successCallback: (groups: Array<Group>) => void) { let groups_ = new Array<Group>(); if (this.groups_ == undefined) { successCallback([]) return } if (this.groups_.length == 0) { successCallback([]) return } // Initi the group. let setGroup_ = (index: number) => { if (index < this.groups_.length) { let groupId = this.groups_[index]["$id"] Group.getGroup(groupId, (g: Group) => { groups_.push(g) index++ setGroup_(index); }, () => { index++ if (index < this.groups_.length) { setGroup_(index); } else { successCallback(groups_); } }) } else { successCallback(groups_); } } // start the recursion. setGroup_(0) } // Test if a account is member of a given group. isMemberOf(id: string): boolean { this.groups_.forEach((g: any) => { if (g._id == id) { // be sure the account is in the group reference list... return g.hasMember(this) } }) return false; } public static setAccount(a: Account) { if (Account.accounts == null) { Account.accounts = [] } Account.accounts[a.id + "@" + a.domain] = a; } private static getListener(id: string) { if (Account.listeners == undefined) { return null; } return Account.listeners[id]; } // Keep track of the listener. private static setListener(id: string, domain: string, uuid: string) { if (Account.listeners == undefined) { Account.listeners = {}; } Account.listeners[id + "@" + domain] = uuid; return } private static unsetListener(id: string, domain: string) { let uuid = Account.getListener(id + "@" + domain); if (uuid != null) { Model.getGlobule(domain).eventHub.unSubscribe(`update_account_${id + "@" + domain}_data_evt`, uuid); } } /** * Read user data one result at time. */ private static readOneUserData( query: string, userName: string, userDomain: string, successCallback: (results: any) => void, errorCallback: (err: any) => void ) { let rqst = new FindOneRqst(); // remove unwanted characters let id = userName.split("@").join("_").split(".").join("_") let db = id + "_db"; // set the connection id. rqst.setId(id); rqst.setDatabase(db); let collection = "user_data"; rqst.setCollection(collection); rqst.setQuery(query); rqst.setOptions(""); let globule = Model.getGlobule(userDomain) generatePeerToken(globule, token => { // call persist data Model.getGlobule(userDomain).persistenceService .findOne(rqst, { token: token, application: Model.application, domain: Model.domain // the domain at the origin of the request. }) .then((rsp: any) => { let data = rsp.getResult().toJavaScript(); successCallback(data); }) .catch((err: any) => { if (err.code == 13) { if (Application.account == null) { ApplicationView.displayMessage("no connection found on the server you need to login", 3000) setTimeout(() => { localStorage.removeItem("remember_me"); localStorage.removeItem("user_token"); localStorage.removeItem("user_id"); localStorage.removeItem("user_name"); localStorage.removeItem("user_email"); localStorage.removeItem("token_expired"); location.reload(); return; }, 3000) } if (Application.account.id == id) { if (err.message.indexOf("no documents in result") != -1) { successCallback({}); } else { ApplicationView.displayMessage("no connection found on the server you need to login", 3000) setTimeout(() => { localStorage.removeItem("remember_me"); localStorage.removeItem("user_token"); localStorage.removeItem("user_id"); localStorage.removeItem("user_name"); localStorage.removeItem("user_email"); localStorage.removeItem("token_expired"); location.reload(); return; }, 3000) } } else { successCallback({}); } } else { errorCallback(err); } }); }, err => ApplicationView.displayMessage(err, 3000)) } private setData(data: any) { this.hasData = true; this.firstName = data["firstName_"]; if (this.firstName == undefined) { this.firstName = "" } this.lastName = data["lastName_"]; if (this.lastName == undefined) { this.lastName = "" } this.middleName = data["middleName_"]; if (this.middleName == undefined) { this.middleName = ""; } if (data["profilePicture_"] != undefined) { this.profilePicture = data["profilePicture_"]; // keep the user data into the localstore. localStorage.setItem(this.id + "@" + this.domain, JSON.stringify(data)) } } /** * Must be called once when the session open. * @param account */ initData(callback: (account: Account) => void, onError: (err: any) => void) { let userName = this.name if (this.hasData == true) { return this } // Retreive user data... Account.readOneUserData( `{"$or":[{"_id":"${this.id}"},{"name":"${this.id}"} ]}`, // The query is made on the user database and not local_ressource Accounts here so name is name_ here userName, // The database to search into this.domain, (data: any) => { if (Object.keys(data).length == 0) { if (localStorage.getItem(this.id) != undefined) { data = JSON.parse(localStorage.getItem(this.id)); this.setData(data); } } else { this.setData(data); } // Here I will keep the Account up-to date. if (Account.getListener(this.id) == undefined) { // Here I will connect the objet to keep track of accout data change. Model.getGlobule(this.domain).eventHub.subscribe(`update_account_${this.id + "@" + this.domain}_data_evt`, (uuid: string) => { Account.setListener(this.id, this.domain, uuid); }, (str: string) => { let data = JSON.parse(str); this.setData(data); // refresh data. // Here I will rethrow the event locally... Model.eventHub.publish(`__update_account_${this.id + "@" + this.domain}_data_evt__`, data, true); }, false, this) } // Keep in the local map... Account.setAccount(this) // Init list of contacts. Account.getContacts(this, `{}`, (contacts: []) => { // Set the list of contacts (received invitation, sent invitation and actual contact id's) callback(this); }, onError) }, (err: any) => { this.hasData = false; if (localStorage.getItem(this.id) != undefined) { this.setData(JSON.parse(localStorage.getItem(this.id))) callback(this); return } // Call success callback ... if (callback != undefined && this.session != null) { this.session.initData(() => { callback(this); }, onError) callback(this); } } ); } /** * Change the user profil picture... * @param dataUrl The data url of the new profile picture. * @param onSaveAccount The success callback * @param onError The error callback */ changeProfilImage( dataUrl: string ) { this.profilePicture_ = dataUrl; } /** * Save user data into the user_data collection. Insert one or replace one depending if values * are present in the firstName and lastName. */ save( callback: (account: Account) => void, onError: (err: any) => void ) { let userName = this.name; // save the user_data let rqst = new ReplaceOneRqst(); let id = userName.split("@").join("_").split(".").join("_"); let db = id + "_db"; // set the connection infos, rqst.setId(id); rqst.setDatabase(db); let collection = "user_data"; // save only user data and not the how user info... let data = this.toString(); rqst.setCollection(collection); rqst.setQuery(`{"$or":[{"_id":"${this.id}"},{"name":"${this.id}"} ]}`); rqst.setValue(data); rqst.setOptions(`[{"upsert": true}]`); // So here I will set the address from the address found in the token and not // the address of the client itself. let token = localStorage.getItem("user_token") // call persist data Model.getGlobule(this.domain).persistenceService .replaceOne(rqst, { token: token, application: Model.application, domain: Model.domain }) .then((rsp: ReplaceOneRsp) => { // Here I will return the value with it Model.publish(`update_account_${this.id + "@" + this.domain}_data_evt`, data, false) callback(this); }) .catch((err: any) => { onError(err); }); } toString(): string { return JSON.stringify({ _id: this.id, email_: this.email, firstName_: this.firstName, lastName_: this.lastName, middleName_: this.middleName, profilePicture_: this.profilePicture, domain_:this.domain }); } static fromObject(obj: any): any { return new Account(obj._id, obj.email_, obj.name_, obj.domain_, obj.firstName_, obj.lastName_, obj.middleName_, obj.profilePicture_) } static fromString(jsonStr:string):any{ return Account.fromObject(JSON.parse(jsonStr)) } getId(): string { return this._id } getDomain(): string { return this.domain } getName(): string { return this.name_ } static getContacts(account: Account, query: string, callback: (contacts: Array<any>) => void, errorCallback: (err: any) => void) { // Insert the notification in the db. let rqst = new FindRqst(); // set connection infos. let id = account.name.split("@").join("_").split(".").join("_") let db = id + "_db"; rqst.setId(id); rqst.setDatabase(db); rqst.setCollection("Contacts"); rqst.setQuery(query); let token = localStorage.getItem("user_token") let globule = Model.getGlobule(account.domain) let stream = globule.persistenceService.find(rqst, { token: token, application: Model.application, domain: Model.domain }); let data: any; data = []; stream.on("data", (rsp: FindResp) => { data = mergeTypedArrays(data, rsp.getData()); }); stream.on("status", (status) => { if (status.code == 0) { uint8arrayToStringMethod(data, (str: string) => { callback(JSON.parse(str)); }); } else { console.log("fail to retreive contacts with error: ", status.details) // In case of error I will return an empty array callback([]); } }); } public static setContact(from: Account, status_from: string, to: Account, status_to: string, successCallback: () => void, errorCallback: (err: any) => void) { // So here I will save the contact invitation into pending contact invitation collection... let rqst = new ResourceService.SetAccountContactRqst rqst.setAccountid(from.id + "@" + from.domain) let contact = new ResourceService.Contact contact.setId(to.id + "@" + to.domain) contact.setStatus(status_from) contact.setInvitationtime(Math.round(Date.now() / 1000)) // Set optional values... if (to.ringtone) contact.setRingtone(to.ringtone) if (to.profilePicture) contact.setProfilepicture(to.profilePicture) rqst.setContact(contact) let token = localStorage.getItem("user_token") let globule = Model.getGlobule(from.domain) globule.resourceService.setAccountContact(rqst, { token: token, application: Model.application, domain: Model.domain }) .then((rsp: ResourceService.SetAccountContactRsp) => { let sentInvitation = `{"_id":"${to.id + "@" + to.domain}", "invitationTime":${Math.floor(Date.now() / 1000)}, "status":"${status_from}"}` Model.getGlobule(from.domain).eventHub.publish(status_from + "_" + from.id + "@" + from.domain + "_evt", sentInvitation, false) if (from.domain != to.domain) { Model.getGlobule(to.domain).eventHub.publish(status_from + "_" + from.id + "@" + from.domain + "_evt", sentInvitation, false) } // Here I will return the value with it let rqst = new ResourceService.SetAccountContactRqst rqst.setAccountid(to.id + "@" + to.domain) let contact = new ResourceService.Contact contact.setId(from.id + "@" + from.domain) contact.setStatus(status_to) contact.setInvitationtime(Math.round(Date.now() / 1000)) rqst.setContact(contact) let token = localStorage.getItem("user_token") // call persist data Model.getGlobule(to.domain).resourceService .setAccountContact(rqst, { token: token, application: Model.application, domain: Model.domain }) .then((rsp: ReplaceOneRsp) => { // Here I will return the value with it let receivedInvitation = `{"_id":"${from.id + "@" + from.domain}", "invitationTime":${Math.floor(Date.now() / 1000)}, "status":"${status_to}"}` Model.getGlobule(from.domain).eventHub.publish(status_to + "_" + to.id + "@" + to.domain + "_evt", receivedInvitation, false) if (from.domain != to.domain) { Model.getGlobule(to.domain).eventHub.publish(status_to + "_" + to.id + "@" + to.domain + "_evt", receivedInvitation, false) } successCallback(); }) .catch(errorCallback); }).catch(errorCallback); } // Get all accounts from all globules... static getAccounts(query: string, callback: (accounts: Array<Account>) => void, errorCallback: (err: any) => void) { let accounts_ = new Array<Account>() let connections = Model.getGlobules() let _getAccounts_ = () => { let globule = connections.pop() if (connections.length == 0) { Account._getAccounts(globule, query, (accounts: Array<Account>) => { for (var i = 0; i < accounts.length; i++) { let a = accounts[i] if (accounts_.filter(a_ => { return a.id == a_.id && a.domain == a_.domain; }).length == 0) { accounts_.push(a) } } callback(accounts_) }, errorCallback) } else { Account._getAccounts(globule, query, (accounts: Array<Account>) => { for (var i = 0; i < accounts.length; i++) { let a = accounts[i] if (accounts_.filter(a_ => { return a.id == a_.id && a.domain == a_.domain; }).length == 0) { accounts_.push(a) } } _getAccounts_() // get the account from the next globule. }, errorCallback) } } // get account from all register peers. _getAccounts_() } // Get all account data from a give globule... private static _getAccounts(globule: Globular, query: string, callback: (accounts: Array<Account>) => void, errorCallback: (err: any) => void) { let rqst = new ResourceService.GetAccountsRqst rqst.setQuery(query) let stream = globule.resourceService.getAccounts(rqst, { domain: Model.domain, application: Model.application, token: localStorage.getItem("user_token") }) let accounts_ = new Array<ResourceService.Account>(); stream.on("data", (rsp) => { accounts_ = accounts_.concat(rsp.getAccountsList()) }); stream.on("status", (status) => { if (status.code == 0) { let accounts = new Array<Account>(); if (accounts_.length == 0) { callback(accounts); return; } // In that case I will return the list of account without init ther data if (query == "{}") { accounts_.forEach(a_ => { if (Account.accounts[a_.getId() + "@" + a_.getDomain()] != undefined) { accounts.push(Account.accounts[a_.getId() + "@" + a_.getDomain()]) } else { let account = new Account(a_.getId(), a_.getEmail(), a_.getName(), a_.getDomain(), a_.getFirstname(), a_.getLastname(), a_.getMiddle(), a_.getProfilepicture()) accounts.push(account) } }) callback(accounts) return } let initAccountData = () => { let a_ = accounts_.pop() if (Account.accounts[a_.getId() + "@" + a_.getDomain()] == undefined) { let a = new Account(a_.getId(), a_.getEmail(), a_.getName(), a_.getDomain(), a_.getFirstname(), a_.getLastname(), a_.getMiddle(), a_.getProfilepicture()) if (accounts_.length > 0) { a.initData(() => { accounts.push(a) initAccountData() }, errorCallback) } else { a.initData( () => { accounts.push(a) callback(accounts) }, errorCallback) } } else { accounts.push(Account.accounts[a_.getId() + "@" + a_.getDomain()]) if (accounts_.length > 0) { initAccountData() } else { callback(accounts) } } } // intialyse the account data. initAccountData(); } else { // In case of error I will return an empty array errorCallback(status.details) } }); } }