From 55225fd53e2bf745ddc0e7288261e01bfdc7ff7f Mon Sep 17 00:00:00 2001 From: William Chargin Date: Mon, 19 Mar 2018 17:33:27 -0700 Subject: [PATCH] Save graph fetcher credentials in local storage Test Plan: Make a request, then refresh, and note that the fields are populated. Paired with @dandelionmane. wchargin-branch: graph-fetcher-localstore --- .../artifact/editor/GitHubGraphFetcher.js | 7 +- src/plugins/artifact/editor/LocalStore.js | 83 +++++++++++++++++++ 2 files changed, 89 insertions(+), 1 deletion(-) create mode 100644 src/plugins/artifact/editor/LocalStore.js diff --git a/src/plugins/artifact/editor/GitHubGraphFetcher.js b/src/plugins/artifact/editor/GitHubGraphFetcher.js index e4ef879..d4f7e6f 100644 --- a/src/plugins/artifact/editor/GitHubGraphFetcher.js +++ b/src/plugins/artifact/editor/GitHubGraphFetcher.js @@ -3,6 +3,7 @@ import React from "react"; import type {Graph} from "../../../core/graph"; +import LocalStore from "./LocalStore"; import fetchGitHubRepo from "../../github/fetchGitHubRepo"; import type { NodePayload as GitHubNodePayload, @@ -19,14 +20,17 @@ type State = { repoName: string, }; +const SETTINGS_KEY = "GitHubGraphFetcher.settings"; + export class GitHubGraphFetcher extends React.Component { constructor() { super(); - this.state = { + const defaultState = { apiToken: "", repoOwner: "", repoName: "", }; + this.state = LocalStore.get(SETTINGS_KEY, defaultState); } render() { @@ -78,6 +82,7 @@ export class GitHubGraphFetcher extends React.Component { fetchGraph() { const {repoOwner, repoName, apiToken} = this.state; + LocalStore.set(SETTINGS_KEY, {apiToken, repoOwner, repoName}); fetchGitHubRepo(repoOwner, repoName, apiToken) .then((json) => { const parser = new GithubParser(`${repoOwner}/${repoName}`); diff --git a/src/plugins/artifact/editor/LocalStore.js b/src/plugins/artifact/editor/LocalStore.js new file mode 100644 index 0000000..29904c9 --- /dev/null +++ b/src/plugins/artifact/editor/LocalStore.js @@ -0,0 +1,83 @@ +// @flow + +/* + * A simple abstraction over 'localStorage' to provide transparent JSON + * serialization and deserialization. + * + * The implementation is borrowed heavily from Khan Academy's LocalStore + * module, and also KaVideoPlayer's SafeLocalStore module. + */ +export default { + // Bump this to expire all old values. + version: 1, + keyPrefix: "artifact-editor", + + cacheKey(key: string): string { + if (!key) { + throw new Error("Falsy key provided to cacheKey: " + key); + } + return [this.keyPrefix, this.version, key].join(":"); + }, + + get(key: string, whenUnavailable: any): any { + if (!this.isEnabled()) { + return whenUnavailable; + } + try { + const data = window.localStorage[this.cacheKey(key)]; + if (data) { + return JSON.parse(data); + } else { + return whenUnavailable; + } + } catch (e) { + // If we had trouble retrieving, like FF's NS_FILE_CORRUPTED: + // http://stackoverflow.com/q/18877643/ + return whenUnavailable; + } + }, + + set(key: string, data: any): void { + if (!this.isEnabled()) { + return; + } + const stringified = JSON.stringify(data); + + try { + window.localStorage[this.cacheKey(key)] = stringified; + } catch (e) { + // Probably went over the storage limit... that's not good. + throw e; + } + }, + + /* + * Delete whatever data was associated with the given key. + */ + del(key: string): void { + if (!this.isEnabled()) { + return; + } + const cacheKey = this.cacheKey(key); + if (cacheKey in window.localStorage) { + // (IE throws when deleting a non-existent entry.) + delete window.localStorage[cacheKey]; + } + }, + + /* + * Local storage might be disabled in old browsers or in Safari's + * private browsing mode. Don't die. + */ + isEnabled(): boolean { + const uid = String(+new Date()); + try { + window.sessionStorage[uid] = uid; + const enabled = window.sessionStorage[uid] === uid; + window.sessionStorage.removeItem(uid); + return enabled; + } catch (e) { + return false; + } + }, +};