Stream should work outside of iframe for debugging

This commit is contained in:
Chi Vinh Le
2018-09-06 00:06:58 +02:00
parent 28c8252b63
commit 441cc9d57b
17 changed files with 138 additions and 81 deletions
@@ -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;
@@ -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<TalkContext> {
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(),
};
@@ -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", () => {
@@ -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) {
@@ -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);
}
@@ -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();
});
@@ -0,0 +1,41 @@
export interface PromisifiedStorage {
/**
* value = storage[key]
*/
getItem(key: string): Promise<string | null>;
/**
* delete storage[key]
*/
removeItem(key: string): Promise<void>;
/**
* storage[key] = value
*/
setItem(key: string, value: string): Promise<void>;
}
/**
* 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);
}
@@ -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<string | null>;
/**
* delete storage[key]
*/
removeItem(key: string): Promise<void>;
/**
* storage[key] = value
*/
setItem(key: string, value: string): Promise<void>;
}
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,
@@ -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);
}
@@ -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";
@@ -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();
});
@@ -13,15 +13,16 @@ export type SetAuthTokenMutation = (input: SetAuthTokenInput) => Promise<void>;
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");
@@ -1,21 +1,8 @@
import { PymStorage } from "talk-framework/lib/storage";
export class FakeStorage implements PymStorage {
public store: Record<string, string> = {};
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());
}
@@ -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 {
@@ -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;
@@ -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<TalkContext> = {
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<TalkContext> = {
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<TalkContext> = {
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<TalkContext> = {
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");
});
@@ -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.