Skip to content

Instantly share code, notes, and snippets.

@deanrad
Created November 11, 2021 02:29
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save deanrad/735a685de898303837a43838ccedc8db to your computer and use it in GitHub Desktop.
Save deanrad/735a685de898303837a43838ccedc8db to your computer and use it in GitHub Desktop.
⭐️ Polyrhythm Design Language for Distributed Systems
<title>⭐️ Polyrhythm Design Language for Distributed Systems</title>

Design principles

  • Name every message (Problems with assuming ‘return value’: synchronicity, multiplicity, agency)
  • Message name: actor/verb - The message name determines its schema/validation criteria. If TV needs to send auth/authorize, or listen to auth/grant, it needs a reference to Auth. Generally the messages you send to an actor start with that actor’s name, as do the messages that come from that actor.

Step 1: Simple TV Auth - Credentials through TV

Dependency Graph

<style>#mermaid-svg-dgCG5ylb25RBiFvs{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#000000;}#mermaid-svg-dgCG5ylb25RBiFvs .error-icon{fill:#552222;}#mermaid-svg-dgCG5ylb25RBiFvs .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-dgCG5ylb25RBiFvs .edge-thickness-normal{stroke-width:2px;}#mermaid-svg-dgCG5ylb25RBiFvs .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-dgCG5ylb25RBiFvs .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-dgCG5ylb25RBiFvs .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-dgCG5ylb25RBiFvs .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-dgCG5ylb25RBiFvs .marker{fill:#666;stroke:#666;}#mermaid-svg-dgCG5ylb25RBiFvs .marker.cross{stroke:#666;}#mermaid-svg-dgCG5ylb25RBiFvs svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-dgCG5ylb25RBiFvs .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#000000;}#mermaid-svg-dgCG5ylb25RBiFvs .cluster-label text{fill:#333;}#mermaid-svg-dgCG5ylb25RBiFvs .cluster-label span{color:#333;}#mermaid-svg-dgCG5ylb25RBiFvs .label text,#mermaid-svg-dgCG5ylb25RBiFvs span{fill:#000000;color:#000000;}#mermaid-svg-dgCG5ylb25RBiFvs .node rect,#mermaid-svg-dgCG5ylb25RBiFvs .node circle,#mermaid-svg-dgCG5ylb25RBiFvs .node ellipse,#mermaid-svg-dgCG5ylb25RBiFvs .node polygon,#mermaid-svg-dgCG5ylb25RBiFvs .node path{fill:#eee;stroke:#999;stroke-width:1px;}#mermaid-svg-dgCG5ylb25RBiFvs .node .label{text-align:center;}#mermaid-svg-dgCG5ylb25RBiFvs .node.clickable{cursor:pointer;}#mermaid-svg-dgCG5ylb25RBiFvs .arrowheadPath{fill:#333333;}#mermaid-svg-dgCG5ylb25RBiFvs .edgePath .path{stroke:#666;stroke-width:1.5px;}#mermaid-svg-dgCG5ylb25RBiFvs .flowchart-link{stroke:#666;fill:none;}#mermaid-svg-dgCG5ylb25RBiFvs .edgeLabel{background-color:white;text-align:center;}#mermaid-svg-dgCG5ylb25RBiFvs .edgeLabel rect{opacity:0.5;background-color:white;fill:white;}#mermaid-svg-dgCG5ylb25RBiFvs .cluster rect{fill:hsl(210,66.6666666667%,95%);stroke:#26a;stroke-width:1px;}#mermaid-svg-dgCG5ylb25RBiFvs .cluster text{fill:#333;}#mermaid-svg-dgCG5ylb25RBiFvs .cluster span{color:#333;}#mermaid-svg-dgCG5ylb25RBiFvs div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(-160,0%,93.3333333333%);border:1px solid #26a;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-dgCG5ylb25RBiFvs:root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;}#mermaid-svg-dgCG5ylb25RBiFvs flowchart{fill:apa;}</style>
User
TV
Auth

Data Flow diagram

