diff --git a/src/assets/stylesheets/index.scss b/src/assets/stylesheets/index.scss index 6b1d3b5ffe..f1b8517025 100644 --- a/src/assets/stylesheets/index.scss +++ b/src/assets/stylesheets/index.scss @@ -80,6 +80,19 @@ body { } } } + + :local(.sign-in) { + flex: 3; + display: flex; + align-items: center; + justify-content: end; + + a { + text-decoration: underline; + font-weight: bold; + cursor: pointer; + } + } } :local(.video-container) { diff --git a/src/hub.js b/src/hub.js index 3a0c082ee8..c85e2454f3 100644 --- a/src/hub.js +++ b/src/hub.js @@ -531,6 +531,10 @@ document.addEventListener("DOMContentLoaded", async () => { const pushSubscriptionEndpoint = await subscriptions.getCurrentEndpoint(); const joinPayload = { profile: store.state.profile, push_subscription_endpoint: pushSubscriptionEndpoint, context }; + const { token } = store.state.credentials; + if (token) { + joinPayload.auth_token = token; + } const hubPhxChannel = socket.channel(`hub:${hubId}`, joinPayload); const presenceLogEntries = []; @@ -559,15 +563,9 @@ document.addEventListener("DOMContentLoaded", async () => { .join() .receive("ok", async data => { hubChannel.setPhoenixChannel(hubPhxChannel); - - const { token } = store.state.credentials; - if (token) { - await hubChannel.signIn(token); - } - subscriptions.setHubChannel(hubChannel); subscriptions.setSubscribed(data.subscriptions.web_push); - remountUI({ initialIsSubscribed: subscriptions.isSubscribed() }); + remountUI({ initialIsSubscribed: subscriptions.isSubscribed(), isOwner: data.is_owner }); await handleHubChannelJoined(entryManager, hubChannel, messageDispatch, data); }) .receive("error", res => { diff --git a/src/index.js b/src/index.js index b66ec76162..769bc86486 100644 --- a/src/index.js +++ b/src/index.js @@ -4,7 +4,9 @@ import ReactDOM from "react-dom"; import "./assets/stylesheets/index.scss"; import registerTelemetry from "./telemetry"; import HomeRoot from "./react-components/home-root"; -import { createAndRedirectToNewHub } from "./utils/phoenix-utils"; +import AuthChannel from "./utils/auth-channel"; +import { createAndRedirectToNewHub, connectToReticulum } from "./utils/phoenix-utils"; +import Store from "./storage/store"; const qs = new URLSearchParams(location.search); registerTelemetry(); @@ -23,10 +25,16 @@ const sceneId = qs.get("scene_id") || (pathname.startsWith("/scenes/") && pathna return; } + const store = new Store(); + const authChannel = new AuthChannel(store); + authChannel.setSocket(connectToReticulum()); + const root = ( { + this.setState({ + dialog: + }); + }; + showAuthDialog = verifying => { - this.setState({ dialog: }); + this.showDialog(AuthDialog, { closable: false, verifying, authOrigin: this.props.authOrigin }); }; loadHomeVideo = () => { @@ -87,33 +103,40 @@ class HomeRoot extends Component { playVideoWithStopOnBlur(videoEl); }; - closeDialog() { + closeDialog = () => { this.setState({ dialog: null }); - } + }; - showJoinUsDialog() { - this.setState({ dialog: }); - } + showJoinUsDialog = () => this.showDialog(JoinUsDialog); - showReportDialog() { - this.setState({ dialog: }); - } + showReportDialog = () => this.showDialog(ReportDialog); - showUpdatesDialog() { - this.setState({ - dialog: this.showEmailSubmittedDialog()} /> + showUpdatesDialog = () => + this.showDialog(UpdatesDialog, { + onSubmittedEmail: () => { + this.showDialog( + Great! Please check your e-mail to confirm your subscription. + ); + } }); - } - showEmailSubmittedDialog() { - this.setState({ - dialog: ( - - Great! Please check your e-mail to confirm your subscription. - - ) + showSignInDialog = () => { + this.showDialog(SignInDialog, { + message: messages["sign-in.prompt"], + onSignIn: async email => { + const { authComplete } = await this.props.authChannel.startAuthentication(email); + this.showDialog(SignInDialog, { authStarted: true }); + await authComplete; + this.setState({ signedIn: true, email }); + this.closeDialog(); + } }); - } + }; + + signOut = () => { + this.props.authChannel.signOut(); + this.setState({ signedIn: false }); + }; loadEnvironmentFromScene = async () => { let sceneUrlBase = "/api/v1/scenes"; @@ -155,7 +178,7 @@ class HomeRoot extends Component { Promise.all(environmentLoads).then(() => this.setState({ environments })); }; - onDialogLinkClicked = trigger => { + onLinkClicked = trigger => { return e => { e.preventDefault(); e.stopPropagation(); @@ -204,6 +227,22 @@ class HomeRoot extends Component { +
+ {this.state.signedIn ? ( +
+ + {maskEmail(this.state.email)} + {" "} + + + +
+ ) : ( + + + + )} +
@@ -264,7 +303,7 @@ class HomeRoot extends Component { className={styles.link} rel="noopener noreferrer" href="#" - onClick={this.onDialogLinkClicked(this.showJoinUsDialog.bind(this))} + onClick={this.onLinkClicked(this.showJoinUsDialog)} > @@ -274,7 +313,7 @@ class HomeRoot extends Component { className={styles.link} rel="noopener noreferrer" href="#" - onClick={this.onDialogLinkClicked(this.showUpdatesDialog.bind(this))} + onClick={this.onLinkClicked(this.showUpdatesDialog)} > @@ -284,7 +323,7 @@ class HomeRoot extends Component { className={styles.link} rel="noopener noreferrer" href="#" - onClick={this.onDialogLinkClicked(this.showReportDialog.bind(this))} + onClick={this.onLinkClicked(this.showReportDialog)} > diff --git a/src/react-components/scene-ui.js b/src/react-components/scene-ui.js index 423385bac2..822be821d3 100644 --- a/src/react-components/scene-ui.js +++ b/src/react-components/scene-ui.js @@ -6,8 +6,7 @@ import en from "react-intl/locale-data/en"; import styles from "../assets/stylesheets/scene-ui.scss"; import hubLogo from "../assets/images/hub-preview-white.png"; import spokeLogo from "../assets/images/spoke_logo_black.png"; -import { getReticulumFetchUrl } from "../utils/phoenix-utils"; -import { generateHubName } from "../utils/name-generation"; +import { createAndRedirectToNewHub } from "../utils/phoenix-utils"; import { WithHoverSound } from "./wrap-with-audio"; import CreateRoomDialog from "./create-room-dialog.js"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; @@ -52,23 +51,8 @@ class SceneUI extends Component { this.props.scene.removeEventListener("loaded", this.onSceneLoaded); } - createRoom = async () => { - const payload = { hub: { name: this.state.customRoomName || generateHubName(), scene_id: this.props.sceneId } }; - const createUrl = getReticulumFetchUrl("/api/v1/hubs"); - - const res = await fetch(createUrl, { - body: JSON.stringify(payload), - headers: { "content-type": "application/json" }, - method: "POST" - }); - - const hub = await res.json(); - - if (!process.env.RETICULUM_SERVER || document.location.host === process.env.RETICULUM_SERVER) { - document.location = hub.url; - } else { - document.location = `/hub.html?hub_id=${hub.hub_id}`; - } + createRoom = () => { + createAndRedirectToNewHub(this.state.customRoomName, this.props.sceneId); }; render() { diff --git a/src/react-components/ui-root.js b/src/react-components/ui-root.js index 1ec24f0d88..bd291d9008 100644 --- a/src/react-components/ui-root.js +++ b/src/react-components/ui-root.js @@ -109,6 +109,7 @@ class UIRoot extends Component { platformUnsupportedReason: PropTypes.string, hubId: PropTypes.string, hubName: PropTypes.string, + isOwner: PropTypes.bool, hubScene: PropTypes.object, isSupportAvailable: PropTypes.bool, presenceLogEntries: PropTypes.array, diff --git a/src/utils/auth-channel.js b/src/utils/auth-channel.js index b50f7d1fbc..0574c21b9f 100644 --- a/src/utils/auth-channel.js +++ b/src/utils/auth-channel.js @@ -4,15 +4,27 @@ export default class AuthChannel { constructor(store) { this.store = store; this.socket = null; + this._authenticated = !!this.store.state.credentials.token; } setSocket = socket => { this.socket = socket; }; + get email() { + return this.store.state.credentials.email; + } + + get authenticated() { + return this._authenticated; + } + signOut = async hubChannel => { - await hubChannel.signOut(); + if (hubChannel) { + await hubChannel.signOut(); + } this.store.update({ credentials: { token: null, email: null } }); + this._authenticated = false; }; async startAuthentication(email, hubChannel) { @@ -27,7 +39,10 @@ export default class AuthChannel { const authComplete = new Promise(resolve => channel.on("auth_credentials", async ({ credentials: token }) => { this.store.update({ credentials: { email, token } }); - await hubChannel.signIn(token); + if (hubChannel) { + await hubChannel.signIn(token); + } + this._authenticated = true; resolve(); }) ); diff --git a/src/utils/hub-channel.js b/src/utils/hub-channel.js index c3b0e31546..64b73132eb 100644 --- a/src/utils/hub-channel.js +++ b/src/utils/hub-channel.js @@ -12,7 +12,7 @@ function isSameDay(da, db) { export default class HubChannel { constructor(store) { this.store = store; - this._signedIn = false; + this._signedIn = !!this.store.state.credentials.token; } get signedIn() { diff --git a/src/utils/phoenix-utils.js b/src/utils/phoenix-utils.js index cb24ed7b6e..f68e590a49 100644 --- a/src/utils/phoenix-utils.js +++ b/src/utils/phoenix-utils.js @@ -2,6 +2,8 @@ import uuid from "uuid/v4"; import { Socket } from "phoenix"; import { generateHubName } from "../utils/name-generation"; +import Store from "../storage/store"; + export function connectToReticulum(debug = false) { const qs = new URLSearchParams(location.search); @@ -64,9 +66,15 @@ export async function createAndRedirectToNewHub(name, sceneId, sceneUrl, replace payload.hub.default_environment_gltf_bundle_url = sceneUrl; } + const headers = { "content-type": "application/json" }; + const store = new Store(); + if (store.state && store.state.credentials.token) { + headers.authorization = `bearer ${store.state.credentials.token}`; + } + const res = await fetch(createUrl, { body: JSON.stringify(payload), - headers: { "content-type": "application/json" }, + headers, method: "POST" });