Skip to content

Commit 2c66989

Browse files
authored
[MOB-11524] Support non-JWT requests in react sample app (#505)
* [MOB-11524] Add USE_JWT to env example * [MOB-11524] Add lint exception for sample app * [MOB-11524] Support non-JWT requests in react sample app * [MOB-11524] Fix init * [MOB-11524] Use await * [MOB-11524] Set auth token for non-jwt logins after a logout * [MOB-11524] Set auth token for non-jwt logins after a logout * [MOB-11524] Create helper fn * [MOB-11524] Undo autoformat * [MOB-11524] Resolve linter error
1 parent 0ba7a70 commit 2c66989

8 files changed

Lines changed: 166 additions & 98 deletions

File tree

react-example/.env.example

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,10 @@
55
# Only set BASE_URL if developing locally, as it will take precedence over the production api urls.
66
# BASE_URL="https://api.iterable.com/api"
77

8-
# You can set the URL for the JWT generator here if needed
8+
# You may authenticate with JWT; be sure to use an API key that is configured to require JWT.
9+
# USE_JWT=true
10+
11+
# You can set the URL for the JWT generator here if needed.
912
# JWT_GENERATOR=http://localhost:3000/generate
1013

1114
# Set this to false to prevent messages from being consumed to fetch the same message(s) when testing changes locally.

react-example/.eslintrc

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,8 @@
55
],
66
"rules": {
77
"@typescript-eslint/no-empty-interface": "off",
8-
"react/react-in-jsx-scope": "off",
8+
"import/no-unresolved": "off",
9+
"react/react-in-jsx-scope": "off"
910
},
1011
"ignorePatterns": [
1112
"node_modules/"

react-example/src/components/LoginForm.tsx

Lines changed: 21 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,16 @@
11
/* eslint-disable @typescript-eslint/no-unused-vars */
2-
import { ChangeEvent, FC, FormEvent, SetStateAction, useState } from 'react';
2+
import { ChangeEvent, FC, FormEvent, useState } from 'react';
33
import styled from 'styled-components';
4-
5-
import { TextField } from './TextField';
6-
import { Button } from './Button';
7-
84
import { useUser } from '../context/Users';
5+
import { Button } from './Button';
6+
import { TextField } from './TextField';
97

108
const StyledTextField = styled(TextField)``;
119

1210
const StyledButton = styled(Button)`
1311
margin-left: 0.4em;
14-
max-width: 425px;
12+
width: fit-content;
13+
white-space: nowrap;
1514
`;
1615

1716
const Form = styled.form`
@@ -38,10 +37,10 @@ const Error = styled.div`
3837
`;
3938

4039
interface Props {
41-
setEmail: (email: string) => Promise<string>;
42-
setUserId: (userId: string) => Promise<string>;
40+
setEmail: (email: string) => Promise<string> | void;
41+
setUserId: (userId: string) => Promise<string | void>;
4342
logout: () => void;
44-
refreshJwt: (authTypes: string) => Promise<string>;
43+
refreshJwt?: (authTypes: string) => Promise<string>;
4544
}
4645

4746
export const LoginForm: FC<Props> = ({
@@ -52,24 +51,22 @@ export const LoginForm: FC<Props> = ({
5251
}) => {
5352
const [useEmail, setUseEmail] = useState<boolean>(true);
5453
const [user, updateUser] = useState<string>(process.env.LOGIN_EMAIL || '');
55-
5654
const [error, setError] = useState<string>('');
57-
5855
const [isEditingUser, setEditingUser] = useState<boolean>(false);
5956

6057
const { loggedInUser, setLoggedInUser } = useUser();
6158

62-
const handleSubmit = (e: FormEvent<HTMLFormElement>) => {
59+
const handleSubmit = async (e: FormEvent<HTMLFormElement>) => {
6360
e.preventDefault();
6461

6562
const setUser = useEmail ? setEmail : setUserId;
66-
67-
setUser(user)
68-
.then(() => {
69-
setEditingUser(false);
70-
setLoggedInUser({ type: 'user_update', data: user });
71-
})
72-
.catch(() => setError('Something went wrong!'));
63+
try {
64+
await setUser(user);
65+
setEditingUser(false);
66+
setLoggedInUser({ type: 'user_update', data: user });
67+
} catch (error) {
68+
setError('Something went wrong!');
69+
}
7370
};
7471

7572
const handleLogout = () => {
@@ -105,9 +102,11 @@ export const LoginForm: FC<Props> = ({
105102
<StyledButton onClick={handleEditUser}>
106103
Logged in as {`${first5}...${last9}`} (change)
107104
</StyledButton>
108-
<StyledButton onClick={handleJwtRefresh}>
109-
Manually Refresh JWT Token
110-
</StyledButton>
105+
{refreshJwt && (
106+
<StyledButton onClick={handleJwtRefresh}>
107+
Manually Refresh JWT Token
108+
</StyledButton>
109+
)}
111110
<StyledButton onClick={handleLogout}>Logout</StyledButton>
112111
</>
113112
) : (

react-example/src/index.tsx

Lines changed: 50 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,27 @@
1-
import { initializeWithConfig, WithJWTParams } from '@iterable/web-sdk';
2-
import axios from 'axios';
1+
import {
2+
InitializeParams,
3+
initializeWithConfig,
4+
WithJWT
5+
} from '@iterable/web-sdk';
6+
import axios, { AxiosResponse } from 'axios';
37
import './styles/index.css';
48

5-
import { BrowserRouter, Routes, Route } from 'react-router-dom';
6-
import styled from 'styled-components';
79
import { createRoot } from 'react-dom/client';
8-
import { Home } from './views/Home';
9-
import { Commerce } from './views/Commerce';
10-
import { Events } from './views/Events';
11-
import { Users } from './views/Users';
12-
import { InApp } from './views/InApp';
13-
import { EmbeddedMessage } from './views/Embedded';
10+
import { BrowserRouter, Route, Routes } from 'react-router-dom';
11+
import styled from 'styled-components';
1412
import { Link } from './components/Link';
1513
import { LoginForm } from './components/LoginForm';
16-
import { EmbeddedMsgs } from './views/EmbeddedMsgs';
17-
1814
import { UserProvider } from './context/Users';
19-
import { EmbeddedMsgsImpressionTracker } from './views/EmbeddedMsgsImpressionTracker';
15+
import {
16+
Home,
17+
Commerce,
18+
Events,
19+
Users,
20+
InApp,
21+
EmbeddedMsgs,
22+
EmbeddedMessage,
23+
EmbeddedMsgsImpressionTracker
24+
} from './views';
2025

2126
const Wrapper = styled.div`
2227
display: flex;
@@ -41,32 +46,44 @@ const HomeLink = styled(Link)`
4146
`;
4247

4348
((): void => {
44-
const initializeParams: WithJWTParams = {
45-
authToken: process.env.API_KEY || '',
49+
const authToken = process.env.API_KEY || '';
50+
const jwtGenerator =
51+
process.env.JWT_GENERATOR || 'http://localhost:5000/generate';
52+
const jwtSecret = process.env.JWT_SECRET || '';
53+
const useJwt = process.env.USE_JWT === 'true';
54+
55+
const initializeParams: InitializeParams = {
56+
authToken,
4657
configOptions: {
4758
isEuIterableService: false,
4859
dangerouslyAllowJsPopups: true
4960
},
50-
generateJWT: ({ email, userID }) =>
51-
axios
52-
.post(
53-
process.env.JWT_GENERATOR || 'http://localhost:5000/generate',
54-
{
55-
exp_minutes: 2,
56-
email,
57-
user_id: userID,
58-
jwt_secret: process.env.JWT_SECRET
59-
},
60-
{
61-
headers: {
62-
'Content-Type': 'application/json'
63-
}
64-
}
65-
)
66-
.then((response: any) => response.data?.token)
61+
...(useJwt
62+
? {
63+
generateJWT: ({ email, userID }: { email: string; userID: string }) =>
64+
axios
65+
.post(
66+
jwtGenerator,
67+
{
68+
exp_minutes: 2,
69+
email,
70+
user_id: userID,
71+
jwt_secret: jwtSecret
72+
},
73+
{ headers: { 'Content-Type': 'application/json' } }
74+
)
75+
.then(
76+
(response: AxiosResponse<{ token: string }>) =>
77+
response.data?.token
78+
)
79+
}
80+
: {})
6781
};
68-
const { setEmail, setUserID, logout, refreshJwtToken } =
82+
const { setEmail, setUserID, logout, ...rest } =
6983
initializeWithConfig(initializeParams);
84+
const refreshJwtToken = useJwt
85+
? (rest as Partial<WithJWT>).refreshJwtToken
86+
: undefined;
7087

7188
const container = document.getElementById('root');
7289
const root = createRoot(container);

react-example/src/views/InApp.tsx

Lines changed: 22 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,11 @@
1-
import { FC, FormEvent, useState } from 'react';
1+
import {
2+
DisplayOptions,
3+
getInAppMessages,
4+
InAppMessageResponse
5+
} from '@iterable/web-sdk';
6+
import { AxiosError, AxiosResponse } from 'axios';
7+
import { FC, FormEvent, useEffect, useState } from 'react';
28
import styled from 'styled-components';
3-
import { DisplayOptions, getInAppMessages } from '@iterable/web-sdk';
49
import { Button } from '../components/Button';
510
import { useUser } from '../context/Users';
611
import { EndpointWrapper, Heading, Response } from './Components.styled';
@@ -28,6 +33,8 @@ const AutoDisplayContainer = styled.div`
2833
}
2934
`;
3035

36+
const DEFAULT_GET_MESSAGES_RESPONSE = 'Endpoint JSON goes here';
37+
3138
const { request, pauseMessageStream, resumeMessageStream } = getInAppMessages(
3239
{
3340
count: 20,
@@ -44,7 +51,7 @@ export const InApp: FC<{}> = () => {
4451
const [isGettingMessagesAuto, setIsGettingMessagesAuto] =
4552
useState<boolean>(false);
4653
const [getMessagesResponse, setGetMessagesResponse] = useState<string>(
47-
'Endpoint JSON goes here'
54+
DEFAULT_GET_MESSAGES_RESPONSE
4855
);
4956
const { loggedInUser } = useUser();
5057
const [rawMessageCount, setRawMessageCount] = useState<number | null>(null);
@@ -60,12 +67,12 @@ export const InApp: FC<{}> = () => {
6067
{ display: DisplayOptions.Deferred }
6168
)
6269
.request()
63-
.then((response: any) => {
70+
.then((response: AxiosResponse<InAppMessageResponse>) => {
6471
setRawMessageCount(response.data.inAppMessages.length);
6572
setIsGettingMessagesRaw(false);
6673
setGetMessagesResponse(JSON.stringify(response.data, null, 2));
6774
})
68-
.catch((e: any) => {
75+
.catch((e: AxiosError<InAppMessageResponse>) => {
6976
setIsGettingMessagesRaw(false);
7077
setGetMessagesResponse(JSON.stringify(e.response.data, null, 2));
7178
});
@@ -77,7 +84,7 @@ export const InApp: FC<{}> = () => {
7784
setIsGettingMessagesAuto(true);
7885

7986
return request()
80-
.then((response: any) => {
87+
.then((response: AxiosResponse<InAppMessageResponse>) => {
8188
setAutoMessageCount(response.data.inAppMessages.length);
8289
setIsGettingMessagesAuto(false);
8390
})
@@ -96,6 +103,15 @@ export const InApp: FC<{}> = () => {
96103
resumeMessageStream();
97104
};
98105

106+
/** Reset state when user is not logged in */
107+
useEffect(() => {
108+
if (!loggedInUser.length) {
109+
setRawMessageCount(null);
110+
setAutoMessageCount(null);
111+
setGetMessagesResponse(DEFAULT_GET_MESSAGES_RESPONSE);
112+
}
113+
}, [loggedInUser]);
114+
99115
return (
100116
<>
101117
<h1>inApp Endpoints</h1>

react-example/src/views/index.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
export * from './Commerce';
2+
export * from './Embedded';
3+
export * from './EmbeddedMsgs';
4+
export * from './EmbeddedMsgsImpressionTracker';
5+
export * from './Events';
6+
export * from './Home';
7+
export * from './InApp';
8+
export * from './Users';

src/authorization/authorization.test.ts

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -105,7 +105,7 @@ describe('API Key Interceptors', () => {
105105
packageName: 'my-lil-website'
106106
});
107107
expect(response.config.headers['Api-Key']).toBe('123');
108-
expect(response.config.headers['Authorization']).toBe(
108+
expect(response.config.headers.Authorization).toBe(
109109
`Bearer ${MOCK_JWT_KEY}`
110110
);
111111
});
@@ -122,7 +122,7 @@ describe('API Key Interceptors', () => {
122122
packageName: 'my-lil-website'
123123
});
124124
expect(response.config.headers['Api-Key']).toBe('123');
125-
expect(response.config.headers['Authorization']).toBe(
125+
expect(response.config.headers.Authorization).toBe(
126126
`Bearer ${MOCK_JWT_KEY}`
127127
);
128128
});
@@ -227,8 +227,8 @@ describe('API Key Interceptors', () => {
227227
await updateUserEmail('helloworld@gmail.com');
228228

229229
jest.advanceTimersByTime(60000 * 4.1);
230-
/*
231-
called once originally, a second time after the email was changed,
230+
/*
231+
called once originally, a second time after the email was changed,
232232
and a third after the JWT was about to expire
233233
*/
234234
expect(mockGenerateJWT).toHaveBeenCalledTimes(3);
@@ -276,8 +276,8 @@ describe('API Key Interceptors', () => {
276276
});
277277

278278
jest.advanceTimersByTime(60000 * 4.1);
279-
/*
280-
called once originally, a second time after the email was changed,
279+
/*
280+
called once originally, a second time after the email was changed,
281281
and a third after the JWT was about to expire
282282
*/
283283
expect(mockGenerateJWT).toHaveBeenCalledTimes(3);
@@ -310,8 +310,8 @@ describe('API Key Interceptors', () => {
310310
});
311311

312312
jest.advanceTimersByTime(60000 * 4.1);
313-
/*
314-
called once originally, a second time after the email was changed,
313+
/*
314+
called once originally, a second time after the email was changed,
315315
and a third after the JWT was about to expire
316316
*/
317317
expect(mockGenerateJWT).toHaveBeenCalledTimes(3);
@@ -1069,7 +1069,7 @@ describe('User Identification', () => {
10691069
packageName: 'my-lil-website'
10701070
});
10711071
expect(response.config.headers['Api-Key']).toBe('123');
1072-
expect(response.config.headers['Authorization']).toBe(
1072+
expect(response.config.headers.Authorization).toBe(
10731073
`Bearer ${MOCK_JWT_KEY}`
10741074
);
10751075
});
@@ -1086,7 +1086,7 @@ describe('User Identification', () => {
10861086
packageName: 'my-lil-website'
10871087
});
10881088
expect(response.config.headers['Api-Key']).toBe('123');
1089-
expect(response.config.headers['Authorization']).toBe(
1089+
expect(response.config.headers.Authorization).toBe(
10901090
`Bearer ${MOCK_JWT_KEY}`
10911091
);
10921092
});

0 commit comments

Comments
 (0)