Merge branch 'master' into lf/chat-bottom-scroll

This commit is contained in:
Liam Fitzgerald 2020-05-05 16:13:36 +10:00
commit 23eb4acc37
47 changed files with 1012 additions and 132 deletions

View File

@ -226,8 +226,6 @@
::
++ catch-up
^- (quip card _state)
?. .^(? %gu /(scot %p our.bowl)/chat-store/(scot %da now.bowl))
[~ state]
=/ =inbox
(scry-for inbox %chat-store /all)
|- ^- (quip card _state)

Binary file not shown.

Before

Width:  |  Height:  |  Size: 498 B

After

Width:  |  Height:  |  Size: 596 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 865 B

View File

@ -26,5 +26,6 @@
<script src="/~channel/channel.js"></script>
<script src="/~modulo/session.js"></script>
<script src="/~chat/js/index.js"></script>
<script src="https://sdk.amazonaws.com/js/aws-sdk-2.1.12.min.js"></script>
</body>
</html>

Binary file not shown.

After

Width:  |  Height:  |  Size: 865 B

View File

@ -13,5 +13,6 @@
<script src="/~channel/channel.js"></script>
<script src="/~modulo/session.js"></script>
<script src="/~groups/js/index.js"></script>
<script src="https://sdk.amazonaws.com/js/aws-sdk-2.1.12.min.js"></script>
</body>
</html>

View File

@ -43,9 +43,9 @@
!:
=> |% ::
++ hood-old :: unified old-state
{?($1 $2 $3) lac/(map @tas hood-part-old)} ::
{?($1 $2 $3 $4) lac/(map @tas hood-part-old)} ::
++ hood-1 :: unified state
{$3 lac/(map @tas hood-part)} ::
{$4 lac/(map @tas hood-part)} ::
++ hood-good :: extract specific
=+ hed=$:hood-head
|@ ++ $
@ -140,7 +140,7 @@
`..on-init
::
++ on-save
!>([%3 lac])
!>([%4 lac])
::
++ on-load
|= =old-state=vase
@ -150,7 +150,8 @@
?- -.old-state
%1 ((wrap on-load):from-drum:(help hid) %1)
%2 ((wrap on-load):from-drum:(help hid) %2)
%3 `lac
%3 ((wrap on-load):from-drum:(help hid) %3)
%4 `lac
==
[cards ..on-init]
::

View File

@ -178,8 +178,11 @@ class Channel {
this.lastEventId = e.lastEventId;
let obj = JSON.parse(e.data);
if (obj.response == "poke") {
let funcs = this.outstandingPokes.get(obj.id);
let pokeFuncs = this.outstandingPokes.get(obj.id);
let subFuncs = this.outstandingSubscriptions.get(obj.id);
if (obj.response == "poke" && !!pokeFuncs) {
let funcs = pokeFuncs;
if (obj.hasOwnProperty("ok")) {
funcs["success"]();
} else if (obj.hasOwnProperty("err")) {
@ -189,19 +192,20 @@ class Channel {
}
this.outstandingPokes.delete(obj.id);
} else if (obj.response == "subscribe") {
} else if (obj.response == "subscribe" ||
(obj.response == "poke" && !!subFuncs)) {
let funcs = subFuncs;
// on a response to a subscribe, we only notify the caller on err
//
let funcs = this.outstandingSubscriptions.get(obj.id);
if (obj.hasOwnProperty("err")) {
funcs["err"](obj.err);
this.outstandingSubscriptions.delete(obj.id);
}
} else if (obj.response == "diff") {
let funcs = this.outstandingSubscriptions.get(obj.id);
let funcs = subFuncs;
funcs["event"](obj.json);
} else if (obj.response == "quit") {
let funcs = this.outstandingSubscriptions.get(obj.id);
let funcs = subFuncs;
funcs["quit"](obj);
this.outstandingSubscriptions.delete(obj.id);
} else {

View File

@ -114,8 +114,6 @@
`t.t.path
~
?~ target |
~? !.^(? %gu (scot %p our.bowl) %metadata-store (scot %da now.bowl) ~)
%woah-md-s-not-booted ::TODO fallback if needed
%+ lien (groups-from-resource:md %link u.target)
|= =group-path
^- ?

View File