<style>#mermaid-svg-G6uNBhlc4k5VK68l{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#000000;}#mermaid-svg-G6uNBhlc4k5VK68l .error-icon{fill:#552222;}#mermaid-svg-G6uNBhlc4k5VK68l .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-G6uNBhlc4k5VK68l .edge-thickness-normal{stroke-width:2px;}#mermaid-svg-G6uNBhlc4k5VK68l .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-G6uNBhlc4k5VK68l .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-G6uNBhlc4k5VK68l .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-G6uNBhlc4k5VK68l .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-G6uNBhlc4k5VK68l .marker{fill:#666;stroke:#666;}#mermaid-svg-G6uNBhlc4k5VK68l .marker.cross{stroke:#666;}#mermaid-svg-G6uNBhlc4k5VK68l svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-G6uNBhlc4k5VK68l g.stateGroup text{fill:#999;stroke:none;font-size:10px;}#mermaid-svg-G6uNBhlc4k5VK68l g.stateGroup text{fill:#000000;stroke:none;font-size:10px;}#mermaid-svg-G6uNBhlc4k5VK68l g.stateGroup .state-title{font-weight:bolder;fill:black;}#mermaid-svg-G6uNBhlc4k5VK68l g.stateGroup rect{fill:#eee;stroke:#999;}#mermaid-svg-G6uNBhlc4k5VK68l g.stateGroup line{stroke:#666;stroke-width:1;}#mermaid-svg-G6uNBhlc4k5VK68l .transition{stroke:#666;stroke-width:1;fill:none;}#mermaid-svg-G6uNBhlc4k5VK68l .stateGroup .composit{fill:#ffffff;border-bottom:1px;}#mermaid-svg-G6uNBhlc4k5VK68l .stateGroup .alt-composit{fill:#e0e0e0;border-bottom:1px;}#mermaid-svg-G6uNBhlc4k5VK68l .state-note{stroke:hsl(60,100%,23.3333333333%);fill:#ffa;}#mermaid-svg-G6uNBhlc4k5VK68l .state-note text{fill:black;stroke:none;font-size:10px;}#mermaid-svg-G6uNBhlc4k5VK68l .stateLabel .box{stroke:none;stroke-width:0;fill:#eee;opacity:0.5;}#mermaid-svg-G6uNBhlc4k5VK68l .edgeLabel .label rect{fill:hsl(-160,0%,93.3333333333%);opacity:0.5;}#mermaid-svg-G6uNBhlc4k5VK68l .edgeLabel .label text{fill:rgb(17.0000000001,17.0000000001,17.0000000001);}#mermaid-svg-G6uNBhlc4k5VK68l .label div .edgeLabel{color:rgb(17.0000000001,17.0000000001,17.0000000001);}#mermaid-svg-G6uNBhlc4k5VK68l .stateLabel text{fill:black;font-size:10px;font-weight:bold;}#mermaid-svg-G6uNBhlc4k5VK68l .node circle.state-start{fill:#666;stroke:black;}#mermaid-svg-G6uNBhlc4k5VK68l .node circle.state-end{fill:hsl(0,0%,83.3333333333%);stroke:#ffffff;stroke-width:1.5;}#mermaid-svg-G6uNBhlc4k5VK68l .end-state-inner{fill:#ffffff;stroke-width:1.5;}#mermaid-svg-G6uNBhlc4k5VK68l .node rect{fill:#eee;stroke:#999;stroke-width:1px;}#mermaid-svg-G6uNBhlc4k5VK68l #statediagram-barbEnd{fill:#666;}#mermaid-svg-G6uNBhlc4k5VK68l .statediagram-cluster rect{fill:#eee;stroke:#999;stroke-width:1px;}#mermaid-svg-G6uNBhlc4k5VK68l .cluster-label,#mermaid-svg-G6uNBhlc4k5VK68l .nodeLabel{color:#000000;}#mermaid-svg-G6uNBhlc4k5VK68l .statediagram-cluster rect.outer{rx:5px;ry:5px;}#mermaid-svg-G6uNBhlc4k5VK68l .statediagram-state .divider{stroke:#999;}#mermaid-svg-G6uNBhlc4k5VK68l .statediagram-state .title-state{rx:5px;ry:5px;}#mermaid-svg-G6uNBhlc4k5VK68l .statediagram-cluster.statediagram-cluster .inner{fill:#ffffff;}#mermaid-svg-G6uNBhlc4k5VK68l .statediagram-cluster.statediagram-cluster-alt .inner{fill:#e0e0e0;}#mermaid-svg-G6uNBhlc4k5VK68l .statediagram-cluster .inner{rx:0;ry:0;}#mermaid-svg-G6uNBhlc4k5VK68l .statediagram-state rect.basic{rx:5px;ry:5px;}#mermaid-svg-G6uNBhlc4k5VK68l .statediagram-state rect.divider{stroke-dasharray:10,10;fill:hsl(210,66.6666666667%,95%);}#mermaid-svg-G6uNBhlc4k5VK68l .note-edge{stroke-dasharray:5;}#mermaid-svg-G6uNBhlc4k5VK68l .statediagram-note rect{fill:#ffa;stroke:hsl(60,100%,23.3333333333%);stroke-width:1px;rx:0;ry:0;}#mermaid-svg-G6uNBhlc4k5VK68l .statediagram-note rect{fill:#ffa;stroke:hsl(60,100%,23.3333333333%);stroke-width:1px;rx:0;ry:0;}#mermaid-svg-G6uNBhlc4k5VK68l .statediagram-note text{fill:#333;}#mermaid-svg-G6uNBhlc4k5VK68l .statediagram-note .nodeLabel{color:#333;}#mermaid-svg-G6uNBhlc4k5VK68l #dependencyStart,#mermaid-svg-G6uNBhlc4k5VK68l #dependencyEnd{fill:#666;stroke:#666;stroke-width:1;}#mermaid-svg-G6uNBhlc4k5VK68l:root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;}#mermaid-svg-G6uNBhlc4k5VK68l stateDiagram{fill:apa;}</style>
User
TV
Auth

Sequence Diagram

