UNPKG

@starbase/messages

Version:
1 lines 10.9 kB
"use strict";function Messages(encryption,db,serverURL,userData=null,sock=null,push=null,options={}){let serverIDK=null,user=null,opk=null,parentChannel="/messages";options&&options.parentChannel&&"string"==typeof options.parentChannel&&(parentChannel=db.path(options.parentChannel).channel());const REQUEST=async(method,payload)=>fetch(serverURL+"/"+method,{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify(payload)}).then(async response=>{var result=await response.json();if(399<response.statusCode)throw result;return result});const resetContact=async userID=>(await db.path(parentChannel).path("/contacts").path(userID).path("session").del(),await db.path(parentChannel).path("/contacts").path(userID).path("stale").del(),await sendMessage(userID,{reset:!0},!0).catch(err=>null),{reset:!0});const loadUser=async()=>{var exists;return userData&&(user=await encryption.loadUser(userData).catch(err=>null)),user||(exists=await db.path(parentChannel).path("/user").get().catch(err=>null))&&(user=await encryption.loadUser(exists.data)),user?await loadOPK():(user=await encryption.createUser(),await loadOPK(),await sendOPK()),await db.path(parentChannel).path("/user").put(user.save()),user},loadOPK=async()=>{var opkData=await db.path(parentChannel).path("/user/opk").get().catch(err=>null);return opkData?(opk=opkData.data,opkData.data):updateOPK()},updateOPK=async()=>(opk=await user.createOPK(),await db.path(parentChannel).path("/user/opk").put(opk),opk),getServerIDK=()=>serverIDK,getToken=async type=>{var tokenData=await db.path(parentChannel).path("/token").path(user.getID()).get().then(result=>result.data).catch(err=>null);return tokenData&&tokenData.decoded&&tokenData.decoded.exp>parseInt(Date.now()/1e3)?(serverIDK=tokenData.serverIDK,tokenData[type+"Token"]):hello().then(result=>result[type+"Token"]||null).catch(err=>{throw{error:"Could not obtain a token from the server."}})},sendOPK=async()=>{user||await loadUser(userData);var sealed=await getToken("user");if(!sealed)return Promise.reject({error:"Invalid or expired token."});sealed=await user.sealEnvelope(getServerIDK(),{opk:opk.card.opk,token:sealed});return await REQUEST("opk",sealed)};const getMessages=async(limit=100)=>{user||await loadUser(userData);var sealed=await getToken("user");if(!sealed)return Promise.reject({error:"Invalid or expired token."});var subscriptionExists=await db.path(parentChannel).path("/push").get().catch(err=>null);let sub=null;subscriptionExists&&(sub=subscriptionExists.data.subscription.keys.auth);sealed=await user.sealEnvelope(getServerIDK(),{token:sealed,limit:parseInt(limit),sub:sub});let response=await REQUEST("messages",sealed);if(response){let msgs=[],ids=[];for(let i=0;i<response.data.length;i++){response.data[i].data.timestamp=new Date(parseInt(response.data[i].key.slice(3,16))).getTime();var msg=await readMessage(response.data[i].data);ids.push(response.data[i].key),msg&&!msg.protocol&&msgs.push(msg)}return ids.length&&await(async(sealed=[])=>{user||await loadUser(userData);var token=await getToken("user");if(!token)return Promise.reject({error:"Invalid or expired token."});sealed=await user.sealEnvelope(getServerIDK(),{token:token,ids:sealed});return await REQUEST("acknowledge",sealed)})(ids),0<response.data.length&&response.data.length<parseInt(limit)&&opk.used&&await updateOPK(),await sendOPK(),msgs.length&&gotMessagesHandler&&"function"==typeof gotMessagesHandler&&gotMessagesHandler(!0),msgs}return Promise.reject({code:400,message:"Failed to open envelope."})};let saveHandler=null;let gotMessagesHandler=null;const saveReadMessage=async(contact,save,protocol=!1,readOnly=!1)=>{var msg={to:user.getID(),from:contact,plaintext:save.plaintext,timestamp:save.timestamp,status:"received"};if(protocol||readOnly)return readOnly?msg:protocol?(msg.plaintext&&msg.plaintext.reset&&(await db.path(parentChannel).path("/contacts").path(contact).path("session").del(),await db.path(parentChannel).path("/contacts").path(contact).path("stale").del()),null):void 0;save=await db.path(parentChannel).path("/contacts").path(contact).path("messages").path(save.timestamp).put(msg);return saveHandler&&"function"==typeof saveHandler&&saveHandler({path:save.path,msg:msg}),msg},readMessage=async(env,readOnly=!1)=>{let saved=null;var opened=await user.openEnvelope(env);if(opened.plaintext&&opened.plaintext.init&&opened.plaintext.init.from&&opened.from!==opened.plaintext.init.from)return null;var stale=await db.path(parentChannel).path("/contacts").path(opened.from).get().catch(err=>null);if(stale&&stale.data.blocked)return null;stale=await db.path(parentChannel).path("/contacts").path(opened.from).path("session").get().catch(err=>null);if(stale){let session=await user.loadSession(stale.data),read=await session.read(opened.plaintext).catch(err=>null);return read?(read.timestamp=env.timestamp,saved=await saveReadMessage(opened.from,read,env.protocol||!1,readOnly),env.protocol||await db.path(parentChannel).path("/contacts").path(opened.from).path("session").put(session.save())):(opened.plaintext.init?(env.protocol||await db.path(parentChannel).path("/contacts").path(opened.from).path("stale").put(session.save()),session=await user.openSession(opened.plaintext.init,opk.secret),opk.used=!0):(stale=await db.path(parentChannel).path("/contacts").path(opened.from).path("stale").get().catch(err=>null))&&(env.protocol||await db.path(parentChannel).path("/contacts").path(opened.from).path("stale").put(session.save()),session=await user.loadSession(stale.data)),read=await session.read(opened.plaintext).catch(err=>null),read&&(read.timestamp=env.timestamp,saved=await saveReadMessage(opened.from,read,env.protocol||!1,readOnly),env.protocol||await db.path(parentChannel).path("/contacts").path(opened.from).path("session").put(session.save()))),saved}{let session=null;if(opened.plaintext.init)session=await user.openSession(opened.plaintext.init,opk.secret),opk.used=!0;else{let stale=await db.path(parentChannel).path("/contacts").path(opened.from).path("stale").get().catch(err=>null);if(!stale)return await resetContact(opened.from).catch(err=>null),null;session=await user.loadSession(stale.data)}let read=await session.read(opened.plaintext).catch(err=>null);return read?(read.timestamp=env.timestamp,saved=await saveReadMessage(opened.from,read,env.protocol||!1,readOnly),env.protocol||await db.path(parentChannel).path("/contacts").path(opened.from).path("session").put(session.save())):await resetContact(opened.from).catch(err=>null),saved}};const sendMessage=async(to,msg,protocol=!1,composeOnly=!1)=>{user||await loadUser(userData);var token=await getToken("anon"),exists=await db.path(parentChannel).path("/contacts").path(to).path("session").get().catch(err=>null);let session=null;exists?session=await user.loadSession(exists.data):(send=await(async id=>{if(!user)await loadUser(userData);let token=await getToken("anon");return REQUEST("card",{token:token,msg:{id:id}}).catch(err=>{return null})})(to),session=await user.createSession(send),await db.path(parentChannel).path("/contacts").path(to).path("session").put(session.save()));var send=await session.send(msg);let sealed=await user.sealEnvelope(to,send);protocol&&(sealed.protocol=!0);let sent=!1;return sent=composeOnly?{timestamp:Date.now()}:await REQUEST("send",{token:token,msg:sealed}).catch(err=>null),sent?(await db.path(parentChannel).path("/contacts").path(to).path("session").put(session.save()),protocol||composeOnly||await db.path(parentChannel).path("/contacts").path(to).path("messages").path(sent.timestamp).put({to:to,from:user.getID(),plaintext:msg,timestamp:sent.timestamp,status:"sent"}),composeOnly?(sealed.timestamp=sent.timestamp,Promise.resolve(sealed)):Promise.resolve(sent)):Promise.reject({code:400,message:"Failed to send message!"})};const hello=async()=>{user||await loadUser(userData);var anonToken=await user.createOPK(),decoded=await REQUEST("hello",anonToken.card),opened=await user.openEnvelope(decoded);let userToken=opened.plaintext.userToken;anonToken=opened.plaintext.anonToken,decoded=JSON.parse(cryptic.toText(cryptic.decode(userToken.split(".")[1])));return serverIDK=opened.from,await db.path(parentChannel).path("/token").path(user.getID()).put({anonToken:anonToken,userToken:userToken,decoded:decoded,serverIDK:opened.from}),{anonToken:anonToken,userToken:userToken,decoded:decoded}};const unsubscribe=async e=>push?push.unsubscribe(serverURL+"/push/unsubscribe").then(async result=>db.path(parentChannel).path("/push").del().then(deleted=>({unsubscribed:!0}))):{supported:!1};return{getMessages:getMessages,readMessage:readMessage,sendMessage:sendMessage,getID:()=>user.getID(),getToken:getToken,listContacts:async query=>db.path(parentChannel).path("/contacts").list(query),updateContact:async(userID,profile={},unread=!1)=>{var exists=await db.path(parentChannel).path("/contacts").path(userID).get().catch(err=>null);let updated=profile||{};return updated.timestamp=Date.now(),updated.unread=unread||!1,updated.userID=userID,exists&&exists.data&&(!updated.name&&exists.data.name&&(updated.name=exists.data.name),!updated.photo&&exists.data.photo&&(updated.photo=exists.data.photo)),db.path(parentChannel).path("/contacts").path(userID).put(updated)},resetContact:resetContact,deleteContact:async userID=>(await messages.resetContact(userID).catch(err=>null),await db.path(parentChannel).path("/contacts").path(userID).del(),!0),listMessages:async(userID,query)=>db.path(parentChannel).path("/contacts").path(userID).path("messages").list(query),clearConversation:async userID=>!!userID&&(await db.path(parentChannel).path("/contacts").path(userID).path("messages").del(),!0),deleteMe:async()=>{user||await loadUser(userData);var response=await getToken("user");if(!response)return Promise.reject({error:"Invalid or expired token."});response=await user.sealEnvelope(getServerIDK(),{delete:user.getID(),token:response}),response=await REQUEST("deleteme",response);return await unsubscribe().catch(err=>null),await db.path(parentChannel).del(),response},onSave:cb=>{saveHandler=cb},connect:async()=>{sock&&(sock.onState(async s=>{"connected"===s&&sock.send({id:user.getID(),token:await getToken("user")})}),sock.onMessage(async()=>{await getMessages()}),sock.getState()||sock.connect())},subscribe:async e=>{return push?push.getPermission().then(result=>"granted"===result).catch(err=>!1)?push.subscribe(serverURL+"/push/subscribe").then(async result=>db.path(parentChannel).path("/push").put({subscription:result.subscription}).then(saved=>({subscribed:!0}))):null:{supported:!1}},unsubscribe:unsubscribe,getContact:async userID=>db.path(parentChannel).path("/contacts").path(userID).get().then(result=>result.data).catch(err=>null),isSubscribed:async()=>db.path(parentChannel).path("/push").get().then(result=>!0).catch(err=>!1),onGotMessages:cb=>{gotMessagesHandler=cb},db:db}}