@ -331,9 +331,16 @@
?+ mar (on-poke:def mar vas)
::
%noun
?: =(q.vas %flush-limbo)
[~ this(limbo [~ ~])]
[~ this]
?+ q.vas
[~ this]
::
%flush-limbo [~ this(limbo [~ ~])]
::
%reset-warp
=/ rav [%sing %t [%da now.bol] /app/publish/notebooks]
:_ this
[%pass /read/paths %arvo %c %warp our.bol q.byk.bol `rav]~
==
::
%handle-http-request
=+ !<([id=@ta req=inbound-request:eyre] vas)
@ -1857,8 +1864,9 @@
::
%remove
=/ app-path [(scot %p author.del) /[book.del]]
=/ group-path (group-from-book app-path)
[(metadata-poke [%remove group-path [%publish app-path]])]~
=/ group-path=(unit path) (group-from-book app-path)
?~ group-path ~
[(metadata-poke [%remove u.group-path [%publish app-path]])]~
==
::
++ add
@ -1884,13 +1892,12 @@
::
++ group-from-book
|= app-path=path
^- path
^- (unit path)
?. .^(? %gu (scot %p our.bol) %metadata-store (scot %da now.bol) ~)
?: ?=([@ ^] app-path)
~& [%assuming-ported-legacy-publish app-path]
[%'~' app-path]
~| [%weird-publish app-path]
!!
`[%'~' app-path]
~&([%weird-publish app-path] ~)
=/ resource-indices
.^ (jug resource group-path)
%gy
@ -1899,8 +1906,12 @@
(scot %da now.bol)
/resource-indices
==
=/ groups=(set path) (~(got by resource-indices) [%publish app-path])
(snag 0 ~(tap in groups))
=/ groups=(unit (set path))
(~(get by resource-indices) [%publish app-path])
?~ groups ~
=/ group-paths ~(tap in u.groups)
?~ group-paths ~
`i.group-paths
--
::
++ metadata-hook-poke

View File

@ -0,0 +1,96 @@
/- *s3
/+ s3-json, default-agent, verb, dbug
~% %s3-top ..is ~
|%
+$ card card:agent:gall
+$ versioned-state
$% state-zero
==
::
+$ state-zero [%0 =credentials =configuration]
--
::
=| state-zero
=* state -
::
%- agent:dbug
%+ verb |
^- agent:gall
~% %s3-agent-core ..card ~
|_ =bowl:gall
+* this .
def ~(. (default-agent this %|) bowl)
::
++ on-init on-init:def
++ on-save !>(state)
++ on-load
|= old-vase=vase
[~ this(state !<(state-zero old-vase))]
::
++ on-poke
~/ %s3-poke
|= [=mark =vase]
^- (quip card _this)
|^
?> (team:title our.bowl src.bowl)
=^ cards state
?+ mark (on-poke:def mark vase)
%s3-action (poke-action !<(action vase))
==
[cards this]
::
++ poke-action
|= act=action
^- (quip card _state)
:- [%give %fact [/all]~ %s3-update !>(act)]~
?- -.act
%set-endpoint
state(endpoint.credentials endpoint.act)
::
%set-access-key-id
state(access-key-id.credentials access-key-id.act)
::
%set-secret-access-key
state(secret-access-key.credentials secret-access-key.act)
::
%set-current-bucket
%_ state
current-bucket.configuration bucket.act
buckets.configuration (~(put in buckets.configuration) bucket.act)
==
::
%add-bucket
state(buckets.configuration (~(put in buckets.configuration) bucket.act))
::
%remove-bucket
state(buckets.configuration (~(del in buckets.configuration) bucket.act))
==
--
::
++ on-watch
~/ %s3-watch
|= =path
^- (quip card _this)
|^
?> (team:title our.bowl src.bowl)
=/ cards=(list card)
?+ path (on-watch:def path)
[%all ~]
:~ (give %s3-update !>([%credentials credentials]))
(give %s3-update !>([%configuration configuration]))
==
==
[cards this]
::
++ give
|= =cage
^- card
[%give %fact ~ cage]
--
::
++ on-leave on-leave:def
++ on-peek on-peek:def
++ on-agent on-agent:def
++ on-arvo on-arvo:def
++ on-fail on-fail:def
--

View File

@ -0,0 +1,10 @@
:: s3-store|add-bucket: add new bucket to S3 store
::
/- *s3
:- %say
|= $: [now=@da eny=@uvJ =beak]
[[bucket=@t ~] ~]
==
:- %s3-action
^- action
[%add-bucket bucket]

View File

@ -0,0 +1,10 @@
:: s3-store|remove-bucket: remove bucket from S3 store
::
/- *s3
:- %say
|= $: [now=@da eny=@uvJ =beak]
[[bucket=@t ~] ~]
==
:- %s3-action
^- action
[%remove-bucket bucket]

View File

@ -0,0 +1,10 @@
:: s3-store|set-access-key-id: set S3 access key ID
::
/- *s3
:- %say
|= $: [now=@da eny=@uvJ =beak]
[[access-key-id=@t ~] ~]
==
:- %s3-action
^- action
[%set-access-key-id access-key-id]

View File

@ -0,0 +1,10 @@
:: s3-store|set-current-bucket: set current bucket for S3
::
/- *s3
:- %say
|= $: [now=@da eny=@uvJ =beak]
[[bucket=@t ~] ~]
==
:- %s3-action
^- action
[%set-current-bucket bucket]

View File

@ -0,0 +1,10 @@
:: s3-store|set-endpoint: set S3 endpoint
::
/- *s3
:- %say
|= $: [now=@da eny=@uvJ =beak]
[[endpoint=@t ~] ~]
==
:- %s3-action
^- action
[%set-endpoint endpoint]

View File

@ -0,0 +1,10 @@
:: s3-store|set-secret-access-key: set S3 secret access key
::
/- *s3
:- %say
|= $: [now=@da eny=@uvJ =beak]
[[secret-access-key=@t ~] ~]
==
:- %s3-action
^- action
[%set-secret-access-key secret-access-key]

View File

@ -118,6 +118,7 @@
%link-view
%metadata-store
%metadata-hook
%s3-store
==
::
++ deft-fish :: default connects
@ -155,6 +156,7 @@
=+ (~(gut by bin) ost *source)
=* dev -
|_ {moz/(list card:agent:gall) biz/(list dill-blit:dill)}
+* this .
++ diff-sole-effect-phat :: app event
|= {way/wire fec/sole-effect}
=< se-abet =< se-view
@ -223,7 +225,7 @@
==
::
++ on-load
|= ver=?(%1 %2)
|= ver=?(%1 %2 %3)
?- ver
%1
=< se-abet =< se-view
@ -237,7 +239,8 @@
=< (se-born %home %link-store)
=< (se-born %home %link-proxy-hook)
=< (se-born %home %link-listen-hook)
(se-born %home %link-view)
=< (se-born %home %link-view)
(se-born %home %s3-store)
::
%2
=< se-abet =< se-view
@ -250,7 +253,22 @@
=< (se-born %home %link-store)
=< (se-born %home %link-proxy-hook)
=< (se-born %home %link-listen-hook)
(se-born %home %link-view)
=< (se-born %home %link-view)
(se-born %home %s3-store)
::
%3
=< se-abet =< se-view
=< (se-emit %pass /kiln %arvo %g %sear ~wisrut-nocsub)
=< (se-born %home %metadata-store)
=< (se-born %home %metadata-hook)
=< (se-born %home %contact-store)
=< (se-born %home %contact-hook)
=< (se-born %home %contact-view)
=< (se-born %home %link-store)
=< (se-born %home %link-proxy-hook)
=< (se-born %home %link-listen-hook)
=< (se-born %home %link-view)
(se-born %home %s3-store)
==
::
++ reap-phat :: ack connect
@ -329,23 +347,70 @@
[%give %fact ~[/drum] %dill-blit !>(dill-blit)]
::
++ se-adit :: update servers
^+ .
:: ensure dojo connects after talk
=* dojo-on-top |=([a=* b=*] |(=(%dojo a) &(!=(%dojo b) (aor a b))))
%+ roll (sort ~(tap in ray) dojo-on-top)
=< .(con +>)
|: $:{wel/well:gall con/_..se-adit} ^+ con
=. +>.$ con
=+ hig=(~(get by fur) q.wel)
?: &(?=(^ hig) |(?=(~ u.hig) =(p.wel syd.u.u.hig))) +>.$
=. +>.$ (se-text "activated app {(trip p.wel)}/{(trip q.wel)}")
%- se-emit(fur (~(put by fur) q.wel ~))
^+ this
|^
=/ servers=(list well:gall)
(sort ~(tap in ray) sort-by-priorities)
|-
?~ servers
this
=/ wel=well:gall
i.servers
=/ =wire [%drum p.wel q.wel ~]
[%pass wire %arvo %g %conf [our.hid q.wel] our.hid p.wel]
=/ hig=(unit (unit server))
(~(get by fur) q.wel)
?: &(?=(^ hig) |(?=(~ u.hig) =(p.wel syd.u.u.hig)))
this
=. fur
(~(put by fur) q.wel ~)
=. this
(se-text "activated app {(trip p.wel)}/{(trip q.wel)}")
=. this
%- se-emit
[%pass wire %arvo %g %conf [our.hid q.wel] our.hid p.wel]
$(servers t.servers)
::
++ priorities
^- (list (set @))
:~
:: set up stores with priority: depended on, but never depending
%- sy
:~ %permission-store
%chat-store
%contact-store
%group-store
%link-store
%invite-store
%metadata-store
==
:: ensure chat-cli can sub to invites
(sy ~[%chat-hook])
==
++ sort-by-priorities
=/ priorities priorities
|= [[desk a=term] [desk b=term]]
^- ?
?~ priorities
(aor a b)
=* priority i.priorities
?: &((~(has in priority) a) (~(has in priority) b))
(aor a b)
?: (~(has in priority) a)
%.y
?: (~(has in priority) b)
%.n
$(priorities t.priorities)
--
::
++ se-adze :: update connections
^+ .
%+ roll ~(tap in eel)
%+ roll
%+ sort
~(tap in eel)
|= [[@ a=term] [@ b=term]]
?: =(a %dojo) %.n
?: =(b %dojo) %.y
(aor a b)
=< .(con +>)
|: $:{gil/gill:gall con/_.} ^+ con
=. +>.$ con

50
pkg/arvo/lib/s3-json.hoon Normal file
View File

@ -0,0 +1,50 @@
/- *s3
|%
++ json-to-action
|= =json
^- action
=, format
|^ (parse-json json)
++ parse-json
%- of:dejs
:~ [%set-endpoint so:dejs]
[%set-access-key-id so:dejs]
[%set-secret-access-key so:dejs]
[%add-bucket so:dejs]
[%remove-bucket so:dejs]
[%set-current-bucket so:dejs]
==
--
::
++ update-to-json
|= upd=update
^- json
=, format
%+ frond:enjs %s3-update
%- pairs:enjs
:~ ?- -.upd
%set-current-bucket [%'setCurrentBucket' s+bucket.upd]
%add-bucket [%'addBucket' s+bucket.upd]
%remove-bucket [%'removeBucket' s+bucket.upd]
%set-endpoint [%'setEndpoint' s+endpoint.upd]
%set-access-key-id [%'setAccessKeyId' s+access-key-id.upd]
%set-secret-access-key
[%'setSecretAccessKey' s+secret-access-key.upd]
::
%credentials
:- %credentials
%- pairs:enjs
:~ [%endpoint s+endpoint.credentials.upd]
[%'accessKeyId' s+access-key-id.credentials.upd]
[%'secretAccessKey' s+secret-access-key.credentials.upd]
==
::
%configuration
:- %configuration
%- pairs:enjs
:~ [%buckets a+(turn ~(tap in buckets.configuration.upd) |=(a=@t s+a))]
[%'currentBucket' s+current-bucket.configuration.upd]
==
==
==
--

View File

@ -0,0 +1,8 @@
/+ *s3-json
|_ act=action
++ grab
|%
++ noun action
++ json json-to-action
--
--

View File

@ -0,0 +1,12 @@
/+ *s3-json
|_ upd=update
++ grow
|%
++ json (update-to-json upd)
--
::
++ grab
|%
++ noun update
--
--

27
pkg/arvo/sur/s3.hoon Normal file
View File

@ -0,0 +1,27 @@
|%
+$ credentials
$: endpoint=@t
access-key-id=@t
secret-access-key=@t
==
::
+$ configuration
$: buckets=(set @t)
current-bucket=@t
==
::
+$ action
$% [%set-endpoint endpoint=@t]
[%set-access-key-id access-key-id=@t]
[%set-secret-access-key secret-access-key=@t]
[%add-bucket bucket=@t]
[%remove-bucket bucket=@t]
[%set-current-bucket bucket=@t]
==
::
+$ update
$% [%credentials =credentials]
[%configuration =configuration]
action
==
--

View File

@ -78,7 +78,7 @@ instance FromNoun H.StdMethod where
-- Http Server Configuration ---------------------------------------------------
newtype PEM = PEM { unPEM :: Cord }
newtype PEM = PEM { unPEM :: Wain }
deriving newtype (Eq, Ord, Show, ToNoun, FromNoun)
type Key = PEM

View File

@ -24,6 +24,7 @@ type Life = Word -- Number of Azimoth key revs.
type Bloq = Atom -- TODO
type Oath = Atom -- Signature
-- Parsed URLs -----------------------------------------------------------------
type Host = Each Turf Ipv4
@ -169,7 +170,7 @@ data HttpServerReq = HttpServerReq
data HttpClientEv
= HttpClientEvReceive (KingId, ()) ServerId HttpEvent
| HttpClientEvBorn (KingId, ()) ()
| HttpClientEvCrud Path Cord Tang
| HttpClientEvCrud Path Noun
deriving (Eq, Ord, Show)
data HttpServerEv
@ -178,7 +179,7 @@ data HttpServerEv
| HttpServerEvRequestLocal (ServId, UD, UD, ()) HttpServerReq
| HttpServerEvLive (ServId, ()) Port (Maybe Port)
| HttpServerEvBorn (KingId, ()) ()
| HttpServerEvCrud Path Cord Tang
| HttpServerEvCrud Path Noun
deriving (Eq, Ord, Show)
deriveNoun ''Address
@ -193,7 +194,7 @@ deriveNoun ''HttpServerReq
data AmesEv
= AmesEvHear () AmesDest Bytes
| AmesEvHole () AmesDest Bytes
| AmesEvCrud Path Cord Tang
| AmesEvCrud Path Noun
deriving (Eq, Ord, Show)
deriveNoun ''AmesEv
@ -202,10 +203,10 @@ deriveNoun ''AmesEv
-- Arvo Events -----------------------------------------------------------------
data ArvoEv
= ArvoEvWhom () Ship
| ArvoEvWack () Word512
= ArvoEvWhom () Ship
| ArvoEvWack () Word512
| ArvoEvWarn Path Noun
| ArvoEvCrud Path Cord Tang
| ArvoEvCrud Path Noun
| ArvoEvVeer Atom Noun
deriving (Eq, Ord, Show)
@ -216,7 +217,7 @@ deriveNoun ''ArvoEv
data BoatEv
= BoatEvBoat () ()
| BoatEvCrud Path Cord Tang
| BoatEvCrud Path Noun
deriving (Eq, Ord, Show)
deriveNoun ''BoatEv
@ -227,7 +228,7 @@ deriveNoun ''BoatEv
data BehnEv
= BehnEvWake () ()
| BehnEvBorn (KingId, ()) ()
| BehnEvCrud Path Cord Tang
| BehnEvCrud Path Noun
deriving (Eq, Ord, Show)
deriveNoun ''BehnEv
@ -237,7 +238,7 @@ deriveNoun ''BehnEv
data NewtEv
= NewtEvBorn (KingId, ()) ()
| NewtEvCrud Path Cord Tang
| NewtEvCrud Path Noun
deriving (Eq, Ord, Show)
deriveNoun ''NewtEv
@ -247,7 +248,7 @@ deriveNoun ''NewtEv
data SyncEv
= SyncEvInto (Nullable (KingId, ())) Desk Bool [(Path, Maybe Mime)]
| SyncEvCrud Path Cord Tang
| SyncEvCrud Path Noun
deriving (Eq, Ord, Show)
deriveNoun ''SyncEv
@ -278,7 +279,7 @@ data TermEv
| TermEvBlew (UD, ()) Word Word
| TermEvBoot (UD, ()) Bool LegacyBootEvent
| TermEvHail (UD, ()) ()
| TermEvCrud Path Cord Tang
| TermEvCrud Path Noun
deriving (Eq, Show)
deriveNoun ''LegacyBootEvent

View File

@ -8,7 +8,7 @@ module Urbit.Noun.Conversions
, Bytes(..), Octs(..), File(..)
, Cord(..), Knot(..), Term(..), Tape(..), Tour(..)
, BigTape(..), BigCord(..)
, Wall, Each(..)
, Wain(..), Wall, Each(..)
, UD(..), UV(..), UW(..), cordToUW
, Mug(..), Path(..), EvilPath(..), Ship(..)
, Lenient(..), pathToFilePath, filePathToPath
@ -442,6 +442,20 @@ instance FromNoun Tape where
Right tx -> pure (Tape tx)
-- Wain -- List of Lines -------------------------------------------------------
newtype Wain = Wain { unWain :: Text }
deriving newtype (Eq, Ord, Show, IsString, NFData)
instance ToNoun Wain where
toNoun (Wain t) = toNoun (Cord <$> lines t)
instance FromNoun Wain where
parseNoun n = named "Wain" $ do
tx :: [Cord] <- parseNoun n
pure $ Wain $ unlines (unCord <$> tx)
-- Wall -- Text Lines ----------------------------------------------------------
type Wall = [Tape]

View File

@ -33,6 +33,7 @@ import Urbit.Vere.Pier.Types
import Data.Binary.Builder (Builder, fromByteString)
import Data.Bits (shiftL, (.|.))
import Data.PEM (pemParseBS, pemWriteBS)
import Network.Socket (SockAddr(..))
import System.Directory (doesFileExist, removeFile)
import System.Random (randomIO)
@ -216,6 +217,9 @@ writePortsFile f = writeFile f . encodeUtf8 . portsFileText
cordBytes :: Cord -> ByteString
cordBytes = encodeUtf8 . unCord
wainBytes :: Wain -> ByteString
wainBytes = encodeUtf8 . unWain
pass :: Monad m => m ()
pass = pure ()
@ -499,14 +503,22 @@ httpServerPorts fak = do
pure (PortsToTry { .. })
parseCerts :: ByteString -> Maybe (ByteString, [ByteString])
parseCerts bs = do
pems <- pemParseBS bs & either (const Nothing) Just
case pems of
[] -> Nothing
p:ps -> pure (pemWriteBS p, pemWriteBS <$> ps)
startServ :: (HasPierConfig e, HasLogFunc e, HasNetworkConfig e)
=> Bool -> HttpServerConf -> (Ev -> STM ())
-> RIO e Serv
startServ isFake conf plan = do
logDebug "startServ"
let tls = hscSecure conf <&> \(PEM key, PEM cert) ->
(W.tlsSettingsMemory (cordBytes cert) (cordBytes key))
let tls = do (PEM key, PEM certs) <- hscSecure conf
(cert, chain) <- parseCerts (wainBytes certs)
pure $ W.tlsSettingsChainMemory cert chain $ wainBytes key
sId <- io $ ServId . UV . fromIntegral <$> (randomIO :: IO Word32)
liv <- newTVarIO emptyLiveReqs

View File

@ -67,6 +67,7 @@ dependencies:
- network
- optparse-applicative
- para
- pem
- pretty-show
- primitive
- process

View File

@ -71,7 +71,7 @@ h2 {
}
.clamp-attachment {
overflow: scroll;
overflow: auto;
max-height: 10em;
max-width: 100%;
}

View File

@ -72,7 +72,7 @@ class UrbitApi {
addPendingMessage(msg) {
if (store.state.pendingMessages.has(msg.path)) {
store.state.pendingMessages.get(msg.path).push(msg.envelope);
store.state.pendingMessages.get(msg.path).unshift(msg.envelope);
} else {
store.state.pendingMessages.set(msg.path, [msg.envelope]);
}

View File

@ -527,6 +527,7 @@ export class ChatScreen extends Component {
envelopes={props.envelopes}
contacts={props.contacts}
onEnter={() => this.setState({ scrollLocked: false })}
s3={props.s3}
placeholder="Message..."
/>
</div>

View File

@ -2,12 +2,14 @@ import React, { Component } from 'react';
import _ from 'lodash';
import moment from 'moment';
import { UnControlled as CodeEditor } from 'react-codemirror2';
import CodeMirror from 'codemirror';
import 'codemirror/mode/markdown/markdown';
import 'codemirror/addon/display/placeholder';
import { Sigil } from '/components/lib/icons/sigil';
import { ShipSearch } from '/components/lib/ship-search';
import { S3Upload } from '/components/lib/s3-upload';
import { uxToHex } from '/lib/util';
@ -173,7 +175,7 @@ export class ChatInput extends Component {
isUrl(string) {
try {
const websiteTest = new RegExp(String(/((\w+:\/\/)[-a-zA-Z0-9:@;?&=\/%\+\.\*!'\(\),\$_\{\}\^~\[\]`#|]+)/.source)
const websiteTest = new RegExp(String(/^((\w+:\/\/)[-a-zA-Z0-9:@;?&=\/%\+\.\*!'\(\),\$_\{\}\^~\[\]`#|]+)/.source)
);
return websiteTest.test(string);
} catch (e) {
@ -267,6 +269,20 @@ export class ChatInput extends Component {
}
}
uploadSuccess(url) {
const { props } = this;
props.api.chat.message(
props.station,
`~${window.ship}`,
Date.now(),
{ url }
);
}
uploadError(error) {
// no-op for now
}
render() {
const { props, state } = this;
@ -308,7 +324,9 @@ export class ChatInput extends Component {
'Enter': cm =>
this.messageSubmit(),
'Shift-3': cm =>
this.toggleCode()
cm.getValue().length === 0
? this.toggleCode()
: CodeMirror.Pass
}
};
@ -337,26 +355,38 @@ export class ChatInput extends Component {
</div>
<div
className="fr h-100 flex bg-gray0-d lh-copy pl2 w-100 items-center"
style={{ flexGrow: 1, maxHeight: '224px', width: 'calc(100% - 48px)' }}
style={{ flexGrow: 1, maxHeight: '224px', width: 'calc(100% - 72px)' }}
>
<CodeEditor
options={options}
editorDidMount={(editor) => {
this.editor = editor;
}}
this.editor = editor;
if (!/Android|webOS|iPhone|iPad|iPod|BlackBerry/i.test(
navigator.userAgent
)) {
editor.focus();
}
}}
onChange={(e, d, v) => this.messageChange(e, d, v)}
/>
</div>
<div style={{ height: '24px', width: '24px', flexBasis: 24, marginTop: 6 }}>
<div className="ml2 mr2"
style={{ height: '16px', width: '16px', flexBasis: 16, marginTop: 10 }}>
<S3Upload
configuration={props.s3.configuration}
credentials={props.s3.credentials}
uploadSuccess={this.uploadSuccess.bind(this)}
uploadError={this.uploadError.bind(this)}
/>
</div>
<div style={{ height: '16px', width: '16px', flexBasis: 16, marginTop: 10 }}>
<img
style={{ filter: state.code && 'invert(100%)', height: '100%', width: '100%' }}
onClick={this.toggleCode}
src="/~chat/img/CodeEval.png"
className="contrast-10-d bg-white bg-none-d"
/>
</div>
</div>
);
}