<style>#mermaid-svg-8ywsgrc6ET6wxBa9{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#000000;}#mermaid-svg-8ywsgrc6ET6wxBa9 .error-icon{fill:#552222;}#mermaid-svg-8ywsgrc6ET6wxBa9 .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-8ywsgrc6ET6wxBa9 .edge-thickness-normal{stroke-width:2px;}#mermaid-svg-8ywsgrc6ET6wxBa9 .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-8ywsgrc6ET6wxBa9 .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-8ywsgrc6ET6wxBa9 .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-8ywsgrc6ET6wxBa9 .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-8ywsgrc6ET6wxBa9 .marker{fill:#666;stroke:#666;}#mermaid-svg-8ywsgrc6ET6wxBa9 .marker.cross{stroke:#666;}#mermaid-svg-8ywsgrc6ET6wxBa9 svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-8ywsgrc6ET6wxBa9 .actor{stroke:hsl(0,0%,83%);fill:#eee;}#mermaid-svg-8ywsgrc6ET6wxBa9 text.actor > tspan{fill:#333;stroke:none;}#mermaid-svg-8ywsgrc6ET6wxBa9 .actor-line{stroke:#666;}#mermaid-svg-8ywsgrc6ET6wxBa9 .messageLine0{stroke-width:1.5;stroke-dasharray:none;stroke:#333;}#mermaid-svg-8ywsgrc6ET6wxBa9 .messageLine1{stroke-width:1.5;stroke-dasharray:2,2;stroke:#333;}#mermaid-svg-8ywsgrc6ET6wxBa9 #arrowhead path{fill:#333;stroke:#333;}#mermaid-svg-8ywsgrc6ET6wxBa9 .sequenceNumber{fill:white;}#mermaid-svg-8ywsgrc6ET6wxBa9 #sequencenumber{fill:#333;}#mermaid-svg-8ywsgrc6ET6wxBa9 #crosshead path{fill:#333;stroke:#333;}#mermaid-svg-8ywsgrc6ET6wxBa9 .messageText{fill:#333;stroke:#333;}#mermaid-svg-8ywsgrc6ET6wxBa9 .labelBox{stroke:hsl(0,0%,83%);fill:#eee;}#mermaid-svg-8ywsgrc6ET6wxBa9 .labelText,#mermaid-svg-8ywsgrc6ET6wxBa9 .labelText > tspan{fill:#333;stroke:none;}#mermaid-svg-8ywsgrc6ET6wxBa9 .loopText,#mermaid-svg-8ywsgrc6ET6wxBa9 .loopText > tspan{fill:#333;stroke:none;}#mermaid-svg-8ywsgrc6ET6wxBa9 .loopLine{stroke-width:2px;stroke-dasharray:2,2;stroke:hsl(0,0%,83%);fill:hsl(0,0%,83%);}#mermaid-svg-8ywsgrc6ET6wxBa9 .note{stroke:hsl(60,100%,23.3333333333%);fill:#ffa;}#mermaid-svg-8ywsgrc6ET6wxBa9 .noteText,#mermaid-svg-8ywsgrc6ET6wxBa9 .noteText > tspan{fill:#333;stroke:none;}#mermaid-svg-8ywsgrc6ET6wxBa9 .activation0{fill:#f4f4f4;stroke:#666;}#mermaid-svg-8ywsgrc6ET6wxBa9 .activation1{fill:#f4f4f4;stroke:#666;}#mermaid-svg-8ywsgrc6ET6wxBa9 .activation2{fill:#f4f4f4;stroke:#666;}#mermaid-svg-8ywsgrc6ET6wxBa9:root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;}#mermaid-svg-8ywsgrc6ET6wxBa9 sequence{fill:apa;}</style>UserTVAuthauthorize(creds)verify(creds)grant(token)UserTVAuth

Class Diagram

<style>#mermaid-svg-1oqIKr7Sdwir0OEP{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#000000;}#mermaid-svg-1oqIKr7Sdwir0OEP .error-icon{fill:#552222;}#mermaid-svg-1oqIKr7Sdwir0OEP .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-1oqIKr7Sdwir0OEP .edge-thickness-normal{stroke-width:2px;}#mermaid-svg-1oqIKr7Sdwir0OEP .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-1oqIKr7Sdwir0OEP .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-1oqIKr7Sdwir0OEP .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-1oqIKr7Sdwir0OEP .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-1oqIKr7Sdwir0OEP .marker{fill:#666;stroke:#666;}#mermaid-svg-1oqIKr7Sdwir0OEP .marker.cross{stroke:#666;}#mermaid-svg-1oqIKr7Sdwir0OEP svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-1oqIKr7Sdwir0OEP g.classGroup text{fill:#999;fill:#111111;stroke:none;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:10px;}#mermaid-svg-1oqIKr7Sdwir0OEP g.classGroup text .title{font-weight:bolder;}#mermaid-svg-1oqIKr7Sdwir0OEP .classTitle{font-weight:bolder;}#mermaid-svg-1oqIKr7Sdwir0OEP .node rect,#mermaid-svg-1oqIKr7Sdwir0OEP .node circle,#mermaid-svg-1oqIKr7Sdwir0OEP .node ellipse,#mermaid-svg-1oqIKr7Sdwir0OEP .node polygon,#mermaid-svg-1oqIKr7Sdwir0OEP .node path{fill:#eee;stroke:#999;stroke-width:1px;}#mermaid-svg-1oqIKr7Sdwir0OEP .divider{stroke:#999;stroke:1;}#mermaid-svg-1oqIKr7Sdwir0OEP g.clickable{cursor:pointer;}#mermaid-svg-1oqIKr7Sdwir0OEP g.classGroup rect{fill:#eee;stroke:#999;}#mermaid-svg-1oqIKr7Sdwir0OEP g.classGroup line{stroke:#999;stroke-width:1;}#mermaid-svg-1oqIKr7Sdwir0OEP .classLabel .box{stroke:none;stroke-width:0;fill:#eee;opacity:0.5;}#mermaid-svg-1oqIKr7Sdwir0OEP .classLabel .label{fill:#999;font-size:10px;}#mermaid-svg-1oqIKr7Sdwir0OEP .relation{stroke:#666;stroke-width:1;fill:none;}#mermaid-svg-1oqIKr7Sdwir0OEP .dashed-line{stroke-dasharray:3;}#mermaid-svg-1oqIKr7Sdwir0OEP #compositionStart,#mermaid-svg-1oqIKr7Sdwir0OEP .composition{fill:#666 !important;stroke:#666 !important;stroke-width:1;}#mermaid-svg-1oqIKr7Sdwir0OEP #compositionEnd,#mermaid-svg-1oqIKr7Sdwir0OEP .composition{fill:#666 !important;stroke:#666 !important;stroke-width:1;}#mermaid-svg-1oqIKr7Sdwir0OEP #dependencyStart,#mermaid-svg-1oqIKr7Sdwir0OEP .dependency{fill:#666 !important;stroke:#666 !important;stroke-width:1;}#mermaid-svg-1oqIKr7Sdwir0OEP #dependencyStart,#mermaid-svg-1oqIKr7Sdwir0OEP .dependency{fill:#666 !important;stroke:#666 !important;stroke-width:1;}#mermaid-svg-1oqIKr7Sdwir0OEP #extensionStart,#mermaid-svg-1oqIKr7Sdwir0OEP .extension{fill:#666 !important;stroke:#666 !important;stroke-width:1;}#mermaid-svg-1oqIKr7Sdwir0OEP #extensionEnd,#mermaid-svg-1oqIKr7Sdwir0OEP .extension{fill:#666 !important;stroke:#666 !important;stroke-width:1;}#mermaid-svg-1oqIKr7Sdwir0OEP #aggregationStart,#mermaid-svg-1oqIKr7Sdwir0OEP .aggregation{fill:#eee !important;stroke:#666 !important;stroke-width:1;}#mermaid-svg-1oqIKr7Sdwir0OEP #aggregationEnd,#mermaid-svg-1oqIKr7Sdwir0OEP .aggregation{fill:#eee !important;stroke:#666 !important;stroke-width:1;}#mermaid-svg-1oqIKr7Sdwir0OEP .edgeTerminals{font-size:11px;}#mermaid-svg-1oqIKr7Sdwir0OEP:root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;}#mermaid-svg-1oqIKr7Sdwir0OEP class{fill:apa;}</style>TVCreds credsToken tokenAuth authauthorize(creds) : tokenAuthMap<Creds,Tokens> authorizationsverify(creds) : token

