mirror of
https://github.com/wassname/talk.git
synced 2026-07-28 11:27:05 +08:00
[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:
@@ -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;
|
||||
+11
-6
@@ -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;
|
||||
+9
-7
@@ -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;
|
||||
@@ -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);
|
||||
|
||||
@@ -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(
|
||||
|
||||
Reference in New Issue
Block a user