From 60afd6c52c44643daaae2066259074f2c0785716 Mon Sep 17 00:00:00 2001
From: Greg Fodor <gfodor@gmail.com>
Date: Sat, 21 Apr 2018 19:17:50 -0700
Subject: [PATCH] Clean up schema of local storage, remove ToS check

---
 src/assets/stylesheets/profile.scss         | 25 ----------
 src/assets/translations.data.json           |  5 --
 src/hub.js                                  | 14 +++---
 src/react-components/profile-entry-panel.js | 54 +++++++--------------
 src/react-components/ui-root.js             |  6 +--
 src/storage/store.js                        | 35 ++++++++-----
 src/utils/hub-channel.js                    |  5 +-
 src/utils/identity.js                       |  4 +-
 8 files changed, 55 insertions(+), 93 deletions(-)

diff --git a/src/assets/stylesheets/profile.scss b/src/assets/stylesheets/profile.scss
index d392c99ab..479207b3e 100644
--- a/src/assets/stylesheets/profile.scss
+++ b/src/assets/stylesheets/profile.scss
@@ -64,31 +64,6 @@
     margin: 0.5em 0;
   }
 
-  &__terms {
-    margin-bottom: 16px;
-
-    &__checkbox {
-      @extend %checkbox;
-      vertical-align: unset;
-    }
-    &__checkbox:checked {
-      @extend %checkbox-checked;
-    }
-
-    &__text {
-      display: inline-block;
-      max-width: 20em;
-    }
-
-    &__link {
-      color: white;
-    }
-
-    &__link:visited {
-      color: grey;
-    }
-  }
-
   &__form-submit {
     @extend %bottom-button;
     margin: 0;
diff --git a/src/assets/translations.data.json b/src/assets/translations.data.json
index 5a1ad32fa..c5274d398 100644
--- a/src/assets/translations.data.json
+++ b/src/assets/translations.data.json
@@ -15,11 +15,6 @@
     "profile.save": "SAVE",
     "profile.display_name.validation_warning": "Alphanumerics and hyphens. At least 3 characters, no more than 32",
     "profile.header": "Your display name:",
-    "profile.terms.prefix": "I confirm that I am over the age of 13 and agree to the",
-    "profile.terms.privacy": "privacy policy",
-    "profile.terms.conjunction": "and",
-    "profile.terms.tou": "terms of use",
-    "profile.terms.suffix": ".",
     "profile.avatar-selector.loading": "Loading Avatars...",
     "audio.title": "Test your audio",
     "audio.subtitle-desktop": "Confirm HMD speaker output",
diff --git a/src/hub.js b/src/hub.js
index 3cd164eb0..5a5dfc3d3 100644
--- a/src/hub.js
+++ b/src/hub.js
@@ -116,11 +116,11 @@ const concurrentLoadDetector = new ConcurrentLoadDetector();
 concurrentLoadDetector.start();
 
 // Always layer in any new default profile bits
-store.update({ profile: { ...generateDefaultProfile(), ...(store.state.profile || {}) } });
+store.update({ activity: {}, settings: {}, profile: { ...generateDefaultProfile(), ...(store.state.profile || {}) } });
 
 // Regenerate name to encourage users to change it.
-if (!store.state.profile.has_changed_name) {
-  store.update({ profile: { display_name: generateRandomName() } });
+if (!store.state.activity.hasChangedName) {
+  store.update({ profile: { displayName: generateRandomName() } });
 }
 
 function mountUI(scene, props = {}) {
@@ -128,7 +128,7 @@ function mountUI(scene, props = {}) {
   const forcedVREntryType = qs.vr_entry_type || null;
   const enableScreenSharing = qsTruthy("enable_screen_sharing");
   const htmlPrefix = document.body.dataset.htmlPrefix || "";
-  const showProfileEntry = !store.state.profile.has_changed_name;
+  const showProfileEntry = !store.state.activity.hasChangedName;
 
   ReactDOM.render(
     <UIRoot
@@ -167,10 +167,10 @@ const onReady = async () => {
   };
 
   const applyProfileFromStore = playerRig => {
-    const displayName = store.state.profile.display_name;
+    const displayName = store.state.profile.displayName;
     playerRig.setAttribute("player-info", {
       displayName,
-      avatarSrc: "#" + (store.state.profile.avatar_id || "botdefault")
+      avatarSrc: "#" + (store.state.profile.avatarId || "botdefault")
     });
     document.querySelector("a-scene").emit("username-changed", { username: displayName });
   };
@@ -244,7 +244,7 @@ const onReady = async () => {
     if (!qsTruthy("offline")) {
       document.body.addEventListener("connected", () => {
         hubChannel.sendEntryEvent().then(() => {
-          store.update({ lastEnteredAt: moment().toJSON() });
+          store.update({ activity: { lastEnteredAt: moment().toJSON() } });
         });
         remountUI({ occupantCount: NAF.connection.adapter.publisher.initialOccupants.length + 1 });
       });
diff --git a/src/react-components/profile-entry-panel.js b/src/react-components/profile-entry-panel.js
index d38ac55e3..5414b0229 100644
--- a/src/react-components/profile-entry-panel.js
+++ b/src/react-components/profile-entry-panel.js
@@ -14,26 +14,28 @@ class ProfileEntryPanel extends Component {
 
   constructor(props) {
     super(props);
-    const { display_name, avatar_id } = this.props.store.state.profile;
-    this.state = { display_name, avatar_id };
+    const { displayName, avatarId } = this.props.store.state.profile;
+    this.state = { displayName, avatarId };
     this.props.store.addEventListener("statechanged", this.storeUpdated);
   }
 
   storeUpdated = () => {
-    const { avatar_id, display_name } = this.props.store.state.profile;
-    this.setState({ avatar_id, display_name });
+    const { avatarId, displayName } = this.props.store.state.profile;
+    this.setState({ avatarId, displayName });
   };
 
   saveStateAndFinish = e => {
     e.preventDefault();
-    const has_agreed_to_terms = this.props.store.state.profile.has_agreed_to_terms || this.state.has_agreed_to_terms;
-    if (!has_agreed_to_terms) return;
-    const { has_changed_name, display_name } = this.props.store.state.profile;
-    const hasChangedName = has_changed_name || this.state.display_name !== display_name;
+
+    const { displayName } = this.props.store.state.profile;
+    const { hasChangedName } = this.props.store.state.activity;
+
+    const hasChangedNowOrPreviously = hasChangedName || this.state.displayName !== displayName;
     this.props.store.update({
+      activity: {
+        hasChangedName: hasChangedNowOrPreviously
+      },
       profile: {
-        has_agreed_to_terms: true,
-        has_changed_name: hasChangedName,
         ...this.state
       }
     });
@@ -48,7 +50,7 @@ class ProfileEntryPanel extends Component {
     if (e.source !== this.avatarSelector.contentWindow) {
       return;
     }
-    this.setState({ avatar_id: e.data.avatarId });
+    this.setState({ avatarId: e.data.avatarId });
   };
 
   componentDidMount() {
@@ -81,41 +83,19 @@ class ProfileEntryPanel extends Component {
               <input
                 id="profile-entry-display-name"
                 className="profile-entry__form-field-text"
-                value={this.state.display_name}
-                onChange={e => this.setState({ display_name: e.target.value })}
+                value={this.state.displayName}
+                onChange={e => this.setState({ displayName: e.target.value })}
                 required
-                pattern={SCHEMA.definitions.profile.properties.display_name.pattern}
+                pattern={SCHEMA.definitions.profile.properties.displayName.pattern}
                 title={formatMessage({ id: "profile.display_name.validation_warning" })}
                 ref={inp => (this.nameInput = inp)}
               />
             </label>
             <iframe
               className="profile-entry__avatar-selector"
-              src={`/${this.props.htmlPrefix}avatar-selector.html#avatar_id=${this.state.avatar_id}`}
+              src={`/${this.props.htmlPrefix}avatar-selector.html#avatar_id=${this.state.avatarId}`}
               ref={ifr => (this.avatarSelector = ifr)}
             />
-            {!this.props.store.state.profile.has_agreed_to_terms && (
-              <label className="profile-entry__terms">
-                <input
-                  className="profile-entry__terms__checkbox"
-                  type="checkbox"
-                  required
-                  value={this.state.has_agreed_to_terms}
-                  onChange={e => this.setState({ has_agreed_to_terms: e.target.checked })}
-                />
-                <span className="profile-entry__terms__text">
-                  <FormattedMessage id="profile.terms.prefix" />{" "}
-                  <a className="profile-entry__terms__link" target="_blank" href="/privacy">
-                    <FormattedMessage id="profile.terms.privacy" />
-                  </a>{" "}
-                  <FormattedMessage id="profile.terms.conjunction" />{" "}
-                  <a className="profile-entry__terms__link" target="_blank" href="/terms">
-                    <FormattedMessage id="profile.terms.tou" />
-                  </a>
-                  <FormattedMessage id="profile.terms.suffix" />
-                </span>
-              </label>
-            )}
             <input className="profile-entry__form-submit" type="submit" value={formatMessage({ id: "profile.save" })} />
           </div>
         </form>
diff --git a/src/react-components/ui-root.js b/src/react-components/ui-root.js
index 16b0b8944..6f2f5cbcb 100644
--- a/src/react-components/ui-root.js
+++ b/src/react-components/ui-root.js
@@ -315,7 +315,7 @@ class UIRoot extends Component {
 
   setMediaStreamToDefault = async () => {
     let hasAudio = false;
-    const { lastUsedMicDeviceId } = this.props.store.state;
+    const { lastUsedMicDeviceId } = this.props.store.state.settings;
 
     // Try to fetch last used mic, if there was one.
     if (lastUsedMicDeviceId) {
@@ -410,7 +410,7 @@ class UIRoot extends Component {
       const micDeviceId = this.micDeviceIdForMicLabel(this.micLabelForMediaStream(mediaStream));
 
       if (micDeviceId) {
-        this.props.store.update({ lastUsedMicDeviceId: micDeviceId });
+        this.props.store.update({ settings: { lastUsedMicDeviceId: micDeviceId } });
       }
 
       this.setState({ micLevelAudioContext, micUpdateInterval });
@@ -752,7 +752,7 @@ class UIRoot extends Component {
     ) : (
       <div className="entry-dialog">
         <ProfileInfoHeader
-          name={this.props.store.state.profile.display_name}
+          name={this.props.store.state.profile.displayName}
           onClick={() => this.setState({ showProfileEntry: true })}
         />
         {entryPanel}
diff --git a/src/storage/store.js b/src/storage/store.js
index 4351ebeda..22828ba9e 100644
--- a/src/storage/store.js
+++ b/src/storage/store.js
@@ -1,8 +1,7 @@
-import uuid from "uuid/v4";
 import { Validator } from "jsonschema";
 import { merge } from "lodash";
 
-const LOCAL_STORE_KEY = "___mozilla_duck";
+const LOCAL_STORE_KEY = "___mozilla_hubs";
 const STORE_STATE_CACHE_KEY = Symbol();
 const validator = new Validator();
 import { EventTarget } from "event-target-shim";
@@ -10,17 +9,32 @@ import { EventTarget } from "event-target-shim";
 // Durable (via local-storage) schema-enforced state that is meant to be consumed via forward data flow.
 // (Think flux but with way less incidental complexity, at least for now :))
 export const SCHEMA = {
-  id: "/MozillaDuckStore",
+  id: "/MozillaHubsStore",
 
   definitions: {
     profile: {
       type: "object",
       additionalProperties: false,
       properties: {
-        has_agreed_to_terms: { type: "boolean" },
-        has_changed_name: { type: "boolean" },
-        display_name: { type: "string", pattern: "^[A-Za-z0-9-]{3,32}$" },
-        avatar_id: { type: "string" }
+        displayName: { type: "string", pattern: "^[A-Za-z0-9-]{3,32}$" },
+        avatarId: { type: "string" }
+      }
+    },
+
+    activity: {
+      type: "object",
+      additionalProperties: false,
+      properties: {
+        hasChangedName: { type: "boolean" },
+        lastEnteredAt: { type: "string" }
+      }
+    },
+
+    settings: {
+      type: "object",
+      additionalProperties: false,
+      properties: {
+        lastUsedMicDeviceId: { type: "string" }
       }
     }
   },
@@ -28,10 +42,9 @@ export const SCHEMA = {
   type: "object",
 
   properties: {
-    id: { type: "string", pattern: "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$" },
     profile: { $ref: "#/definitions/profile" },
-    lastUsedMicDeviceId: { type: "string" },
-    lastEnteredAt: { type: "string" }
+    activity: { $ref: "#/definitions/activity" },
+    settings: { $ref: "#/definitions/settings" }
   },
 
   additionalProperties: false
@@ -42,7 +55,7 @@ export default class Store extends EventTarget {
     super();
 
     if (localStorage.getItem(LOCAL_STORE_KEY) === null) {
-      localStorage.setItem(LOCAL_STORE_KEY, JSON.stringify({ id: uuid() }));
+      localStorage.setItem(LOCAL_STORE_KEY, JSON.stringify({}));
     }
   }
 
diff --git a/src/utils/hub-channel.js b/src/utils/hub-channel.js
index 13e51b21b..aad95ec4e 100644
--- a/src/utils/hub-channel.js
+++ b/src/utils/hub-channel.js
@@ -46,12 +46,13 @@ export default class HubChannel {
 
   getEntryTimingFlags = () => {
     const entryTimingFlags = { isNewDaily: true, isNewMonthly: true, isNewDayWindow: true, isNewMonthWindow: true };
+    const storedLastEnteredAt = this.store.state.activity.lastEnteredAt;
 
-    if (!this.store.state.lastEnteredAt) {
+    if (!storedLastEnteredAt) {
       return entryTimingFlags;
     }
 
-    const lastEntered = moment(this.store.state.lastEnteredAt);
+    const lastEntered = moment(storedLastEnteredAt);
     const lastEnteredPst = moment(lastEntered).tz("America/Los_Angeles");
     const nowPst = moment().tz("America/Los_Angeles");
     const dayWindowAgo = moment().subtract(1, "day");
diff --git a/src/utils/identity.js b/src/utils/identity.js
index db78b027e..2aaabfd89 100644
--- a/src/utils/identity.js
+++ b/src/utils/identity.js
@@ -101,8 +101,6 @@ export const avatarIds = avatars.map(av => av.id);
 
 export function generateDefaultProfile() {
   return {
-    has_agreed_to_terms: false,
-    has_changed_name: false,
-    avatar_id: selectRandom(avatarIds)
+    avatarId: selectRandom(avatarIds)
   };
 }
-- 
GitLab