TV:

  • ⚡️ tv/authorize(creds)
    • next: auth/verify(creds)
  • ⚡️📡 auth/verify: ajaxAuth()
    • next: auth/grant(token)
    • error: auth/error(err)
  • ⚡️⚛️ auth/grant: handleToken()

Auth:

  • ⚡️⚛️ auth/verify(creds, device): createTokenForDevice
    • next: auth/grant(token, device)
  • ⚡️📡 auth/grant: pushToken(device)

Step 2: Two-Factor Auth (2FA)

Challenges:

  • The User is definitely an async actor
  • The Auth Service should push a token to the TV

Dependency Graph

<style>#mermaid-svg-IGqspSpq8FwFYhTM{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#000000;}#mermaid-svg-IGqspSpq8FwFYhTM .error-icon{fill:#552222;}#mermaid-svg-IGqspSpq8FwFYhTM .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-IGqspSpq8FwFYhTM .edge-thickness-normal{stroke-width:2px;}#mermaid-svg-IGqspSpq8FwFYhTM .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-IGqspSpq8FwFYhTM .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-IGqspSpq8FwFYhTM .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-IGqspSpq8FwFYhTM .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-IGqspSpq8FwFYhTM .marker{fill:#666;stroke:#666;}#mermaid-svg-IGqspSpq8FwFYhTM .marker.cross{stroke:#666;}#mermaid-svg-IGqspSpq8FwFYhTM svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-IGqspSpq8FwFYhTM .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#000000;}#mermaid-svg-IGqspSpq8FwFYhTM .cluster-label text{fill:#333;}#mermaid-svg-IGqspSpq8FwFYhTM .cluster-label span{color:#333;}#mermaid-svg-IGqspSpq8FwFYhTM .label text,#mermaid-svg-IGqspSpq8FwFYhTM span{fill:#000000;color:#000000;}#mermaid-svg-IGqspSpq8FwFYhTM .node rect,#mermaid-svg-IGqspSpq8FwFYhTM .node circle,#mermaid-svg-IGqspSpq8FwFYhTM .node ellipse,#mermaid-svg-IGqspSpq8FwFYhTM .node polygon,#mermaid-svg-IGqspSpq8FwFYhTM .node path{fill:#eee;stroke:#999;stroke-width:1px;}#mermaid-svg-IGqspSpq8FwFYhTM .node .label{text-align:center;}#mermaid-svg-IGqspSpq8FwFYhTM .node.clickable{cursor:pointer;}#mermaid-svg-IGqspSpq8FwFYhTM .arrowheadPath{fill:#333333;}#mermaid-svg-IGqspSpq8FwFYhTM .edgePath .path{stroke:#666;stroke-width:1.5px;}#mermaid-svg-IGqspSpq8FwFYhTM .flowchart-link{stroke:#666;fill:none;}#mermaid-svg-IGqspSpq8FwFYhTM .edgeLabel{background-color:white;text-align:center;}#mermaid-svg-IGqspSpq8FwFYhTM .edgeLabel rect{opacity:0.5;background-color:white;fill:white;}#mermaid-svg-IGqspSpq8FwFYhTM .cluster rect{fill:hsl(210,66.6666666667%,95%);stroke:#26a;stroke-width:1px;}#mermaid-svg-IGqspSpq8FwFYhTM .cluster text{fill:#333;}#mermaid-svg-IGqspSpq8FwFYhTM .cluster span{color:#333;}#mermaid-svg-IGqspSpq8FwFYhTM div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(-160,0%,93.3333333333%);border:1px solid #26a;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-IGqspSpq8FwFYhTM:root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;}#mermaid-svg-IGqspSpq8FwFYhTM flowchart{fill:apa;}</style>
User
TV
WWW
Auth

