diff --git a/package.json b/package.json index 635a15066..d2ca86843 100644 --- a/package.json +++ b/package.json @@ -13,7 +13,7 @@ "compile:schema": "node ./scripts/generateSchemaTypes.js", "docz": "docz", "start": "node dist/index.js", - "start:development": "cross-env NODE_ENV=development ts-node --project ./src/tsconfig.json -r tsconfig-paths/register ./src/index.ts", + "start:development": "ts-node --project ./src/tsconfig.json -r tsconfig-paths/register ./src/index.ts", "start:webpackDevServer": "ts-node ./scripts/start.ts", "lint": "npm-run-all --parallel lint:*", "lint:server": "tslint --project ./src/tsconfig.json", diff --git a/src/core/client/auth/mutations/SignInMutation.ts b/src/core/client/auth/mutations/SignInMutation.ts new file mode 100644 index 000000000..6a88b9313 --- /dev/null +++ b/src/core/client/auth/mutations/SignInMutation.ts @@ -0,0 +1,23 @@ +import { Environment } from "relay-runtime"; + +import { TalkContext } from "talk-framework/lib/bootstrap"; +import { createMutationContainer } from "talk-framework/lib/relay"; +import { signIn, SignInInput } from "talk-framework/rest"; + +export type SignInMutation = (input: SignInInput) => Promise; + +export async function commit( + environment: Environment, + input: SignInInput, + { rest, postMessage }: TalkContext +) { + try { + const result = await signIn(rest, input); + postMessage.send("setAuthToken", result.token, window.opener); + window.close(); + } catch (err) { + postMessage.send("authError", err.toString(), window.opener); + } +} + +export const withSignInMutation = createMutationContainer("signIn", commit); diff --git a/src/core/client/auth/mutations/SignUpMutation.ts b/src/core/client/auth/mutations/SignUpMutation.ts new file mode 100644 index 000000000..68beaeae2 --- /dev/null +++ b/src/core/client/auth/mutations/SignUpMutation.ts @@ -0,0 +1,23 @@ +import { Environment } from "relay-runtime"; + +import { TalkContext } from "talk-framework/lib/bootstrap"; +import { createMutationContainer } from "talk-framework/lib/relay"; +import { signUp, SignUpInput } from "talk-framework/rest"; + +export type SignUpMutation = (input: SignUpInput) => Promise; + +export async function commit( + environment: Environment, + input: SignUpInput, + { rest, postMessage }: TalkContext +) { + try { + const result = await signUp(rest, input); + postMessage.send("setAuthToken", result.token, window.opener); + window.close(); + } catch (err) { + postMessage.send("authError", err.toString(), window.opener); + } +} + +export const withSignUpMutation = createMutationContainer("signUp", commit); diff --git a/src/core/client/auth/mutations/index.ts b/src/core/client/auth/mutations/index.ts index 50d8d059b..46e02df95 100644 --- a/src/core/client/auth/mutations/index.ts +++ b/src/core/client/auth/mutations/index.ts @@ -1 +1,3 @@ export { withSetViewMutation, SetViewMutation } from "./SetViewMutation"; +export { withSignInMutation, SignInMutation } from "./SignInMutation"; +export { withSignUpMutation, SignUpMutation } from "./SignUpMutation"; diff --git a/src/core/client/framework/lib/rest.ts b/src/core/client/framework/lib/rest.ts index b8ccdccab..6ba52b958 100644 --- a/src/core/client/framework/lib/rest.ts +++ b/src/core/client/framework/lib/rest.ts @@ -21,7 +21,7 @@ const handleResp = (res: Response) => { if (res.status > 399) { return res.json().then((err: any) => { // TODO: sync error handling with server. - const message = err.message || res.status; + const message = err.message || err.error || res.status; const error = new Error(message); throw error; }); @@ -43,7 +43,7 @@ export class RestClient { this.tokenGetter = tokenGetter; } - public fetch(path: string, options: PartialRequestInit): Promise { + public fetch(path: string, options: PartialRequestInit): Promise { let opts = options; if (this.tokenGetter) { opts = merge({}, options, { diff --git a/src/core/client/framework/mutations/SetAuthTokenMutation.spec.ts b/src/core/client/framework/mutations/SetAuthTokenMutation.spec.ts index 7440078ad..587ddc2d5 100644 --- a/src/core/client/framework/mutations/SetAuthTokenMutation.spec.ts +++ b/src/core/client/framework/mutations/SetAuthTokenMutation.spec.ts @@ -2,6 +2,7 @@ import { commitLocalUpdate, 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"; import { commit } from "./SetAuthTokenMutation"; @@ -16,18 +17,34 @@ beforeAll(() => { }); it("Sets auth token", async () => { + const context = { + localStorage: createInMemoryStorage(), + }; const authToken = "auth token"; - commit(environment, { authToken }); + commit(environment, { authToken }, context as any); expect(source.get(LOCAL_ID)!.authToken).toEqual(authToken); + expect(context.localStorage.getItem("authToken")).toEqual(authToken); +}); + +it("Removes auth token from localStorage", async () => { + const context = { + localStorage: createInMemoryStorage(), + }; + localStorage.setItem("authToken", "tmp"); + commit(environment, { authToken: null }, context as any); + expect(context.localStorage.getItem("authToken")).toBeUndefined(); }); it("Should call gc", async () => { + const context = { + localStorage: createInMemoryStorage(), + }; commitLocalUpdate(environment, store => { store.create("should-disappear", "tmp"); }); const authToken = null; expect(source.get("should-disappear")).not.toBeUndefined(); - commit(environment, { authToken }); + commit(environment, { authToken }, context as any); await timeout(); expect(source.get(LOCAL_ID)!.authToken).toEqual(authToken); expect(source.get("should-disappear")).toBeUndefined(); diff --git a/src/core/client/framework/mutations/SetAuthTokenMutation.ts b/src/core/client/framework/mutations/SetAuthTokenMutation.ts index 38ecde460..223bc4054 100644 --- a/src/core/client/framework/mutations/SetAuthTokenMutation.ts +++ b/src/core/client/framework/mutations/SetAuthTokenMutation.ts @@ -1,5 +1,6 @@ import { commitLocalUpdate, Environment } from "relay-runtime"; +import { TalkContext } from "talk-framework/lib/bootstrap"; import { createMutationContainer } from "talk-framework/lib/relay"; import { LOCAL_ID } from "talk-framework/lib/relay/withLocalStateContainer"; @@ -11,11 +12,17 @@ export type SetAuthTokenMutation = (input: SetAuthTokenInput) => Promise; export async function commit( environment: Environment, - input: SetAuthTokenInput + input: SetAuthTokenInput, + { localStorage }: TalkContext ) { return commitLocalUpdate(environment, store => { const record = store.get(LOCAL_ID)!; record.setValue(input.authToken, "authToken"); + if (input.authToken) { + localStorage.setItem("authToken", input.authToken); + } else { + localStorage.removeItem("authToken"); + } // Force gc to trigger. environment diff --git a/src/core/client/framework/rest/signIn.ts b/src/core/client/framework/rest/signIn.ts index 4b537b01c..9f357df02 100644 --- a/src/core/client/framework/rest/signIn.ts +++ b/src/core/client/framework/rest/signIn.ts @@ -1,12 +1,16 @@ import { RestClient } from "../lib/rest"; export interface SignInInput { - username: string; + email: string; password: string; } +export interface SignInResponse { + token: string; +} + export default function signIn(rest: RestClient, input: SignInInput) { - return rest.fetch("/tenant/auth/local", { + return rest.fetch("/tenant/auth/local", { method: "POST", body: input, }); diff --git a/src/core/client/framework/rest/signUp.ts b/src/core/client/framework/rest/signUp.ts index eece6271b..a6aa87d76 100644 --- a/src/core/client/framework/rest/signUp.ts +++ b/src/core/client/framework/rest/signUp.ts @@ -6,8 +6,12 @@ export interface SignUpInput { email: string; } +export interface SignUpResponse { + token: string; +} + export default function signUp(rest: RestClient, input: SignUpInput) { - return rest.fetch("/tenant/auth/local/signup", { + return rest.fetch("/tenant/auth/local/signup", { method: "POST", body: input, }); diff --git a/src/core/client/stream/listeners/onPostMessageSetAuthToken.spec.ts b/src/core/client/stream/listeners/onPostMessageSetAuthToken.spec.ts index 37ed034d6..c35823c8a 100644 --- a/src/core/client/stream/listeners/onPostMessageSetAuthToken.spec.ts +++ b/src/core/client/stream/listeners/onPostMessageSetAuthToken.spec.ts @@ -1,6 +1,7 @@ import { Environment, RecordSource } from "relay-runtime"; import { LOCAL_ID } from "talk-framework/lib/relay"; +import { createInMemoryStorage } from "talk-framework/lib/storage"; import { createRelayEnvironment } from "talk-framework/testHelpers"; import onPostMessageSetAuthToken from "./onPostMessageSetAuthToken"; @@ -24,6 +25,7 @@ it("Sets auth token", () => { }, }, relayEnvironment, + localStorage: createInMemoryStorage(), }; onPostMessageSetAuthToken(context as any); expect(source.get(LOCAL_ID)!.authToken).toEqual(token); diff --git a/src/core/client/stream/listeners/onPostMessageSetAuthToken.ts b/src/core/client/stream/listeners/onPostMessageSetAuthToken.ts index 77be702ad..767af51f7 100644 --- a/src/core/client/stream/listeners/onPostMessageSetAuthToken.ts +++ b/src/core/client/stream/listeners/onPostMessageSetAuthToken.ts @@ -2,12 +2,10 @@ import { TalkContext } from "talk-framework/lib/bootstrap"; import { commit as setAuthToken } from "talk-framework/mutations/SetAuthTokenMutation"; -export default function onPostMessageSetAuthToken({ - relayEnvironment, - postMessage, -}: TalkContext) { +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 }); + setAuthToken(relayEnvironment, { authToken }, ctx); }); } diff --git a/src/core/client/stream/local/initLocalState.ts b/src/core/client/stream/local/initLocalState.ts index dd021f756..171c52294 100644 --- a/src/core/client/stream/local/initLocalState.ts +++ b/src/core/client/stream/local/initLocalState.ts @@ -23,6 +23,7 @@ export default async function initLocalState( { localStorage }: TalkContext ) { commitLocalUpdate(environment, s => { + // TODO: (cvle) move local, auth token and network initialization to framework. const root = s.getRoot(); // Create the Local Record which is the Root for the client states.