From 441cc9d57b7a1e5b26f1c8f89589140cb4456265 Mon Sep 17 00:00:00 2001 From: Chi Vinh Le Date: Wed, 5 Sep 2018 22:00:38 +0200 Subject: [PATCH] Stream should work outside of iframe for debugging --- .../framework/lib/bootstrap/TalkContext.tsx | 6 +-- .../framework/lib/bootstrap/createContext.tsx | 18 +++++++- .../lib/storage/InMemoryStorage.spec.ts | 2 +- .../framework/lib/storage/InMemoryStorage.ts | 2 +- .../framework/lib/storage/LocalStorage.ts | 4 +- .../lib/storage/PromisifiedStorage.spec.ts | 10 +++++ .../lib/storage/PromisifiedStorage.ts | 41 +++++++++++++++++++ .../framework/lib/storage/PymStorage.ts | 17 +------- .../framework/lib/storage/SessionStorage.ts | 4 +- .../client/framework/lib/storage/index.ts | 6 ++- .../mutations/SetAuthTokenMutation.spec.ts | 31 ++++++++------ .../mutations/SetAuthTokenMutation.ts | 7 ++-- .../testHelpers/createFakePymStorage.ts | 23 +++-------- .../containers/PostCommentFormContainer.tsx | 4 +- .../containers/ReplyCommentFormContainer.tsx | 4 +- .../stream/local/initLocalState.spec.ts | 36 +++++++++------- .../client/stream/local/initLocalState.ts | 4 +- 17 files changed, 138 insertions(+), 81 deletions(-) create mode 100644 src/core/client/framework/lib/storage/PromisifiedStorage.spec.ts create mode 100644 src/core/client/framework/lib/storage/PromisifiedStorage.ts diff --git a/src/core/client/framework/lib/bootstrap/TalkContext.tsx b/src/core/client/framework/lib/bootstrap/TalkContext.tsx index 7119940ba..bcfb7b365 100644 --- a/src/core/client/framework/lib/bootstrap/TalkContext.tsx +++ b/src/core/client/framework/lib/bootstrap/TalkContext.tsx @@ -9,7 +9,7 @@ import { Environment } from "relay-runtime"; import { BrowserInfo } from "talk-framework/lib/browserInfo"; import { PostMessageService } from "talk-framework/lib/postMessage"; import { RestClient } from "talk-framework/lib/rest"; -import { PymStorage } from "talk-framework/lib/storage"; +import { PromisifiedStorage } from "talk-framework/lib/storage"; import { UIContext } from "talk-ui/components"; import { ClickFarAwayRegister } from "talk-ui/components/ClickOutside"; @@ -30,10 +30,10 @@ export interface TalkContext { sessionStorage: Storage; /** Session Storage over pym */ - pymLocalStorage?: PymStorage; + pymLocalStorage?: PromisifiedStorage; /** Session storage over pym */ - pymSessionStorage?: PymStorage; + pymSessionStorage?: PromisifiedStorage; /** media query values for testing purposes */ mediaQueryValues?: MediaQueryMatchers; diff --git a/src/core/client/framework/lib/bootstrap/createContext.tsx b/src/core/client/framework/lib/bootstrap/createContext.tsx index a36201e12..14967ddcc 100644 --- a/src/core/client/framework/lib/bootstrap/createContext.tsx +++ b/src/core/client/framework/lib/bootstrap/createContext.tsx @@ -9,6 +9,7 @@ import { getBrowserInfo } from "talk-framework/lib/browserInfo"; import { LOCAL_ID } from "talk-framework/lib/relay"; import { createLocalStorage, + createPromisifiedStorage, createPymStorage, createSessionStorage, } from "talk-framework/lib/storage"; @@ -57,6 +58,14 @@ export const timeagoFormatter: Formatter = (value, unit, suffix) => { ); }; +function areWeInIframe() { + try { + return window.self !== window.top; + } catch (e) { + return true; + } +} + /** * `createContext` manages the dependencies of our framework * and returns a `TalkContext` that can be passed to the @@ -69,6 +78,7 @@ export default async function createContext({ pym, eventEmitter = new EventEmitter2({ wildcard: true }), }: CreateContextArguments): Promise { + const inIframe = areWeInIframe(); // Initialize Relay. const source = new RecordSource(); const tokenGetter: TokenGetter = () => { @@ -120,8 +130,12 @@ export default async function createContext({ postMessage: new PostMessageService(), localStorage: createLocalStorage(), sessionStorage: createSessionStorage(), - pymLocalStorage: pym && createPymStorage(pym, "localStorage"), - pymSessionStorage: pym && createPymStorage(pym, "sessionStorage"), + pymLocalStorage: + (pym && (inIframe && createPymStorage(pym, "localStorage"))) || + createPromisifiedStorage(createLocalStorage("talkPym")), + pymSessionStorage: + (pym && (inIframe && createPymStorage(pym, "sessionStorage"))) || + createPromisifiedStorage(createSessionStorage("talkPym")), browserInfo: getBrowserInfo(), }; diff --git a/src/core/client/framework/lib/storage/InMemoryStorage.spec.ts b/src/core/client/framework/lib/storage/InMemoryStorage.spec.ts index 8c141ed61..78fd8a44c 100644 --- a/src/core/client/framework/lib/storage/InMemoryStorage.spec.ts +++ b/src/core/client/framework/lib/storage/InMemoryStorage.spec.ts @@ -5,7 +5,7 @@ it("should set and unset values", () => { storage.setItem("test", "value"); expect(storage.getItem("test")).toBe("value"); storage.removeItem("test"); - expect(storage.getItem("test")).toBeUndefined(); + expect(storage.getItem("test")).toBeNull(); }); it("should return length", () => { diff --git a/src/core/client/framework/lib/storage/InMemoryStorage.ts b/src/core/client/framework/lib/storage/InMemoryStorage.ts index ec4838b20..c6e0e876a 100644 --- a/src/core/client/framework/lib/storage/InMemoryStorage.ts +++ b/src/core/client/framework/lib/storage/InMemoryStorage.ts @@ -28,7 +28,7 @@ class InMemoryStorage implements Storage { } public getItem(key: string) { - return this.storage[key]; + return this.storage[key] || null; } public setItem(key: string, value: string) { diff --git a/src/core/client/framework/lib/storage/LocalStorage.ts b/src/core/client/framework/lib/storage/LocalStorage.ts index 72588d880..639acfc3f 100644 --- a/src/core/client/framework/lib/storage/LocalStorage.ts +++ b/src/core/client/framework/lib/storage/LocalStorage.ts @@ -1,5 +1,5 @@ import prefixStorage from "./prefixStorage"; -export default function createLocalStorage(): Storage { - return prefixStorage(window.localStorage, "talk"); +export default function createLocalStorage(prefix = "talk"): Storage { + return prefixStorage(window.localStorage, prefix); } diff --git a/src/core/client/framework/lib/storage/PromisifiedStorage.spec.ts b/src/core/client/framework/lib/storage/PromisifiedStorage.spec.ts new file mode 100644 index 000000000..eaba3f8c0 --- /dev/null +++ b/src/core/client/framework/lib/storage/PromisifiedStorage.spec.ts @@ -0,0 +1,10 @@ +import createInMemoryStorage from "./InMemoryStorage"; +import createPromisifiedStorage from "./PromisifiedStorage"; + +it("should set and unset values", () => { + const storage = createPromisifiedStorage(createInMemoryStorage()); + expect(storage.setItem("test", "value")).resolves.toBeUndefined(); + expect(storage.getItem("test")).resolves.toBe("value"); + storage.removeItem("test"); + expect(storage.getItem("test")).resolves.toBeUndefined(); +}); diff --git a/src/core/client/framework/lib/storage/PromisifiedStorage.ts b/src/core/client/framework/lib/storage/PromisifiedStorage.ts new file mode 100644 index 000000000..b6e377214 --- /dev/null +++ b/src/core/client/framework/lib/storage/PromisifiedStorage.ts @@ -0,0 +1,41 @@ +export interface PromisifiedStorage { + /** + * value = storage[key] + */ + getItem(key: string): Promise; + /** + * delete storage[key] + */ + removeItem(key: string): Promise; + /** + * storage[key] = value + */ + setItem(key: string, value: string): Promise; +} + +/** + * BackedPromisifedStorage. + */ +class BackedPromisifedStorage implements PromisifiedStorage { + private storage: Storage; + + constructor(storage: Storage) { + this.storage = storage; + } + + public getItem(key: string) { + return Promise.resolve(this.storage.getItem(key)); + } + + public setItem(key: string, value: string) { + return Promise.resolve(this.storage.setItem(key, value)); + } + + public removeItem(key: string) { + return Promise.resolve(this.storage.removeItem(key)); + } +} + +export default function createPromisifiedStorage(storage: Storage) { + return new BackedPromisifedStorage(storage); +} diff --git a/src/core/client/framework/lib/storage/PymStorage.ts b/src/core/client/framework/lib/storage/PymStorage.ts index ba53ec827..67b602854 100644 --- a/src/core/client/framework/lib/storage/PymStorage.ts +++ b/src/core/client/framework/lib/storage/PymStorage.ts @@ -1,20 +1,7 @@ import { Child, Parent } from "pym.js"; import uuid from "uuid/v4"; -export interface PymStorage { - /** - * value = storage[key] - */ - getItem(key: string): Promise; - /** - * delete storage[key] - */ - removeItem(key: string): Promise; - /** - * storage[key] = value - */ - setItem(key: string, value: string): Promise; -} +import { PromisifiedStorage } from "./PromisifiedStorage"; type Pym = Child | Parent; @@ -27,7 +14,7 @@ type Pym = Child | Parent; export default function createPymStorage( pym: Pym, type: "localStorage" | "sessionStorage" -): PymStorage { +): PromisifiedStorage { // A Map of requestID => {resolve, reject} const requests: Record< string, diff --git a/src/core/client/framework/lib/storage/SessionStorage.ts b/src/core/client/framework/lib/storage/SessionStorage.ts index 7b45e09a3..3675ccf5c 100644 --- a/src/core/client/framework/lib/storage/SessionStorage.ts +++ b/src/core/client/framework/lib/storage/SessionStorage.ts @@ -1,5 +1,5 @@ import prefixStorage from "./prefixStorage"; -export default function createSessionStorage(): Storage { - return prefixStorage(window.sessionStorage, "talk"); +export default function createSessionStorage(prefix = "talk"): Storage { + return prefixStorage(window.sessionStorage, prefix); } diff --git a/src/core/client/framework/lib/storage/index.ts b/src/core/client/framework/lib/storage/index.ts index e17d10668..ca667465b 100644 --- a/src/core/client/framework/lib/storage/index.ts +++ b/src/core/client/framework/lib/storage/index.ts @@ -1,4 +1,8 @@ export { default as createInMemoryStorage } from "./InMemoryStorage"; export { default as createLocalStorage } from "./LocalStorage"; export { default as createSessionStorage } from "./SessionStorage"; -export { default as createPymStorage, PymStorage } from "./PymStorage"; +export { default as createPymStorage } from "./PymStorage"; +export { + default as createPromisifiedStorage, + PromisifiedStorage, +} from "./PromisifiedStorage"; diff --git a/src/core/client/framework/mutations/SetAuthTokenMutation.spec.ts b/src/core/client/framework/mutations/SetAuthTokenMutation.spec.ts index 587ddc2d5..977dbb182 100644 --- a/src/core/client/framework/mutations/SetAuthTokenMutation.spec.ts +++ b/src/core/client/framework/mutations/SetAuthTokenMutation.spec.ts @@ -1,6 +1,5 @@ -import { commitLocalUpdate, Environment, RecordSource } from "relay-runtime"; +import { Environment, RecordSource } from "relay-runtime"; -import { timeout } from "talk-common/utils"; import { LOCAL_ID } from "talk-framework/lib/relay"; import { createInMemoryStorage } from "talk-framework/lib/storage"; import { createRelayEnvironment } from "talk-framework/testHelpers"; @@ -16,7 +15,7 @@ beforeAll(() => { }); }); -it("Sets auth token", async () => { +it("Sets auth token to localStorage", () => { const context = { localStorage: createInMemoryStorage(), }; @@ -26,26 +25,32 @@ it("Sets auth token", async () => { expect(context.localStorage.getItem("authToken")).toEqual(authToken); }); -it("Removes auth token from localStorage", async () => { +it("Removes auth token from localStorage", () => { const context = { localStorage: createInMemoryStorage(), }; localStorage.setItem("authToken", "tmp"); commit(environment, { authToken: null }, context as any); - expect(context.localStorage.getItem("authToken")).toBeUndefined(); + expect(context.localStorage.getItem("authToken")).toBeNull(); }); -it("Should call gc", async () => { +it("Sets auth token to pymLocalStorage", async () => { const context = { + pymLocalStorage: createInMemoryStorage(), localStorage: createInMemoryStorage(), }; - commitLocalUpdate(environment, store => { - store.create("should-disappear", "tmp"); - }); - const authToken = null; - expect(source.get("should-disappear")).not.toBeUndefined(); + const authToken = "auth token"; commit(environment, { authToken }, context as any); - await timeout(); expect(source.get(LOCAL_ID)!.authToken).toEqual(authToken); - expect(source.get("should-disappear")).toBeUndefined(); + expect(await context.pymLocalStorage.getItem("authToken")).toEqual(authToken); +}); + +it("Removes auth token from pymLocalStorage", async () => { + const context = { + pymLocalStorage: createInMemoryStorage(), + localStorage: createInMemoryStorage(), + }; + localStorage.setItem("authToken", "tmp"); + commit(environment, { authToken: null }, context as any); + expect(await context.pymLocalStorage.getItem("authToken")).toBeNull(); }); diff --git a/src/core/client/framework/mutations/SetAuthTokenMutation.ts b/src/core/client/framework/mutations/SetAuthTokenMutation.ts index 90e520155..f74c0a276 100644 --- a/src/core/client/framework/mutations/SetAuthTokenMutation.ts +++ b/src/core/client/framework/mutations/SetAuthTokenMutation.ts @@ -13,15 +13,16 @@ export type SetAuthTokenMutation = (input: SetAuthTokenInput) => Promise; export async function commit( environment: Environment, input: SetAuthTokenInput, - { localStorage }: TalkContext + { localStorage, pymLocalStorage }: TalkContext ) { return commitLocalUpdate(environment, store => { const record = store.get(LOCAL_ID)!; record.setValue(input.authToken, "authToken"); + const storage = pymLocalStorage || localStorage; if (input.authToken) { - localStorage.setItem("authToken", input.authToken); + storage.setItem("authToken", input.authToken); } else { - localStorage.removeItem("authToken"); + storage.removeItem("authToken"); } // Increment auth revision to indicate a change in auth state. record.setValue(record.getValue("authRevision") + 1, "authRevision"); diff --git a/src/core/client/framework/testHelpers/createFakePymStorage.ts b/src/core/client/framework/testHelpers/createFakePymStorage.ts index f7303411f..e0d9c7800 100644 --- a/src/core/client/framework/testHelpers/createFakePymStorage.ts +++ b/src/core/client/framework/testHelpers/createFakePymStorage.ts @@ -1,21 +1,8 @@ -import { PymStorage } from "talk-framework/lib/storage"; - -export class FakeStorage implements PymStorage { - public store: Record = {}; - - public setItem(key: string, value: string) { - this.store[key] = value; - return Promise.resolve(); - } - public removeItem(key: string) { - delete this.store[key]; - return Promise.resolve(); - } - public getItem(key: string) { - return Promise.resolve(this.store[key] || null); - } -} +import { + createInMemoryStorage, + createPromisifiedStorage, +} from "talk-framework/lib/storage"; export default function createFakePymStorage() { - return new FakeStorage(); + return createPromisifiedStorage(createInMemoryStorage()); } diff --git a/src/core/client/stream/containers/PostCommentFormContainer.tsx b/src/core/client/stream/containers/PostCommentFormContainer.tsx index c862ce8ad..fe8b7d29c 100644 --- a/src/core/client/stream/containers/PostCommentFormContainer.tsx +++ b/src/core/client/stream/containers/PostCommentFormContainer.tsx @@ -2,7 +2,7 @@ import React, { Component } from "react"; import { withContext } from "talk-framework/lib/bootstrap"; import { BadUserInputError } from "talk-framework/lib/errors"; -import { PymStorage } from "talk-framework/lib/storage"; +import { PromisifiedStorage } from "talk-framework/lib/storage"; import { PropTypesOf } from "talk-framework/types"; import PostCommentForm, { @@ -13,7 +13,7 @@ import { CreateCommentMutation, withCreateCommentMutation } from "../mutations"; interface InnerProps { createComment: CreateCommentMutation; assetID: string; - pymSessionStorage: PymStorage; + pymSessionStorage: PromisifiedStorage; } interface State { diff --git a/src/core/client/stream/containers/ReplyCommentFormContainer.tsx b/src/core/client/stream/containers/ReplyCommentFormContainer.tsx index 4bd5f311b..10f7cd6de 100644 --- a/src/core/client/stream/containers/ReplyCommentFormContainer.tsx +++ b/src/core/client/stream/containers/ReplyCommentFormContainer.tsx @@ -5,7 +5,7 @@ import { graphql } from "react-relay"; import { withContext } from "talk-framework/lib/bootstrap"; import { BadUserInputError } from "talk-framework/lib/errors"; import { withFragmentContainer } from "talk-framework/lib/relay"; -import { PymStorage } from "talk-framework/lib/storage"; +import { PromisifiedStorage } from "talk-framework/lib/storage"; import { PropTypesOf } from "talk-framework/types"; import { ReplyCommentFormContainer_asset as AssetData } from "talk-stream/__generated__/ReplyCommentFormContainer_asset.graphql"; import { ReplyCommentFormContainer_comment as CommentData } from "talk-stream/__generated__/ReplyCommentFormContainer_comment.graphql"; @@ -17,7 +17,7 @@ import { CreateCommentMutation, withCreateCommentMutation } from "../mutations"; interface InnerProps { createComment: CreateCommentMutation; - pymSessionStorage: PymStorage; + pymSessionStorage: PromisifiedStorage; comment: CommentData; asset: AssetData; onClose?: () => void; diff --git a/src/core/client/stream/local/initLocalState.spec.ts b/src/core/client/stream/local/initLocalState.spec.ts index 49f090742..29ddcc61b 100644 --- a/src/core/client/stream/local/initLocalState.spec.ts +++ b/src/core/client/stream/local/initLocalState.spec.ts @@ -1,8 +1,12 @@ import { Environment, RecordSource } from "relay-runtime"; import { timeout } from "talk-common/utils"; +import { TalkContext } from "talk-framework/lib/bootstrap"; import { LOCAL_ID } from "talk-framework/lib/relay"; -import { createInMemoryStorage } from "talk-framework/lib/storage"; +import { + createInMemoryStorage, + createPromisifiedStorage, +} from "talk-framework/lib/storage"; import { createRelayEnvironment } from "talk-framework/testHelpers"; import initLocalState from "./initLocalState"; @@ -19,14 +23,18 @@ beforeEach(() => { }); it("init local state", async () => { - await initLocalState(environment, { - localStorage: createInMemoryStorage(), - } as any); + const context: Partial = { + pymLocalStorage: createPromisifiedStorage(createInMemoryStorage()), + }; + await initLocalState(environment, context as any); await timeout(); expect(JSON.stringify(source.toJSON(), null, 2)).toMatchSnapshot(); }); it("set assetID from query", async () => { + const context: Partial = { + pymLocalStorage: createPromisifiedStorage(createInMemoryStorage()), + }; const assetID = "asset-id"; const previousLocation = location.toString(); const previousState = window.history.state; @@ -35,14 +43,15 @@ it("set assetID from query", async () => { document.title, `http://localhost/?assetID=${assetID}` ); - await initLocalState(environment, { - localStorage: createInMemoryStorage(), - } as any); + await initLocalState(environment, context as any); expect(source.get(LOCAL_ID)!.assetID).toBe(assetID); window.history.replaceState(previousState, document.title, previousLocation); }); it("set commentID from query", async () => { + const context: Partial = { + pymLocalStorage: createPromisifiedStorage(createInMemoryStorage()), + }; const commentID = "comment-id"; const previousLocation = location.toString(); const previousState = window.history.state; @@ -51,18 +60,17 @@ it("set commentID from query", async () => { document.title, `http://localhost/?commentID=${commentID}` ); - await initLocalState(environment, { - localStorage: createInMemoryStorage(), - } as any); + await initLocalState(environment, context as any); expect(source.get(LOCAL_ID)!.commentID).toBe(commentID); window.history.replaceState(previousState, document.title, previousLocation); }); it("set authToken from localStorage", async () => { + const context: Partial = { + pymLocalStorage: createPromisifiedStorage(createInMemoryStorage()), + }; const authToken = "auth-token"; - const localStorage = createInMemoryStorage(); - localStorage.setItem("authToken", authToken); - await initLocalState(environment, { localStorage } as any); + context.pymLocalStorage!.setItem("authToken", authToken); + await initLocalState(environment, context as any); expect(source.get(LOCAL_ID)!.authToken).toBe(authToken); - localStorage.removeItem("authToken"); }); diff --git a/src/core/client/stream/local/initLocalState.ts b/src/core/client/stream/local/initLocalState.ts index 81b32a742..4666ab261 100644 --- a/src/core/client/stream/local/initLocalState.ts +++ b/src/core/client/stream/local/initLocalState.ts @@ -20,9 +20,9 @@ import { */ export default async function initLocalState( environment: Environment, - { localStorage }: TalkContext + { pymLocalStorage }: TalkContext ) { - const authToken = await localStorage.getItem("authToken"); + const authToken = await pymLocalStorage!.getItem("authToken"); commitLocalUpdate(environment, s => { // TODO: (cvle) move local, auth token and network initialization to framework.