Data Flow diagram

<style>#mermaid-svg-teMi6qN8KuuoTHS7{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#000000;}#mermaid-svg-teMi6qN8KuuoTHS7 .error-icon{fill:#552222;}#mermaid-svg-teMi6qN8KuuoTHS7 .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-teMi6qN8KuuoTHS7 .edge-thickness-normal{stroke-width:2px;}#mermaid-svg-teMi6qN8KuuoTHS7 .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-teMi6qN8KuuoTHS7 .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-teMi6qN8KuuoTHS7 .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-teMi6qN8KuuoTHS7 .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-teMi6qN8KuuoTHS7 .marker{fill:#666;stroke:#666;}#mermaid-svg-teMi6qN8KuuoTHS7 .marker.cross{stroke:#666;}#mermaid-svg-teMi6qN8KuuoTHS7 svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-teMi6qN8KuuoTHS7 g.stateGroup text{fill:#999;stroke:none;font-size:10px;}#mermaid-svg-teMi6qN8KuuoTHS7 g.stateGroup text{fill:#000000;stroke:none;font-size:10px;}#mermaid-svg-teMi6qN8KuuoTHS7 g.stateGroup .state-title{font-weight:bolder;fill:black;}#mermaid-svg-teMi6qN8KuuoTHS7 g.stateGroup rect{fill:#eee;stroke:#999;}#mermaid-svg-teMi6qN8KuuoTHS7 g.stateGroup line{stroke:#666;stroke-width:1;}#mermaid-svg-teMi6qN8KuuoTHS7 .transition{stroke:#666;stroke-width:1;fill:none;}#mermaid-svg-teMi6qN8KuuoTHS7 .stateGroup .composit{fill:#ffffff;border-bottom:1px;}#mermaid-svg-teMi6qN8KuuoTHS7 .stateGroup .alt-composit{fill:#e0e0e0;border-bottom:1px;}#mermaid-svg-teMi6qN8KuuoTHS7 .state-note{stroke:hsl(60,100%,23.3333333333%);fill:#ffa;}#mermaid-svg-teMi6qN8KuuoTHS7 .state-note text{fill:black;stroke:none;font-size:10px;}#mermaid-svg-teMi6qN8KuuoTHS7 .stateLabel .box{stroke:none;stroke-width:0;fill:#eee;opacity:0.5;}#mermaid-svg-teMi6qN8KuuoTHS7 .edgeLabel .label rect{fill:hsl(-160,0%,93.3333333333%);opacity:0.5;}#mermaid-svg-teMi6qN8KuuoTHS7 .edgeLabel .label text{fill:rgb(17.0000000001,17.0000000001,17.0000000001);}#mermaid-svg-teMi6qN8KuuoTHS7 .label div .edgeLabel{color:rgb(17.0000000001,17.0000000001,17.0000000001);}#mermaid-svg-teMi6qN8KuuoTHS7 .stateLabel text{fill:black;font-size:10px;font-weight:bold;}#mermaid-svg-teMi6qN8KuuoTHS7 .node circle.state-start{fill:#666;stroke:black;}#mermaid-svg-teMi6qN8KuuoTHS7 .node circle.state-end{fill:hsl(0,0%,83.3333333333%);stroke:#ffffff;stroke-width:1.5;}#mermaid-svg-teMi6qN8KuuoTHS7 .end-state-inner{fill:#ffffff;stroke-width:1.5;}#mermaid-svg-teMi6qN8KuuoTHS7 .node rect{fill:#eee;stroke:#999;stroke-width:1px;}#mermaid-svg-teMi6qN8KuuoTHS7 #statediagram-barbEnd{fill:#666;}#mermaid-svg-teMi6qN8KuuoTHS7 .statediagram-cluster rect{fill:#eee;stroke:#999;stroke-width:1px;}#mermaid-svg-teMi6qN8KuuoTHS7 .cluster-label,#mermaid-svg-teMi6qN8KuuoTHS7 .nodeLabel{color:#000000;}#mermaid-svg-teMi6qN8KuuoTHS7 .statediagram-cluster rect.outer{rx:5px;ry:5px;}#mermaid-svg-teMi6qN8KuuoTHS7 .statediagram-state .divider{stroke:#999;}#mermaid-svg-teMi6qN8KuuoTHS7 .statediagram-state .title-state{rx:5px;ry:5px;}#mermaid-svg-teMi6qN8KuuoTHS7 .statediagram-cluster.statediagram-cluster .inner{fill:#ffffff;}#mermaid-svg-teMi6qN8KuuoTHS7 .statediagram-cluster.statediagram-cluster-alt .inner{fill:#e0e0e0;}#mermaid-svg-teMi6qN8KuuoTHS7 .statediagram-cluster .inner{rx:0;ry:0;}#mermaid-svg-teMi6qN8KuuoTHS7 .statediagram-state rect.basic{rx:5px;ry:5px;}#mermaid-svg-teMi6qN8KuuoTHS7 .statediagram-state rect.divider{stroke-dasharray:10,10;fill:hsl(210,66.6666666667%,95%);}#mermaid-svg-teMi6qN8KuuoTHS7 .note-edge{stroke-dasharray:5;}#mermaid-svg-teMi6qN8KuuoTHS7 .statediagram-note rect{fill:#ffa;stroke:hsl(60,100%,23.3333333333%);stroke-width:1px;rx:0;ry:0;}#mermaid-svg-teMi6qN8KuuoTHS7 .statediagram-note rect{fill:#ffa;stroke:hsl(60,100%,23.3333333333%);stroke-width:1px;rx:0;ry:0;}#mermaid-svg-teMi6qN8KuuoTHS7 .statediagram-note text{fill:#333;}#mermaid-svg-teMi6qN8KuuoTHS7 .statediagram-note .nodeLabel{color:#333;}#mermaid-svg-teMi6qN8KuuoTHS7 #dependencyStart,#mermaid-svg-teMi6qN8KuuoTHS7 #dependencyEnd{fill:#666;stroke:#666;stroke-width:1;}#mermaid-svg-teMi6qN8KuuoTHS7:root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;}#mermaid-svg-teMi6qN8KuuoTHS7 stateDiagram{fill:apa;}</style>
(display challenge)
(push token)
User
TV
WWW
Auth

