[next] Start a clean session when user logs in / out (#1853)

* Clear user session after login / logout

* Filename cases

* Improve type checking

* Apply suggestions
This commit is contained in:
Kiwi
2018-09-12 18:04:54 +02:00
committed by Wyatt Johnson
parent 26b59fc17c
commit 8b09b52be8
30 changed files with 485 additions and 331 deletions
+5 -15
View File
@@ -2,11 +2,7 @@ import React from "react";
import { StatelessComponent } from "react";
import ReactDOM from "react-dom";
import {
createContext,
TalkContext,
TalkContextProvider,
} from "talk-framework/lib/bootstrap";
import { createManaged } from "talk-framework/lib/bootstrap";
import AppContainer from "./containers/AppContainer";
import resizePopup from "./dom/resizePopup";
@@ -32,23 +28,17 @@ function pollPopupHeight(interval: number = 100) {
}, interval);
}
// This is called when the context is first initialized.
async function init({ relayEnvironment }: TalkContext) {
await initLocalState(relayEnvironment);
}
async function main() {
// Bootstrap our context.
const context = await createContext({
init,
const ManagedTalkContextProvider = await createManaged({
initLocalState,
localesData,
userLocales: navigator.languages,
});
const Index: StatelessComponent = () => (
<TalkContextProvider value={context}>
<ManagedTalkContextProvider>
<AppContainer />
</TalkContextProvider>
</ManagedTalkContextProvider>
);
ReactDOM.render(<Index />, document.getElementById("app"));
+4 -1
View File
@@ -1,4 +1,6 @@
import { EventEmitter2 } from "eventemitter2";
import { IResolvers } from "graphql-tools";
import { noop } from "lodash";
import React from "react";
import TestRenderer from "react-test-renderer";
import { Environment, RecordProxy, RecordSourceProxy } from "relay-runtime";
@@ -29,7 +31,6 @@ export default function create(params: CreateParams) {
logNetwork: params.logNetwork,
resolvers: params.resolvers,
initLocalState: (localRecord, source, env) => {
localRecord.setValue(0, "authRevision");
if (params.initLocalState) {
params.initLocalState(localRecord, source, env);
}
@@ -45,6 +46,8 @@ export default function create(params: CreateParams) {
postMessage: new PostMessageService(),
browserInfo: { ios: false },
uuidGenerator: createUUIDGenerator(),
eventEmitter: new EventEmitter2({ wildcard: true, maxListeners: 20 }),
clearSession: noop,
};
const testRenderer = TestRenderer.create(
+2 -5
View File
@@ -3,12 +3,9 @@ import { Environment, ROOT_ID } from "relay-runtime";
export default function getMe(environment: Environment) {
const source = environment.getStore().getSource();
const root = source.get(ROOT_ID)!;
const meKey = Object.keys(root)
.reverse()
.find(s => s.startsWith("me("))!;
if (!root[meKey]) {
if (!root.me) {
return null;
}
const meID = root[meKey].__ref;
const meID = root.me.__ref;
return source.get(meID)!;
}
@@ -1,3 +1,4 @@
import { EventEmitter2 } from "eventemitter2";
import { LocalizationProvider } from "fluent-react/compat";
import { FluentBundle } from "fluent/compat";
import { Child as PymChild } from "pym.js";
@@ -52,6 +53,12 @@ export interface TalkContext {
/** Generates uuids. */
uuidGenerator: () => string;
/** A event emitter */
eventEmitter: EventEmitter2;
/** Clear session data. */
clearSession: () => void;
}
const { Provider, Consumer } = React.createContext<TalkContext>({} as any);
@@ -1,147 +0,0 @@
import { EventEmitter2 } from "eventemitter2";
import { Localized } from "fluent-react/compat";
import { noop } from "lodash";
import { Child as PymChild } from "pym.js";
import React from "react";
import { Formatter } from "react-timeago";
import { Environment, Network, RecordSource, Store } from "relay-runtime";
import uuid from "uuid/v4";
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";
import { RestClient } from "talk-framework/lib/rest";
import { ClickFarAwayRegister } from "talk-ui/components/ClickOutside";
import { generateBundles, LocalesData, negotiateLanguages } from "../i18n";
import { createFetch, TokenGetter } from "../network";
import { PostMessageService } from "../postMessage";
import { TalkContext } from "./TalkContext";
interface CreateContextArguments {
/** Locales that the user accepts, usually `navigator.languages`. */
userLocales: ReadonlyArray<string>;
/** Locales data that is returned by our `locales-loader`. */
localesData: LocalesData;
/** Init will be called after the context has been created. */
init?: ((context: TalkContext) => void | Promise<void>);
/** A pym child that interacts with the pym parent. */
pym?: PymChild;
/** Supports emitting and listening to events. */
eventEmitter?: EventEmitter2;
}
/**
* timeagoFormatter integrates timeago into our translation
* framework. It gets injected into the UIContext.
*/
export const timeagoFormatter: Formatter = (value, unit, suffix) => {
// We use 'in' instead of 'from now' for language consistency
const ourSuffix = suffix === "from now" ? "in" : suffix;
return (
<Localized
id="framework-timeago"
$value={value}
$unit={unit}
$suffix={ourSuffix}
>
<span>now</span>
</Localized>
);
};
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
* `TalkContextProvider`.
*/
export default async function createContext({
init = noop,
userLocales,
localesData,
pym,
eventEmitter = new EventEmitter2({ wildcard: true }),
}: CreateContextArguments): Promise<TalkContext> {
const inIframe = areWeInIframe();
// Initialize Relay.
const source = new RecordSource();
const tokenGetter: TokenGetter = () => {
const localState = source.get(LOCAL_ID);
if (localState) {
return localState.authToken || "";
}
return "";
};
const relayEnvironment = new Environment({
network: Network.create(createFetch(tokenGetter)),
store: new Store(source),
});
// Listen for outside clicks.
let registerClickFarAway: ClickFarAwayRegister | undefined;
if (pym) {
registerClickFarAway = cb => {
pym.onMessage("click", cb);
// Return unlisten callback.
return () => {
const index = pym.messageHandlers.click.indexOf(cb);
if (index > -1) {
pym.messageHandlers.click.splice(index, 1);
}
};
};
}
// Initialize i18n.
const locales = negotiateLanguages(userLocales, localesData);
if (process.env.NODE_ENV !== "production") {
// tslint:disable:next-line: no-console
console.log(`Negotiated locales ${JSON.stringify(locales)}`);
}
const localeBundles = await generateBundles(locales, localesData);
// Assemble context.
const context = {
relayEnvironment,
localeBundles,
timeagoFormatter,
pym,
eventEmitter,
registerClickFarAway,
rest: new RestClient("/api", tokenGetter),
postMessage: new PostMessageService(),
localStorage:
(pym && inIframe && createPymStorage(pym, "localStorage")) ||
createPromisifiedStorage(createLocalStorage()),
sessionStorage:
(pym && inIframe && createPymStorage(pym, "sessionStorage")) ||
createPromisifiedStorage(createSessionStorage()),
browserInfo: getBrowserInfo(),
uuidGenerator: uuid,
};
// Run custom initializations.
await init(context);
return context;
}
@@ -0,0 +1,252 @@
import { EventEmitter2 } from "eventemitter2";
import { Localized } from "fluent-react/compat";
import { noop } from "lodash";
import { Child as PymChild } from "pym.js";
import React, { Component, ComponentType } from "react";
import { Formatter } from "react-timeago";
import { Environment, Network, RecordSource, Store } from "relay-runtime";
import uuid from "uuid/v4";
import { getBrowserInfo } from "talk-framework/lib/browserInfo";
import { LOCAL_ID } from "talk-framework/lib/relay";
import {
createLocalStorage,
createPromisifiedStorage,
createPymStorage,
createSessionStorage,
PromisifiedStorage,
} from "talk-framework/lib/storage";
import { RestClient } from "talk-framework/lib/rest";
import { ClickFarAwayRegister } from "talk-ui/components/ClickOutside";
import { generateBundles, LocalesData, negotiateLanguages } from "../i18n";
import { createFetch, TokenGetter } from "../network";
import { PostMessageService } from "../postMessage";
import { TalkContext, TalkContextProvider } from "./TalkContext";
export type InitLocalState = ((
environment: Environment,
context: TalkContext
) => void | Promise<void>);
interface CreateContextArguments {
/** Locales that the user accepts, usually `navigator.languages`. */
userLocales: ReadonlyArray<string>;
/** Locales data that is returned by our `locales-loader`. */
localesData: LocalesData;
/** Init will be called after the context has been created. */
initLocalState?: InitLocalState;
/** A pym child that interacts with the pym parent. */
pym?: PymChild;
/** Supports emitting and listening to events. */
eventEmitter?: EventEmitter2;
}
/**
* timeagoFormatter integrates timeago into our translation
* framework. It gets injected into the UIContext.
*/
export const timeagoFormatter: Formatter = (value, unit, suffix) => {
// We use 'in' instead of 'from now' for language consistency
const ourSuffix = suffix === "from now" ? "in" : suffix;
return (
<Localized
id="framework-timeago"
$value={value}
$unit={unit}
$suffix={ourSuffix}
>
<span>now</span>
</Localized>
);
};
/**
* Returns true if we are in an iframe.
*/
function areWeInIframe() {
try {
return window.self !== window.top;
} catch (e) {
return true;
}
}
function createRelayEnvironment() {
// Initialize Relay.
const source = new RecordSource();
const tokenGetter: TokenGetter = () => {
const localState = source.get(LOCAL_ID);
if (localState) {
return localState.authToken || "";
}
return "";
};
const environment = new Environment({
network: Network.create(createFetch(tokenGetter)),
store: new Store(source),
});
return { environment, tokenGetter };
}
function createRestAPI(tokenGetter: (() => string)) {
return new RestClient("/api", tokenGetter);
}
/**
* Returns a managed TalkContextProvider, that includes given context
* and handles context changes, e.g. when a user session changes.
*/
function createMangedTalkContextProvider(
context: TalkContext,
initLocalState: InitLocalState
) {
const ManagedTalkContextProvider = class extends Component<
{},
{ context: TalkContext }
> {
constructor(props: {}) {
super(props);
this.state = {
context: {
...context,
clearSession: this.clearSession,
},
};
}
// This is called every time a user session starts or ends.
private clearSession = async () => {
// Clear session storage.
this.state.context.sessionStorage.clear();
// Create a new context with a new Relay Environment.
const {
environment: newEnvironment,
tokenGetter: newTokenGetter,
} = createRelayEnvironment();
const newContext = {
...this.state.context,
relayEnvironment: newEnvironment,
rest: createRestAPI(newTokenGetter),
};
// Initialize local state.
await initLocalState(newContext.relayEnvironment, newContext);
// Propagate new context.
this.setState({
context: newContext,
});
};
public render() {
return (
<TalkContextProvider value={this.state.context}>
{this.props.children}
</TalkContextProvider>
);
}
};
return ManagedTalkContextProvider;
}
/**
* resolveLocalStorage decides which local storage to use in the context
*/
function resolveLocalStorage(pym?: PymChild): PromisifiedStorage {
if (pym && areWeInIframe()) {
// Use local storage over pym when we have pym and are in an iframe.
return createPymStorage(pym, "localStorage");
}
// Use promisified, prefixed local storage.
return createPromisifiedStorage(createLocalStorage());
}
/**
* resolveSessionStorage decides which session storage to use in the context
*/
function resolveSessionStorage(pym?: PymChild): PromisifiedStorage {
if (pym && areWeInIframe()) {
// Use session storage over pym when we have pym and are in an iframe.
return createPymStorage(pym, "sessionStorage");
}
// Use promisified, prefixed session storage.
return createPromisifiedStorage(createSessionStorage());
}
/**
* `createManaged` establishes the dependencies of our framework
* and returns a `ManagedTalkContextProvider` that provides the context
* to the rest of the application.
*/
export default async function createManaged({
initLocalState = noop,
userLocales,
localesData,
pym,
eventEmitter = new EventEmitter2({ wildcard: true, maxListeners: 20 }),
}: CreateContextArguments): Promise<ComponentType> {
// Listen for outside clicks.
let registerClickFarAway: ClickFarAwayRegister | undefined;
if (pym) {
registerClickFarAway = cb => {
pym.onMessage("click", cb);
// Return unlisten callback.
return () => {
const index = pym.messageHandlers.click.indexOf(cb);
if (index > -1) {
pym.messageHandlers.click.splice(index, 1);
}
};
};
}
// Initialize i18n.
const locales = negotiateLanguages(userLocales, localesData);
if (process.env.NODE_ENV !== "production") {
// tslint:disable:next-line: no-console
console.log(`Negotiated locales ${JSON.stringify(locales)}`);
}
const localeBundles = await generateBundles(locales, localesData);
const localStorage = resolveLocalStorage(pym);
const sessionStorage = resolveSessionStorage(pym);
const { environment, tokenGetter } = createRelayEnvironment();
// Assemble context.
const context: TalkContext = {
relayEnvironment: environment,
localeBundles,
timeagoFormatter,
pym,
eventEmitter,
registerClickFarAway,
rest: createRestAPI(tokenGetter),
postMessage: new PostMessageService(),
localStorage,
sessionStorage,
browserInfo: getBrowserInfo(),
uuidGenerator: uuid,
// Noop, this is later replaced by the
// managed TalkContextProvider.
clearSession: noop,
};
// Initialize local state.
await initLocalState(context.relayEnvironment, context);
// Returns a managed TalkContextProvider, that includes the above
// context and handles context changes, e.g. when a user session changes.
return createMangedTalkContextProvider(context, initLocalState);
}
@@ -1,3 +1,7 @@
export * from "./TalkContext";
export { default as createContext } from "./createContext";
export {
TalkContext,
TalkContextConsumer,
TalkContextProvider,
} from "./TalkContext";
export { default as createManaged } from "./createManaged";
export { default as withContext } from "./withContext";
@@ -0,0 +1,18 @@
import {
commitLocalUpdate,
Environment,
RecordSourceProxy,
} from "relay-runtime";
export default function commitLocalUpdatePromisified(
environment: Environment,
updater: (store: RecordSourceProxy) => Promise<void>
) {
return new Promise((resolve, reject) => {
commitLocalUpdate(environment, store => {
updater(store)
.then(() => resolve())
.catch(err => reject(err));
});
});
}
@@ -13,3 +13,6 @@ export {
commitMutationPromiseNormalized,
} from "./commitMutationPromise";
export { graphql } from "react-relay";
export {
default as commitLocalUpdatePromisified,
} from "./commitLocalUpdatePromisified";
@@ -1,7 +1,9 @@
import { Environment, RecordSource } from "relay-runtime";
import sinon from "sinon";
import { TalkContext } from "talk-framework/lib/bootstrap";
import { LOCAL_ID } from "talk-framework/lib/relay";
import { createInMemoryStorage } from "talk-framework/lib/storage";
import { createPromisifiedStorage } from "talk-framework/lib/storage";
import { createRelayEnvironment } from "talk-framework/testHelpers";
import { commit } from "./SetAuthTokenMutation";
@@ -15,21 +17,29 @@ beforeAll(() => {
});
});
it("Sets auth token to localStorage", () => {
const context = {
localStorage: createInMemoryStorage(),
it("Sets auth token to localStorage", async () => {
const clearSessionStub = sinon.stub();
const context: Partial<TalkContext> = {
localStorage: createPromisifiedStorage(),
clearSession: clearSessionStub,
};
const authToken = "auth token";
commit(environment, { authToken }, context as any);
await commit(environment, { authToken }, context as any);
expect(source.get(LOCAL_ID)!.authToken).toEqual(authToken);
expect(context.localStorage.getItem("authToken")).toEqual(authToken);
await expect(context.localStorage!.getItem("authToken")).resolves.toEqual(
authToken
);
expect(clearSessionStub.calledOnce).toBe(true);
});
it("Removes auth token from localStorage", () => {
const context = {
localStorage: createInMemoryStorage(),
it("Removes auth token from localStorage", async () => {
const clearSessionStub = sinon.stub();
const context: Partial<TalkContext> = {
localStorage: createPromisifiedStorage(),
clearSession: clearSessionStub,
};
localStorage.setItem("authToken", "tmp");
commit(environment, { authToken: null }, context as any);
expect(context.localStorage.getItem("authToken")).toBeNull();
await commit(environment, { authToken: null }, context as any);
await expect(context.localStorage!.getItem("authToken")).resolves.toBeNull();
expect(clearSessionStub.calledOnce).toBe(true);
});
@@ -1,7 +1,10 @@
import { commitLocalUpdate, Environment } from "relay-runtime";
import { Environment } from "relay-runtime";
import { TalkContext } from "talk-framework/lib/bootstrap";
import { createMutationContainer } from "talk-framework/lib/relay";
import {
commitLocalUpdatePromisified,
createMutationContainer,
} from "talk-framework/lib/relay";
import { LOCAL_ID } from "talk-framework/lib/relay/withLocalStateContainer";
export interface SetAuthTokenInput {
@@ -13,27 +16,18 @@ export type SetAuthTokenMutation = (input: SetAuthTokenInput) => Promise<void>;
export async function commit(
environment: Environment,
input: SetAuthTokenInput,
{ localStorage }: TalkContext
{ localStorage, clearSession }: TalkContext
) {
return commitLocalUpdate(environment, store => {
return await commitLocalUpdatePromisified(environment, async store => {
const record = store.get(LOCAL_ID)!;
record.setValue(input.authToken, "authToken");
if (input.authToken) {
localStorage.setItem("authToken", input.authToken);
await localStorage.setItem("authToken", input.authToken);
} else {
localStorage.removeItem("authToken");
await localStorage.removeItem("authToken");
}
// Increment auth revision to indicate a change in auth state.
record.setValue(record.getValue("authRevision") + 1, "authRevision");
// Force gc to trigger.
environment
.retain({
dataID: "tmp",
node: { selections: [] },
variables: {},
})
.dispose();
// Clear current session, as we are starting a new one.
clearSession();
});
}
+19 -25
View File
@@ -3,46 +3,40 @@ import React from "react";
import { StatelessComponent } from "react";
import ReactDOM from "react-dom";
import {
createContext,
TalkContext,
TalkContextProvider,
} from "talk-framework/lib/bootstrap";
import { createManaged } from "talk-framework/lib/bootstrap";
import AppContainer from "./containers/AppContainer";
import {
onPostMessageAuthError,
onPostMessageSetAuthToken,
onPymSetCommentID,
OnPostMessageAuthError,
OnPostMessageSetAuthToken,
OnPymSetCommentID,
} from "./listeners";
import { initLocalState } from "./local";
import localesData from "./locales";
const listeners = [
onPymSetCommentID,
onPostMessageSetAuthToken,
onPostMessageAuthError,
];
// This is called when the context is first initialized.
async function init(context: TalkContext) {
await initLocalState(context.relayEnvironment, context);
listeners.forEach(f => f(context));
}
const listeners = (
<>
<OnPymSetCommentID />
<OnPostMessageSetAuthToken />
<OnPostMessageAuthError />
</>
);
async function main() {
// Bootstrap our context.
const context = await createContext({
init,
const ManagedTalkContextProvider = await createManaged({
initLocalState,
localesData,
userLocales: navigator.languages,
pym: new PymChild({ polling: 100 }),
});
const Index: StatelessComponent = () => (
<TalkContextProvider value={context}>
<AppContainer />
</TalkContextProvider>
<ManagedTalkContextProvider>
<>
{listeners}
<AppContainer />
</>
</ManagedTalkContextProvider>
);
ReactDOM.render(<Index />, document.getElementById("app"));
@@ -0,0 +1,28 @@
import { Component } from "react";
import { withContext } from "talk-framework/lib/bootstrap";
import { PostMessageService } from "talk-framework/lib/postMessage";
interface Props {
postMessage: PostMessageService;
}
class OnPostMessageAuthError extends Component<Props> {
constructor(props: Props) {
super(props);
// Auth popup will use this to send back errors during login.
props.postMessage!.on("authError", error => {
// tslint:disable-next-line:no-console
console.error(error);
});
}
public render() {
return null;
}
}
const enhanced = withContext(({ postMessage }) => ({ postMessage }))(
OnPostMessageAuthError
);
export default enhanced;
@@ -1,10 +1,14 @@
import { shallow } from "enzyme";
import { noop } from "lodash";
import React from "react";
import { Environment, RecordSource } from "relay-runtime";
import { TalkContext } from "talk-framework/lib/bootstrap";
import { LOCAL_ID } from "talk-framework/lib/relay";
import { createInMemoryStorage } from "talk-framework/lib/storage";
import { createPromisifiedStorage } from "talk-framework/lib/storage";
import { createRelayEnvironment } from "talk-framework/testHelpers";
import onPostMessageSetAuthToken from "./onPostMessageSetAuthToken";
import { OnPostMessageSetAuthToken } from "./OnPostMessageSetAuthToken";
let relayEnvironment: Environment;
const source: RecordSource = new RecordSource();
@@ -17,16 +21,17 @@ beforeAll(() => {
it("Sets auth token", () => {
const token = "auth-token";
const context = {
const context: Partial<TalkContext> = {
postMessage: {
on: (name: string, cb: (token: string) => void) => {
expect(name).toBe("setAuthToken");
cb(token);
},
},
} as any,
relayEnvironment,
localStorage: createInMemoryStorage(),
localStorage: createPromisifiedStorage(),
clearSession: noop,
};
onPostMessageSetAuthToken(context as any);
shallow(<OnPostMessageSetAuthToken context={context as any} />);
expect(source.get(LOCAL_ID)!.authToken).toEqual(token);
});
@@ -0,0 +1,31 @@
import { Component } from "react";
import { TalkContext, withContext } from "talk-framework/lib/bootstrap";
import { commit as setAuthToken } from "talk-framework/mutations/SetAuthTokenMutation";
interface Props {
context: TalkContext;
}
export class OnPostMessageSetAuthToken extends Component<Props> {
constructor(props: Props) {
super(props);
// Auth popup will use this to handle a successful login.
props.context.postMessage!.on("setAuthToken", (authToken: string) => {
setAuthToken(
this.props.context.relayEnvironment,
{ authToken },
this.props.context
);
});
}
public render() {
return null;
}
}
const enhanced = withContext(context => ({ context }))(
OnPostMessageSetAuthToken
);
export default enhanced;
@@ -1,9 +1,11 @@
import { shallow } from "enzyme";
import React from "react";
import { Environment, RecordSource } from "relay-runtime";
import { LOCAL_ID } from "talk-framework/lib/relay";
import { createRelayEnvironment } from "talk-framework/testHelpers";
import onPymSetCommentID from "./onPymSetCommentID";
import { OnPymSetCommentID } from "./OnPymSetCommentID";
let relayEnvironment: Environment;
const source: RecordSource = new RecordSource();
@@ -16,30 +18,30 @@ beforeAll(() => {
it("Sets comment id", () => {
const id = "comment1-id";
const context = {
const props = {
pym: {
onMessage: (eventName: string, cb: (id: string) => void) => {
expect(eventName).toBe("setCommentID");
cb(id);
},
},
} as any,
relayEnvironment,
};
onPymSetCommentID(context as any);
shallow(<OnPymSetCommentID {...props} />);
expect(source.get(LOCAL_ID)!.commentID).toEqual(id);
});
it("Sets comment id to null when empty", () => {
const id = "";
const context = {
const props = {
pym: {
onMessage: (eventName: string, cb: (data: string) => void) => {
expect(eventName).toBe("setCommentID");
cb(id);
},
},
} as any,
relayEnvironment,
};
onPymSetCommentID(context as any);
shallow(<OnPymSetCommentID {...props} />);
expect(source.get(LOCAL_ID)!.commentID).toEqual(null);
});
@@ -0,0 +1,39 @@
import { Child } from "pym.js";
import { Component } from "react";
import { commitLocalUpdate } from "react-relay";
import { Environment } from "relay-runtime";
import { withContext } from "talk-framework/lib/bootstrap";
import { LOCAL_ID } from "talk-framework/lib/relay";
interface Props {
relayEnvironment: Environment;
pym: Child;
}
export class OnPymSetCommentID extends Component<Props> {
constructor(props: Props) {
super(props);
// Sets comment id through pym.
props.pym!.onMessage("setCommentID", raw => {
commitLocalUpdate(this.props.relayEnvironment, s => {
const id = raw || null;
if (s.get(LOCAL_ID)!.getValue("commentID") !== id) {
s.get(LOCAL_ID)!.setValue(id, "commentID");
}
});
});
}
public render() {
return null;
}
}
const enhanced = withContext(({ relayEnvironment, pym }) => ({
relayEnvironment,
pym,
}))(OnPymSetCommentID);
export default enhanced;
+4 -4
View File
@@ -1,5 +1,5 @@
export { default as onPymSetCommentID } from "./onPymSetCommentID";
export { default as OnPymSetCommentID } from "./OnPymSetCommentID";
export {
default as onPostMessageSetAuthToken,
} from "./onPostMessageSetAuthToken";
export { default as onPostMessageAuthError } from "./onPostMessageAuthError";
default as OnPostMessageSetAuthToken,
} from "./OnPostMessageSetAuthToken";
export { default as OnPostMessageAuthError } from "./OnPostMessageAuthError";
@@ -1,11 +0,0 @@
import { TalkContext } from "talk-framework/lib/bootstrap";
export default function onPostMessageSetAuthToken({
postMessage,
}: TalkContext) {
// Auth popup will use this to send back errors during login.
postMessage!.on("authError", error => {
// tslint:disable-next-line:no-console
console.error(error);
});
}
@@ -1,11 +0,0 @@
import { TalkContext } from "talk-framework/lib/bootstrap";
import { commit as setAuthToken } from "talk-framework/mutations/SetAuthTokenMutation";
export default function onPostMessageSetAuthToken(ctx: TalkContext) {
const { relayEnvironment, postMessage } = ctx;
// Auth popup will use this to handle a successful login.
postMessage!.on("setAuthToken", (authToken: string) => {
setAuthToken(relayEnvironment, { authToken }, ctx);
});
}
@@ -1,19 +0,0 @@
import { commitLocalUpdate } from "react-relay";
import { TalkContext } from "talk-framework/lib/bootstrap";
import { LOCAL_ID } from "talk-framework/lib/relay";
export default function onPymSetCommentID({
relayEnvironment,
pym,
}: TalkContext) {
// Sets comment id through pym.
pym!.onMessage("setCommentID", raw => {
commitLocalUpdate(relayEnvironment, s => {
const id = raw || null;
if (s.get(LOCAL_ID)!.getValue("commentID") !== id) {
s.get(LOCAL_ID)!.setValue(id, "commentID");
}
});
});
}
@@ -13,7 +13,6 @@ exports[`init local state 1`] = `
\\"__id\\": \\"client:root.local\\",
\\"__typename\\": \\"Local\\",
\\"authToken\\": \\"\\",
\\"authRevision\\": 0,
\\"network\\": {
\\"__ref\\": \\"client:root.local.network\\"
},
@@ -35,9 +35,6 @@ export default async function initLocalState(
// Set auth token
localRecord.setValue(authToken || "", "authToken");
// Set initial auth revision, this is increment whenenver auth state might have changed.
localRecord.setValue(0, "authRevision");
// Parse query params
const query = qs.parse(location.search);
@@ -26,10 +26,6 @@ type Local {
commentID: String
authPopup: AuthPopup!
authToken: String
# Used to invalidate the `me` endpoint.
# This is incremented whenever the auth status
# might have changed.
authRevision: Int!
}
extend type Query {
@@ -45,19 +45,12 @@ export const render = ({
};
const PermalinkViewQuery: StatelessComponent<InnerProps> = ({
local: { commentID, assetID, authRevision },
local: { commentID, assetID },
}) => (
<QueryRenderer<QueryTypes>
query={graphql`
query PermalinkViewQuery(
$commentID: ID!
$assetID: ID!
$authRevision: Int!
) {
# authRevision is increment every time auth state has changed.
# This is basically a cache invalidation and causes relay
# to automatically update this query.
me(clientAuthRevision: $authRevision) {
query PermalinkViewQuery($commentID: ID!, $assetID: ID!) {
me {
...PermalinkViewContainer_me
}
asset(id: $assetID) {
@@ -71,7 +64,6 @@ const PermalinkViewQuery: StatelessComponent<InnerProps> = ({
variables={{
assetID: assetID!,
commentID: commentID!,
authRevision,
}}
render={render}
/>
@@ -81,7 +73,6 @@ const enhanced = withLocalStateContainer(
graphql`
fragment PermalinkViewQueryLocal on Local {
assetID
authRevision
commentID
}
`
@@ -38,15 +38,12 @@ export const render = ({
};
const StreamQuery: StatelessComponent<InnerProps> = ({
local: { assetID, authRevision },
local: { assetID },
}) => (
<QueryRenderer<QueryTypes>
query={graphql`
query StreamQuery($assetID: ID!, $authRevision: Int!) {
# authRevision is increment every time auth state has changed.
# This is basically a cache invalidation and causes relay
# to automatically update this query.
me(clientAuthRevision: $authRevision) {
query StreamQuery($assetID: ID!) {
me {
...StreamContainer_me
}
asset(id: $assetID) {
@@ -56,7 +53,6 @@ const StreamQuery: StatelessComponent<InnerProps> = ({
`}
variables={{
assetID: assetID!,
authRevision,
}}
render={render}
/>
@@ -66,7 +62,6 @@ const enhanced = withLocalStateContainer(
graphql`
fragment StreamQueryLocal on Local {
assetID
authRevision
}
`
)(StreamQuery);
+4 -1
View File
@@ -1,4 +1,6 @@
import { EventEmitter2 } from "eventemitter2";
import { IResolvers } from "graphql-tools";
import { noop } from "lodash";
import React from "react";
import TestRenderer from "react-test-renderer";
import { Environment, RecordProxy, RecordSourceProxy } from "relay-runtime";
@@ -30,7 +32,6 @@ export default function create(params: CreateParams) {
logNetwork: params.logNetwork,
resolvers: params.resolvers,
initLocalState: (localRecord, source, env) => {
localRecord.setValue(0, "authRevision");
if (params.initLocalState) {
params.initLocalState(localRecord, source, env);
}
@@ -46,6 +47,8 @@ export default function create(params: CreateParams) {
postMessage: new PostMessageService(),
browserInfo: { ios: false },
uuidGenerator: createUUIDGenerator(),
eventEmitter: new EventEmitter2({ wildcard: true, maxListeners: 20 }),
clearSession: noop,
};
const testRenderer = TestRenderer.create(
@@ -11,14 +11,8 @@ let testRenderer: ReactTestRenderer;
beforeEach(() => {
const resolvers = {
Query: {
asset: createSinonStub(
s => s.throws(),
s => s.withArgs(undefined, { id: assets[0].id }).returns(assets[0])
),
me: createSinonStub(
s => s.throws(),
s => s.withArgs(undefined, { clientAuthRevision: 0 }).returns(users[0])
),
asset: createSinonStub(s => s.throws(), s => s.returns(assets[0])),
me: createSinonStub(s => s.throws(), s => s.returns(users[0])),
},
Mutation: {
createComment: createSinonStub(
@@ -11,14 +11,8 @@ let testRenderer: ReactTestRenderer;
beforeEach(() => {
const resolvers = {
Query: {
asset: createSinonStub(
s => s.throws(),
s => s.withArgs(undefined, { id: assets[0].id }).returns(assets[0])
),
me: createSinonStub(
s => s.throws(),
s => s.withArgs(undefined, { clientAuthRevision: 0 }).returns(users[0])
),
asset: createSinonStub(s => s.throws(), s => s.returns(assets[0])),
me: createSinonStub(s => s.throws(), s => s.returns(users[0])),
},
Mutation: {
createComment: createSinonStub(
@@ -809,12 +809,8 @@ type Query {
"""
me is the current logged in User.
clientAuthRevision is an implementation detail that is only
used on the client to invalidate the cache.
TODO: This should move to a client side directive if this becomes possible.
"""
me(clientAuthRevision: Int): User
me: User
"""
settings is the Settings for a given Tenant.