[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 16:04:54 +00:00
committed by Wyatt Johnson
parent 26b59fc17c
commit 8b09b52be8
30 changed files with 485 additions and 331 deletions
+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(