Sequence Diagram

<style>#mermaid-svg-qdnoVasOoRgsCZe6{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#000000;}#mermaid-svg-qdnoVasOoRgsCZe6 .error-icon{fill:#552222;}#mermaid-svg-qdnoVasOoRgsCZe6 .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-qdnoVasOoRgsCZe6 .edge-thickness-normal{stroke-width:2px;}#mermaid-svg-qdnoVasOoRgsCZe6 .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-qdnoVasOoRgsCZe6 .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-qdnoVasOoRgsCZe6 .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-qdnoVasOoRgsCZe6 .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-qdnoVasOoRgsCZe6 .marker{fill:#666;stroke:#666;}#mermaid-svg-qdnoVasOoRgsCZe6 .marker.cross{stroke:#666;}#mermaid-svg-qdnoVasOoRgsCZe6 svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-qdnoVasOoRgsCZe6 .actor{stroke:hsl(0,0%,83%);fill:#eee;}#mermaid-svg-qdnoVasOoRgsCZe6 text.actor > tspan{fill:#333;stroke:none;}#mermaid-svg-qdnoVasOoRgsCZe6 .actor-line{stroke:#666;}#mermaid-svg-qdnoVasOoRgsCZe6 .messageLine0{stroke-width:1.5;stroke-dasharray:none;stroke:#333;}#mermaid-svg-qdnoVasOoRgsCZe6 .messageLine1{stroke-width:1.5;stroke-dasharray:2,2;stroke:#333;}#mermaid-svg-qdnoVasOoRgsCZe6 #arrowhead path{fill:#333;stroke:#333;}#mermaid-svg-qdnoVasOoRgsCZe6 .sequenceNumber{fill:white;}#mermaid-svg-qdnoVasOoRgsCZe6 #sequencenumber{fill:#333;}#mermaid-svg-qdnoVasOoRgsCZe6 #crosshead path{fill:#333;stroke:#333;}#mermaid-svg-qdnoVasOoRgsCZe6 .messageText{fill:#333;stroke:#333;}#mermaid-svg-qdnoVasOoRgsCZe6 .labelBox{stroke:hsl(0,0%,83%);fill:#eee;}#mermaid-svg-qdnoVasOoRgsCZe6 .labelText,#mermaid-svg-qdnoVasOoRgsCZe6 .labelText > tspan{fill:#333;stroke:none;}#mermaid-svg-qdnoVasOoRgsCZe6 .loopText,#mermaid-svg-qdnoVasOoRgsCZe6 .loopText > tspan{fill:#333;stroke:none;}#mermaid-svg-qdnoVasOoRgsCZe6 .loopLine{stroke-width:2px;stroke-dasharray:2,2;stroke:hsl(0,0%,83%);fill:hsl(0,0%,83%);}#mermaid-svg-qdnoVasOoRgsCZe6 .note{stroke:hsl(60,100%,23.3333333333%);fill:#ffa;}#mermaid-svg-qdnoVasOoRgsCZe6 .noteText,#mermaid-svg-qdnoVasOoRgsCZe6 .noteText > tspan{fill:#333;stroke:none;}#mermaid-svg-qdnoVasOoRgsCZe6 .activation0{fill:#f4f4f4;stroke:#666;}#mermaid-svg-qdnoVasOoRgsCZe6 .activation1{fill:#f4f4f4;stroke:#666;}#mermaid-svg-qdnoVasOoRgsCZe6 .activation2{fill:#f4f4f4;stroke:#666;}#mermaid-svg-qdnoVasOoRgsCZe6:root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;}#mermaid-svg-qdnoVasOoRgsCZe6 sequence{fill:apa;}</style>UserTVAuthWWWauthorize()authorize(deviceID)challenge(code)challenge(code)authorize(code, creds)verify(code, creds)grant(token)UserTVAuthWWW

Class Diagram