View File

@ -65,17 +65,17 @@ export class Message extends Component {
(!!letter.code.output &&
letter.code.output.length && letter.code.output.length > 0) ?
(
<pre className="f7 clamp-attachment pa1 mt0 mb0">
<pre className="f7 clamp-attachment pa1 mt0 mb0 b--gray4 b--gray1-d bl br bb">
{letter.code.output[0].join('\n')}
</pre>
) : null;
return (
<span>
<pre className="f7 clamp-attachment pa1 mt0 mb0 bg-light-gray">
<div className="mv2">
<pre className="f7 clamp-attachment pa1 mt0 mb0 bg-light-gray b--gray4 b--gray1-d ba">
{letter.code.expression}
</pre>
{outputElement}
</span>
</div>
);
} else if ('url' in letter) {
let imgMatch =

View File

@ -0,0 +1,96 @@
import React, { Component } from 'react'
import S3Client from '/lib/s3';
export class S3Upload extends Component {
constructor(props) {
super(props);
this.s3 = new S3Client();
this.setCredentials(props.credentials, props.configuration);
this.inputRef = React.createRef();
}
isReady(creds, config) {
return (
!!creds &&
'endpoint' in creds &&
'accessKeyId' in creds &&
'secretAccessKey' in creds &&
creds.endpoint !== '' &&
creds.accessKeyId !== '' &&
creds.secretAccessKey !== '' &&
!!config &&
'currentBucket' in config &&
config.currentBucket !== ''
);
}
componentDidUpdate(prevProps) {
const { props } = this;
this.setCredentials(props.credentials, props.configuration);
}
setCredentials(credentials, configuration) {
if (!this.isReady(credentials, configuration)) { return; }
this.s3.setCredentials(
credentials.endpoint,
credentials.accessKeyId,
credentials.secretAccessKey
);
}
getFileUrl(endpoint, filename) {
return endpoint + '/' + filename;
}
onChange() {
const { props } = this;
if (!this.inputRef.current) { return; }
let files = this.inputRef.current.files;
if (files.length <= 0) { return; }
let file = files.item(0);
let bucket = props.configuration.currentBucket;
this.s3.upload(bucket, file.name, file).then((data) => {
if (!data || !('Location' in data)) {
return;
}
this.props.uploadSuccess(data.Location);
}).catch((err) => {
console.error(err);
this.props.uploadError(err);
});
}
onClick() {
if (!this.inputRef.current) { return; }
this.inputRef.current.click();
}
render() {
const { props } = this;
if (!this.isReady(props.credentials, props.configuration)) {
return <div></div>;
} else {
let classes = !!props.className ?
"pointer " + props.className : "pointer";
return (
<div className={classes}>
<input className="dn"
type="file"
id="fileElement"
ref={this.inputRef}
accept="image/*"
onChange={this.onChange.bind(this)} />
<img className="invert-d"
src="/~chat/img/ImageUpload.png"
width="16"
height="16"
onClick={this.onClick.bind(this)} />
</div>
);
}
}
}

View File

@ -11,13 +11,10 @@ export class NewDmScreen extends Component {
super(props);
this.state = {
ship: null,
idError: false,
inviteError: false,
allowHistory: true,
station: null,
awaiting: false
};
this.setInvite = this.setInvite.bind(this);
this.onClickCreate = this.onClickCreate.bind(this);
}
@ -26,8 +23,6 @@ export class NewDmScreen extends Component {
if (props.autoCreate && urbitOb.isValidPatp(props.autoCreate)) {
this.setState(
{
error: false,
success: true,
ship: props.autoCreate.slice(1),
awaiting: true
},
@ -40,20 +35,14 @@ export class NewDmScreen extends Component {
const { props, state } = this;
if (prevProps !== props) {
let station = `/~${window.ship}/${state.idName}`;
if (station in props.inbox) {
props.history.push("/~chat/room" + station);
const { station } = this.state;
if (station && station in props.inbox) {
this.setState({ awaiting: false });
props.history.push(`/~chat/room${station}`);
}
}
}
setInvite(value) {
this.setState({
groups: [],
ship: value.ships[0]
});
}
onClickCreate() {
const { props, state } = this;
@ -73,15 +62,11 @@ export class NewDmScreen extends Component {
this.setState(
{
error: false,
success: true,
group: [],
ship: [],
awaiting: true
station
},
() => {
let groupPath = station;
let submit = props.api.chatView.create(
props.api.chatView.create(
`~${window.ship} <-> ~${state.ship}`,
"",
station,
@ -90,10 +75,6 @@ export class NewDmScreen extends Component {
state.ship !== window.ship ? [`~${state.ship}`] : [],
true
);
submit.then(() => {
this.setState({ awaiting: false });
props.history.push(`/~chat/room${station}`);
});
}
);
}

View File

@ -60,6 +60,7 @@ export class Root extends Component {
let contacts = !!state.contacts ? state.contacts : {};
let associations = !!state.associations ? state.associations : {chat: {}, contacts: {}};
let s3 = !!state.s3 ? state.s3 : {};
const renderChannelSidebar = (props, station) => (
<Sidebar
@ -244,6 +245,7 @@ export class Root extends Component {
inbox={state.inbox}
contacts={roomContacts}
permission={permission}
s3={s3}
pendingMessages={state.pendingMessages}
popout={popout}
sidebarShown={state.sidebarShown}

View File

@ -0,0 +1,53 @@
export default class S3Client {
constructor() {
this.s3 = null;
this.endpoint = "";
this.accessKeyId = "";
this.secretAccesskey = "";
}
setCredentials(endpoint, accessKeyId, secretAccessKey) {
if (!window.AWS) {
setTimeout(() => {
this.setCredentials(endpoint, accessKeyId, secretAccessKey);
}, 2000);
return;
}
this.endpoint = new window.AWS.Endpoint(endpoint);
this.accessKeyId = accessKeyId;
this.secretAccessKey = secretAccessKey;
this.s3 =
new window.AWS.S3({
endpoint: this.endpoint,
credentials: new window.AWS.Credentials({
accessKeyId: this.accessKeyId,
secretAccessKey: this.secretAccessKey
})
});
}
upload(bucket, filename, buffer) {
let params = {
Bucket: bucket,
Key: filename,
Body: buffer,
ACL: 'public-read'
};
return new Promise((resolve, reject) => {
if (!this.s3) {
reject({ error: 'S3 not initialized!' });
return;
}
this.s3.upload(params, (error, data) => {
if (error) {
reject({ error });
} else {
resolve(data);
}
});
});
}
}

View File

@ -0,0 +1,81 @@
import _ from 'lodash';
export class S3Reducer {
reduce(json, state) {
let data = _.get(json, 's3-update', false);
if (data) {
this.credentials(data, state);
this.configuration(data, state);
this.currentBucket(data, state);
this.addBucket(data, state);
this.removeBucket(data, state);
this.endpoint(data, state);
this.accessKeyId(data, state);
this.secretAccessKey(data, state);
}
}
credentials(json, state) {
let data = _.get(json, 'credentials', false);
if (data) {
state.s3.credentials = data;
}
}
configuration(json, state) {
let data = _.get(json, 'configuration', false);
if (data) {
state.s3.configuration = {
buckets: new Set(data.buckets),
currentBucket: data.currentBucket
};
}
}
currentBucket(json, state) {
let data = _.get(json, 'setCurrentBucket', false);
if (data) {
state.s3.configuration.currentBucket = data;
}
}
addBucket(json, state) {
let data = _.get(json, 'addBucket', false);
if (data) {
state.s3.configuration.buckets =
state.s3.configuration.buckets.add(data);
}
}
removeBucket(json, state) {
let data = _.get(json, 'removeBucket', false);
if (data) {
state.s3.configuration.buckets =
state.s3.configuration.buckets.delete(data);
}
}
endpoint(json, state) {
let data = _.get(json, 'setEndpoint', false);
if (data) {
state.s3.credentials.endpoint = data;
}
}
accessKeyId(json, state) {
let data = _.get(json, 'setAccessKeyId', false);
if (data) {
state.s3.credentials.accessKeyId = data;
}
}
secretAccessKey(json, state) {
let data = _.get(json, 'setSecretAccessKey', false);
if (data) {
state.s3.credentials.secretAccessKey = data;
}
}
}

View File

@ -4,6 +4,7 @@ import { ChatUpdateReducer } from '/reducers/chat-update';
import { InviteUpdateReducer } from '/reducers/invite-update';
import { PermissionUpdateReducer } from '/reducers/permission-update';
import { MetadataReducer } from '/reducers/metadata-update.js';
import { S3Reducer } from '/reducers/s3.js';
import { LocalReducer } from '/reducers/local.js';
@ -17,6 +18,7 @@ class Store {
this.chatUpdateReducer = new ChatUpdateReducer();
this.inviteUpdateReducer = new InviteUpdateReducer();
this.metadataReducer = new MetadataReducer();
this.s3Reducer = new S3Reducer();
this.localReducer = new LocalReducer();
this.setState = () => {};
}
@ -32,6 +34,7 @@ class Store {
chat: {},
contacts: {}
},
s3: {},
selectedGroups: [],
sidebarShown: true,
pendingMessages: new Map([]),
@ -58,6 +61,7 @@ class Store {
this.chatUpdateReducer.reduce(json, this.state);
this.inviteUpdateReducer.reduce(json, this.state);
this.metadataReducer.reduce(json, this.state);
this.s3Reducer.reduce(json, this.state);
this.localReducer.reduce(json, this.state);
this.setState(this.state);

View File

@ -54,6 +54,7 @@ export class Subscription {
this.subscribe('/primary', 'contact-view');
this.subscribe('/app-name/chat', 'metadata-store');
this.subscribe('/app-name/contacts', 'metadata-store');
this.subscribe('/all', 's3-store');
}
handleEvent(diff) {

View File

@ -6,6 +6,7 @@ import { Link } from 'react-router-dom';
import { EditElement } from '/components/lib/edit-element';
import { Spinner } from './icons/icon-spinner';
import { uxToHex } from '/lib/util';
import { S3Upload } from '/components/lib/s3-upload';
export class ContactCard extends Component {
constructor(props) {
@ -117,8 +118,7 @@ export class ContactCard extends Component {
(state.avatarToSet === '') ||
(
Boolean(props.contact.avatar) &&
'url' in props.contact.avatar &&
state.avatarToSet === props.contact.avatar.url
state.avatarToSet === props.contact.avatar
)
) {
return false;
@ -129,6 +129,7 @@ export class ContactCard extends Component {
awaiting: true,
type: 'Saving to group'
}, (() => {
console.log(state.avatarToSet);
api.contactEdit(props.path, ship, {
avatar: {
url: state.avatarToSet
@ -304,7 +305,7 @@ export class ContactCard extends Component {
email: props.rootIdentity.email,
phone: props.rootIdentity.phone,
website: props.rootIdentity.website,
avatar: { url: props.rootIdentity.avatar },
avatar: !!props.rootIdentity.avatar ? { url: props.rootIdentity.avatar } : null,
notes: props.rootIdentity.notes,
color: uxToHex(props.rootIdentity.color)
} : {
@ -312,11 +313,11 @@ export class ContactCard extends Component {
email: props.contact.email,
phone: props.contact.phone,
website: props.contact.website,
avatar: { url: props.contact.avatar },
avatar: !!props.contact.avatar ? { url: props.contact.avatar } : null,
notes: props.contact.notes,
color: props.contact.color
};
const contact = {
nickname: this.pickFunction(state.nickNameToSet, defaultVal.nickname),
email: this.pickFunction(state.emailToSet, defaultVal.email),
@ -324,8 +325,12 @@ export class ContactCard extends Component {
website: this.pickFunction(state.websiteToSet, defaultVal.website),
notes: this.pickFunction(state.notesToSet, defaultVal.notes),
color: this.pickFunction(state.colorToSet, defaultVal.color),
avatar: this.pickFunction({ url: state.avatarToSet }, defaultVal.avatar)
avatar: this.pickFunction(
!!state.avatarToSet ? { url: state.avatarToSet } : null,
defaultVal.avatar
)
};
this.setState({ awaiting: true, type: 'Sharing with group' }, (() => {
api.contactView.share(
`~${props.ship}`, props.path, `~${window.ship}`, contact
@ -363,6 +368,18 @@ export class ContactCard extends Component {
}));
}
uploadSuccess(url) {
this.setState({
avatarToSet: url
}, () => {
this.setField('avatar');
});
}
uploadError(error) {
// no-op for now
}
renderEditCard() {
const { props, state } = this;
// if this is our first edit in a new group, propagate from root identity
@ -391,17 +408,28 @@ export class ContactCard extends Component {
let currentColor = state.colorToSet ? state.colorToSet : defaultColor;
currentColor = uxToHex(currentColor);
const hasAvatar =
'avatar' in props.contact && props.contact.avatar !== null;
const avatar = ('avatar' in props.contact && props.contact.avatar !== null)
? <img className="dib h-auto"
width={128}
src={props.contact.avatar}
/>
: <span className="dn"></span>;
const avatar = (hasAvatar)
? <span>
<img className="dib h-auto"
width={128}
src={props.contact.avatar}
/>
const imageSetter = (!props.share) ? (
<span className="db">
<p className="f9 gray2 db pb1">Avatar image url</p>
<span className="cf db">
<span className="w-20 fl pt1">
<S3Upload
className="fr pr3"
configuration={props.s3.configuration}
credentials={props.s3.credentials}
uploadSuccess={this.uploadSuccess.bind(this)}
uploadError={this.uploadError.bind(this)}
/>
</span>
<EditElement
title="Avatar Image URL"
className="fr w-80"
defaultValue={defaultValue.avatar}
onChange={this.avatarToSet}
onDeleteClick={() => this.setField('removeAvatar')}
@ -409,21 +437,14 @@ export class ContactCard extends Component {
showButtons={!props.share}
/>
</span>
: <span>
<EditElement
title="Avatar Image URL"
defaultValue={''}
onChange={this.avatarToSet}
onDeleteClick={() => this.setField('removeAvatar')}
onSaveClick={() => this.setField('avatar')}
showButtons={!props.share}
/>
</span>;
</span>
) : (<span className="dn"></span>);
return (
<div className="w-100 mt8 flex justify-center pa4 pt8 pt0-l pa0-xl pt4-xl pb8">
<div className="w-100 mw6 tc">
{avatar}
{imageSetter}
<Sigil
ship={props.ship}
size={128}

View File

@ -23,8 +23,10 @@ export class EditElement extends Component {
? { resize: "vertical", height: 40, paddingTop: 10 }
: { resize: "none", height: 40, paddingTop: 10 }
let classes = !!props.className ? "pb4 " + props.className : "pb4";
return (
<div className="pb4">
<div className={classes}>
<p className="f9 gray2">{props.title}</p>
<div className="w-100 flex">
<textarea

View File

@ -0,0 +1,96 @@
import React, { Component } from 'react'
import S3Client from '/lib/s3';
export class S3Upload extends Component {
constructor(props) {
super(props);
this.s3 = new S3Client();
this.setCredentials(props.credentials, props.configuration);
this.inputRef = React.createRef();
}
isReady(creds, config) {
return (
!!creds &&
'endpoint' in creds &&
'accessKeyId' in creds &&
'secretAccessKey' in creds &&
creds.endpoint !== '' &&
creds.accessKeyId !== '' &&
creds.secretAccessKey !== '' &&
!!config &&
'currentBucket' in config &&
config.currentBucket !== ''
);
}
componentDidUpdate(prevProps) {
const { props } = this;
this.setCredentials(props.credentials, props.configuration);
}
setCredentials(credentials, configuration) {
if (!this.isReady(credentials, configuration)) { return; }
this.s3.setCredentials(
credentials.endpoint,
credentials.accessKeyId,
credentials.secretAccessKey
);
}
getFileUrl(endpoint, filename) {
return endpoint + '/' + filename;
}
onChange() {
const { props } = this;
if (!this.inputRef.current) { return; }
let files = this.inputRef.current.files;
if (files.length <= 0) { return; }
let file = files.item(0);
let bucket = props.configuration.currentBucket;
this.s3.upload(bucket, file.name, file).then((data) => {
if (!data || !('Location' in data)) {
return;
}
this.props.uploadSuccess(data.Location);
}).catch((err) => {
console.error(err);
this.props.uploadError(err);
});
}
onClick() {
if (!this.inputRef.current) { return; }
this.inputRef.current.click();
}
render() {
const { props } = this;
if (!this.isReady(props.credentials, props.configuration)) {
return <div></div>;
} else {
let classes = !!props.className ?
"pointer " + props.className : "pointer";
return (
<div className={classes}>
<input className="dn"
type="file"
id="fileElement"
ref={this.inputRef}
accept="image/*"
onChange={this.onChange.bind(this)} />
<img className="invert-d"
src="/~groups/img/ImageUpload.png"
width="32"
height="32"
onClick={this.onClick.bind(this)} />
</div>
);
}
}
}

View File

@ -33,7 +33,9 @@ export class Root extends Component {
let defaultContacts =
(!!state.contacts && '/~/default' in state.contacts) ?
state.contacts['/~/default'] : {};
let groups = !!state.groups ? state.groups : {};
let s3 = !!state.s3 ? state.s3 : {};
let invites =
(!!state.invites && '/contacts' in state.invites) ?
@ -211,6 +213,7 @@ export class Root extends Component {
ship={window.ship}
share={true}
rootIdentity={rootIdentity}
s3={s3}
/>
</Skeleton>
);
@ -259,6 +262,7 @@ export class Root extends Component {
path={groupPath}
ship={props.match.params.contact}
rootIdentity={rootIdentity}
s3={s3}
/>
</Skeleton>
);
@ -283,6 +287,7 @@ export class Root extends Component {
path="/~/default"
contact={me}
ship={window.ship}
s3={s3}
/>
</Skeleton>
);

View File

@ -0,0 +1,53 @@
export default class S3Client {
constructor() {
this.s3 = null;
this.endpoint = "";
this.accessKeyId = "";
this.secretAccesskey = "";
}
setCredentials(endpoint, accessKeyId, secretAccessKey) {
if (!window.AWS) {
setTimeout(() => {
this.setCredentials(endpoint, accessKeyId, secretAccessKey);
}, 2000);
return;
}
this.endpoint = new window.AWS.Endpoint(endpoint);
this.accessKeyId = accessKeyId;
this.secretAccessKey = secretAccessKey;
this.s3 =
new window.AWS.S3({
endpoint: this.endpoint,
credentials: new window.AWS.Credentials({
accessKeyId: this.accessKeyId,
secretAccessKey: this.secretAccessKey
})
});
}
upload(bucket, filename, buffer) {
let params = {
Bucket: bucket,
Key: filename,
Body: buffer,
ACL: 'public-read'
};
return new Promise((resolve, reject) => {
if (!this.s3) {
reject({ error: 'S3 not initialized!' });
return;
}
this.s3.upload(params, (error, data) => {
if (error) {
reject({ error });
} else {
resolve(data);
}
});
});
}
}

View File

@ -0,0 +1,81 @@
import _ from 'lodash';
export class S3Reducer {
reduce(json, state) {
let data = _.get(json, 's3-update', false);
if (data) {
this.credentials(data, state);
this.configuration(data, state);
this.currentBucket(data, state);
this.addBucket(data, state);
this.removeBucket(data, state);
this.endpoint(data, state);
this.accessKeyId(data, state);
this.secretAccessKey(data, state);
}
}
credentials(json, state) {
let data = _.get(json, 'credentials', false);
if (data) {
state.s3.credentials = data;
}
}
configuration(json, state) {
let data = _.get(json, 'configuration', false);
if (data) {
state.s3.configuration = {
buckets: new Set(data.buckets),
currentBucket: data.currentBucket
};
}
}
currentBucket(json, state) {
let data = _.get(json, 'setCurrentBucket', false);
if (data) {
state.s3.configuration.currentBucket = data;
}
}
addBucket(json, state) {
let data = _.get(json, 'addBucket', false);
if (data) {
state.s3.configuration.buckets =
state.s3.configuration.buckets.add(data);
}
}
removeBucket(json, state) {
let data = _.get(json, 'removeBucket', false);
if (data) {
state.s3.configuration.buckets =
state.s3.configuration.buckets.delete(data);
}
}
endpoint(json, state) {
let data = _.get(json, 'setEndpoint', false);
if (data) {
state.s3.credentials.endpoint = data;
}
}
accessKeyId(json, state) {
let data = _.get(json, 'setAccessKeyId', false);
if (data) {
state.s3.credentials.accessKeyId = data;
}
}
secretAccessKey(json, state) {
let data = _.get(json, 'setSecretAccessKey', false);
if (data) {
state.s3.credentials.secretAccessKey = data;
}
}
}

View File

@ -4,6 +4,7 @@ import { GroupUpdateReducer } from '/reducers/group-update';
import { InviteUpdateReducer } from '/reducers/invite-update';
import { PermissionUpdateReducer } from '/reducers/permission-update';
import { MetadataReducer } from '/reducers/metadata-update.js';
import { S3Reducer } from '/reducers/s3.js';
import { LocalReducer } from '/reducers/local.js';
@ -17,6 +18,7 @@ class Store {
this.contactUpdateReducer = new ContactUpdateReducer();
this.inviteUpdateReducer = new InviteUpdateReducer();
this.metadataReducer = new MetadataReducer();
this.s3Reducer = new S3Reducer();
this.localReducer = new LocalReducer();
this.setState = () => {};
}
@ -28,6 +30,7 @@ class Store {
associations: {},
permissions: {},
invites: {},
s3: {},
selectedGroups: []
};
}
@ -51,6 +54,7 @@ class Store {
this.contactUpdateReducer.reduce(json, this.state);
this.inviteUpdateReducer.reduce(json, this.state);
this.metadataReducer.reduce(json, this.state);
this.s3Reducer.reduce(json, this.state);
this.localReducer.reduce(json, this.state);
this.setState(this.state);

View File

@ -7,12 +7,13 @@ import urbitOb from 'urbit-ob';
export class Subscription {
constructor() {
this.firstRoundSubscriptionComplete = false;
this.firstRoundComplete = false;
this.secondRoundComplete = false;
}
start() {
if (api.authTokens) {
this.firstRoundSubscription();
this.firstRound();
window.urb.setOnChannelError(this.onChannelError.bind(this));
} else {
console.error("~~~ ERROR: Must set api.authTokens before operation ~~~");
@ -22,7 +23,8 @@ export class Subscription {
onChannelError(err) {
console.error('event source error: ', err);
console.log('initiating new channel');
this.firstRoundSubscriptionComplete = false;
this.firstRoundComplete = false;
this.secondRoundComplete = false;
setTimeout(2000, () => {
store.handleEvent({
data: { clear : true}
@ -43,21 +45,28 @@ export class Subscription {
});
}
firstRoundSubscription() {
firstRound() {
this.subscribe('/primary', 'contact-view');
}
secondRoundSubscriptions() {
this.subscribe('/synced', 'contact-hook');
this.subscribe('/primary', 'invite-view');
secondRound() {
this.subscribe('/all', 'group-store');
this.subscribe('/all', 'metadata-store');
}
thirdRound() {
this.subscribe('/synced', 'contact-hook');
this.subscribe('/primary', 'invite-view');
this.subscribe('/all', 's3-store');
}
handleEvent(diff) {
if (!this.firstRoundSubscriptionComplete) {
this.firstRoundSubscriptionComplete = true;
this.secondRoundSubscriptions();
if (!this.firstRoundComplete) {
this.firstRoundComplete = true;
this.secondRound();
} else if (!this.secondRoundComplete) {
this.secondRoundComplete = true;
this.thirdRound();
}
store.handleEvent(diff);
}