<style>#mermaid-svg-w5OBx5n9zGm0rhNQ{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#000000;}#mermaid-svg-w5OBx5n9zGm0rhNQ .error-icon{fill:#552222;}#mermaid-svg-w5OBx5n9zGm0rhNQ .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-w5OBx5n9zGm0rhNQ .edge-thickness-normal{stroke-width:2px;}#mermaid-svg-w5OBx5n9zGm0rhNQ .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-w5OBx5n9zGm0rhNQ .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-w5OBx5n9zGm0rhNQ .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-w5OBx5n9zGm0rhNQ .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-w5OBx5n9zGm0rhNQ .marker{fill:#666;stroke:#666;}#mermaid-svg-w5OBx5n9zGm0rhNQ .marker.cross{stroke:#666;}#mermaid-svg-w5OBx5n9zGm0rhNQ svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-w5OBx5n9zGm0rhNQ g.classGroup text{fill:#999;fill:#111111;stroke:none;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:10px;}#mermaid-svg-w5OBx5n9zGm0rhNQ g.classGroup text .title{font-weight:bolder;}#mermaid-svg-w5OBx5n9zGm0rhNQ .classTitle{font-weight:bolder;}#mermaid-svg-w5OBx5n9zGm0rhNQ .node rect,#mermaid-svg-w5OBx5n9zGm0rhNQ .node circle,#mermaid-svg-w5OBx5n9zGm0rhNQ .node ellipse,#mermaid-svg-w5OBx5n9zGm0rhNQ .node polygon,#mermaid-svg-w5OBx5n9zGm0rhNQ .node path{fill:#eee;stroke:#999;stroke-width:1px;}#mermaid-svg-w5OBx5n9zGm0rhNQ .divider{stroke:#999;stroke:1;}#mermaid-svg-w5OBx5n9zGm0rhNQ g.clickable{cursor:pointer;}#mermaid-svg-w5OBx5n9zGm0rhNQ g.classGroup rect{fill:#eee;stroke:#999;}#mermaid-svg-w5OBx5n9zGm0rhNQ g.classGroup line{stroke:#999;stroke-width:1;}#mermaid-svg-w5OBx5n9zGm0rhNQ .classLabel .box{stroke:none;stroke-width:0;fill:#eee;opacity:0.5;}#mermaid-svg-w5OBx5n9zGm0rhNQ .classLabel .label{fill:#999;font-size:10px;}#mermaid-svg-w5OBx5n9zGm0rhNQ .relation{stroke:#666;stroke-width:1;fill:none;}#mermaid-svg-w5OBx5n9zGm0rhNQ .dashed-line{stroke-dasharray:3;}#mermaid-svg-w5OBx5n9zGm0rhNQ #compositionStart,#mermaid-svg-w5OBx5n9zGm0rhNQ .composition{fill:#666 !important;stroke:#666 !important;stroke-width:1;}#mermaid-svg-w5OBx5n9zGm0rhNQ #compositionEnd,#mermaid-svg-w5OBx5n9zGm0rhNQ .composition{fill:#666 !important;stroke:#666 !important;stroke-width:1;}#mermaid-svg-w5OBx5n9zGm0rhNQ #dependencyStart,#mermaid-svg-w5OBx5n9zGm0rhNQ .dependency{fill:#666 !important;stroke:#666 !important;stroke-width:1;}#mermaid-svg-w5OBx5n9zGm0rhNQ #dependencyStart,#mermaid-svg-w5OBx5n9zGm0rhNQ .dependency{fill:#666 !important;stroke:#666 !important;stroke-width:1;}#mermaid-svg-w5OBx5n9zGm0rhNQ #extensionStart,#mermaid-svg-w5OBx5n9zGm0rhNQ .extension{fill:#666 !important;stroke:#666 !important;stroke-width:1;}#mermaid-svg-w5OBx5n9zGm0rhNQ #extensionEnd,#mermaid-svg-w5OBx5n9zGm0rhNQ .extension{fill:#666 !important;stroke:#666 !important;stroke-width:1;}#mermaid-svg-w5OBx5n9zGm0rhNQ #aggregationStart,#mermaid-svg-w5OBx5n9zGm0rhNQ .aggregation{fill:#eee !important;stroke:#666 !important;stroke-width:1;}#mermaid-svg-w5OBx5n9zGm0rhNQ #aggregationEnd,#mermaid-svg-w5OBx5n9zGm0rhNQ .aggregation{fill:#eee !important;stroke:#666 !important;stroke-width:1;}#mermaid-svg-w5OBx5n9zGm0rhNQ .edgeTerminals{font-size:11px;}#mermaid-svg-w5OBx5n9zGm0rhNQ:root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;}#mermaid-svg-w5OBx5n9zGm0rhNQ class{fill:apa;}</style>TVDevice deviceIdToken tokenCode codeAuth authauthorize(deviceId) : Promise<token>display(code)AuthMap<Code, Device> codesMap<Device, Creds> devicesMap<Creds,Tokens> authorizationsauthorize(deviceID) : codeverify(code, creds) : tokenWWWCode codeAuth authshowAuthPage() : voidauthorize(code, creds) : void

Message Graph

<style>#mermaid-svg-ZuRKg62DaE1QujDk{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#000000;}#mermaid-svg-ZuRKg62DaE1QujDk .error-icon{fill:#552222;}#mermaid-svg-ZuRKg62DaE1QujDk .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-ZuRKg62DaE1QujDk .edge-thickness-normal{stroke-width:2px;}#mermaid-svg-ZuRKg62DaE1QujDk .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-ZuRKg62DaE1QujDk .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-ZuRKg62DaE1QujDk .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-ZuRKg62DaE1QujDk .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-ZuRKg62DaE1QujDk .marker{fill:#666;stroke:#666;}#mermaid-svg-ZuRKg62DaE1QujDk .marker.cross{stroke:#666;}#mermaid-svg-ZuRKg62DaE1QujDk svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-ZuRKg62DaE1QujDk g.stateGroup text{fill:#999;stroke:none;font-size:10px;}#mermaid-svg-ZuRKg62DaE1QujDk g.stateGroup text{fill:#000000;stroke:none;font-size:10px;}#mermaid-svg-ZuRKg62DaE1QujDk g.stateGroup .state-title{font-weight:bolder;fill:black;}#mermaid-svg-ZuRKg62DaE1QujDk g.stateGroup rect{fill:#eee;stroke:#999;}#mermaid-svg-ZuRKg62DaE1QujDk g.stateGroup line{stroke:#666;stroke-width:1;}#mermaid-svg-ZuRKg62DaE1QujDk .transition{stroke:#666;stroke-width:1;fill:none;}#mermaid-svg-ZuRKg62DaE1QujDk .stateGroup .composit{fill:#ffffff;border-bottom:1px;}#mermaid-svg-ZuRKg62DaE1QujDk .stateGroup .alt-composit{fill:#e0e0e0;border-bottom:1px;}#mermaid-svg-ZuRKg62DaE1QujDk .state-note{stroke:hsl(60,100%,23.3333333333%);fill:#ffa;}#mermaid-svg-ZuRKg62DaE1QujDk .state-note text{fill:black;stroke:none;font-size:10px;}#mermaid-svg-ZuRKg62DaE1QujDk .stateLabel .box{stroke:none;stroke-width:0;fill:#eee;opacity:0.5;}#mermaid-svg-ZuRKg62DaE1QujDk .edgeLabel .label rect{fill:hsl(-160,0%,93.3333333333%);opacity:0.5;}#mermaid-svg-ZuRKg62DaE1QujDk .edgeLabel .label text{fill:rgb(17.0000000001,17.0000000001,17.0000000001);}#mermaid-svg-ZuRKg62DaE1QujDk .label div .edgeLabel{color:rgb(17.0000000001,17.0000000001,17.0000000001);}#mermaid-svg-ZuRKg62DaE1QujDk .stateLabel text{fill:black;font-size:10px;font-weight:bold;}#mermaid-svg-ZuRKg62DaE1QujDk .node circle.state-start{fill:#666;stroke:black;}#mermaid-svg-ZuRKg62DaE1QujDk .node circle.state-end{fill:hsl(0,0%,83.3333333333%);stroke:#ffffff;stroke-width:1.5;}#mermaid-svg-ZuRKg62DaE1QujDk .end-state-inner{fill:#ffffff;stroke-width:1.5;}#mermaid-svg-ZuRKg62DaE1QujDk .node rect{fill:#eee;stroke:#999;stroke-width:1px;}#mermaid-svg-ZuRKg62DaE1QujDk #statediagram-barbEnd{fill:#666;}#mermaid-svg-ZuRKg62DaE1QujDk .statediagram-cluster rect{fill:#eee;stroke:#999;stroke-width:1px;}#mermaid-svg-ZuRKg62DaE1QujDk .cluster-label,#mermaid-svg-ZuRKg62DaE1QujDk .nodeLabel{color:#000000;}#mermaid-svg-ZuRKg62DaE1QujDk .statediagram-cluster rect.outer{rx:5px;ry:5px;}#mermaid-svg-ZuRKg62DaE1QujDk .statediagram-state .divider{stroke:#999;}#mermaid-svg-ZuRKg62DaE1QujDk .statediagram-state .title-state{rx:5px;ry:5px;}#mermaid-svg-ZuRKg62DaE1QujDk .statediagram-cluster.statediagram-cluster .inner{fill:#ffffff;}#mermaid-svg-ZuRKg62DaE1QujDk .statediagram-cluster.statediagram-cluster-alt .inner{fill:#e0e0e0;}#mermaid-svg-ZuRKg62DaE1QujDk .statediagram-cluster .inner{rx:0;ry:0;}#mermaid-svg-ZuRKg62DaE1QujDk .statediagram-state rect.basic{rx:5px;ry:5px;}#mermaid-svg-ZuRKg62DaE1QujDk .statediagram-state rect.divider{stroke-dasharray:10,10;fill:hsl(210,66.6666666667%,95%);}#mermaid-svg-ZuRKg62DaE1QujDk .note-edge{stroke-dasharray:5;}#mermaid-svg-ZuRKg62DaE1QujDk .statediagram-note rect{fill:#ffa;stroke:hsl(60,100%,23.3333333333%);stroke-width:1px;rx:0;ry:0;}#mermaid-svg-ZuRKg62DaE1QujDk .statediagram-note rect{fill:#ffa;stroke:hsl(60,100%,23.3333333333%);stroke-width:1px;rx:0;ry:0;}#mermaid-svg-ZuRKg62DaE1QujDk .statediagram-note text{fill:#333;}#mermaid-svg-ZuRKg62DaE1QujDk .statediagram-note .nodeLabel{color:#333;}#mermaid-svg-ZuRKg62DaE1QujDk #dependencyStart,#mermaid-svg-ZuRKg62DaE1QujDk #dependencyEnd{fill:#666;stroke:#666;stroke-width:1;}#mermaid-svg-ZuRKg62DaE1QujDk:root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;}#mermaid-svg-ZuRKg62DaE1QujDk stateDiagram{fill:apa;}</style>
tv/authorize()
auth/verify(code, creds)
auth/authorize(deviceId)
www/authorize(code, creds)
auth/challenge(code)
auth/grant(token)
User
TV
WWW
Auth

Event Consequences

TV:

  • ⚡️📡 tv/authorize
    • next: auth/authorize(deviceId)
  • ⚡️⚛️ auth/challenge: displayChallenge
  • ⚡️⚛️ auth/grant: handleToken

WWW:

  • ⚡️📡 www/showAuthPage: showAuthPage
    • until: auth/verify/complete
  • ⚡️📡 www/authorize(code, creds): auth.verify
    • next: auth/verify/complete

Auth:

  • ⚡️⚛️ 📡 auth/authorize(deviceId): createDeviceCode
    • next: auth/challenge(deviceId, code)
  • ⚡️📡 auth/challenge(deviceId, code): sendDeviceCode
  • ⚛️ auth/verify(code, creds): verifyCode
    • next: auth/grant(token)
  • ⚡️📡 auth/grant(token, deviceId): pushToken

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment