Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion examples/ExpoMessaging/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@
"react-native-teleport": "^1.1.12",
"react-native-web": "^0.21.2",
"react-native-worklets": "0.11.1",
"stream-chat": "^10.0.0-rc.16",
"stream-chat": "^10.0.0-rc.17",
"stream-chat-expo": "workspace:^",
"stream-chat-react-native-core": "workspace:^"
},
Expand Down
2 changes: 1 addition & 1 deletion examples/SampleApp/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@
"react-native-teleport": "^1.1.12",
"react-native-video": "^6.19.2",
"react-native-worklets": "^0.12.1",
"stream-chat": "^10.0.0-rc.16",
"stream-chat": "^10.0.0-rc.17",
"stream-chat-react-native": "workspace:^",
"stream-chat-react-native-core": "workspace:^"
},
Expand Down
33 changes: 7 additions & 26 deletions examples/SampleApp/src/ChatUsers.ts
Original file line number Diff line number Diff line change
@@ -1,31 +1,12 @@
import { ClientUser } from 'stream-chat';

export const USER_TOKENS: Record<string, string> = {
e2etest1:
'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX2lkIjoiZTJldGVzdDEifQ.XlQOw8nl7fFzHoBkEiTcYGkNo5r7EBYA40LABGOk4hc',
e2etest2:
'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX2lkIjoiZTJldGVzdDIifQ.2ZsHCMJ7i0vZvRJ5yoT-bm8OD_KAzBgJ-kB6bHGZ4FI',
e2etest3:
'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX2lkIjoiZTJldGVzdDMifQ.RWHY-MYkpP8FTJkfgrxUlCQhwap6eB7DTsp_HsZ1oIw',
khushal:
'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX2lkIjoia2h1c2hhbCJ9.NG3b6I8MgkLevwuTTqDXTpOol-Yj_1NCyvxewL_tg4U',
neil: 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX2lkIjoibmVpbCJ9.ty2YhwFaVEYkq1iUfY8s1G0Um3MpiVYpWK-b5kMky0w',
qatest1:
'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX2lkIjoicWF0ZXN0MSJ9.5Nnj6MsauhjP7_D8jW9WbRovLv5uaxn8LPZZ-HB3mh4',
qatest2:
'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX2lkIjoicWF0ZXN0MiJ9.9v0igQXv_IfL_uKZ6Xz9MXJOS-DMdkeWScJv-POFoTE',
thierry:
'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX2lkIjoidGhpZXJyeSJ9.Rm6o4HHlUE5zYoaJG1YoEE-V219j4KHTqZ9zqXlCHBQ',
tommaso:
'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX2lkIjoidG9tbWFzbyJ9.sfDKQ5peCwo0oObPDo-AFKauqv5uYbesukOOLpKaDOQ',
vir: 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX2lkIjoidmlyIn0.Whk_WyeN9TIuNahSu3KFVRTohF7HDbQq9Ka17kZ6zq4',
vishal:
'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX2lkIjoidmlzaGFsIn0.HOlVh-ZyQnjyuL20G-67RTgKufBuAH-I-gbEELFlass',
ivan5:
'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX2lkIjoiaXZhbjUifQ.c3nq6rlnqyNeCRcwiW0VQ413nHl99pF72Ia2V_N84yU',
rodolphe:
'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX2lkIjoicm9kb2xwaGUifQ.tLl-I8ADBhTKB-x5FB9jK4-am0dELLXgydM6VN9rTL8',
};
/**
* Optional static tokens, keyed by user id. A user listed here logs in with this token; any other
* user gets one from the token generator (see `utils/tokenProvider.ts`). Empty by default, e.g.
* `neil: 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...'`.
*/
export const USER_TOKENS: Partial<Record<string, string>> = {};

/**
* Login fixtures, not server responses: these are the payloads handed to `connectUser`, so they
* carry only `id` / `name` / `image`. Annotated `ClientUser` (everything optional but `id`) rather
Expand Down
5 changes: 3 additions & 2 deletions examples/SampleApp/src/hooks/useChatClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import { SqliteClient } from 'stream-chat-react-native';
import { USER_TOKENS, USERS } from '../ChatUsers';
import type { LoginConfig } from '../types';
import AsyncStore from '../utils/AsyncStore';
import { getTokenOrProvider, SAMPLE_APP_API_KEY } from '../utils/tokenProvider';

const messaging = getMessaging();

Expand Down Expand Up @@ -98,7 +99,7 @@ export const useChatClient = () => {
image: config.userImage,
name: config.userName,
};
await client.connectUser(user, config.userToken);
await client.connectUser(user, getTokenOrProvider(config));
await AsyncStore.setItem('@stream-rn-sampleapp-login-config', config);

const permissionAuthStatus = await messaging.hasPermission();
Expand Down Expand Up @@ -175,7 +176,7 @@ export const useChatClient = () => {
try {
if (userId) {
await loginUser({
apiKey: 'yjrt5yxw77ev',
apiKey: SAMPLE_APP_API_KEY,
userId: USERS[userId].id,
userImage: USERS[userId].image,
userName: USERS[userId].name,
Expand Down
25 changes: 19 additions & 6 deletions examples/SampleApp/src/screens/AdvancedUserSelectorScreen.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { KeyboardCompatibleView, useTheme, version } from 'stream-chat-react-nat

import { useAppContext } from '../context/AppContext';
import { useLegacyColors } from '../theme/useLegacyColors';
import { DEFAULT_TOKEN_URL } from '../utils/tokenProvider';

const styles = StyleSheet.create({
safeArea: { flex: 1 },
Expand Down Expand Up @@ -147,7 +148,8 @@ export const AdvancedUserSelectorScreen: React.FC = () => {
const [userIdError, setUserIdError] = useState(false);
const [userName, setUserName] = useState('');
const [userToken, setUserToken] = useState('');
const [userTokenError, setUserTokenError] = useState(false);
const [tokenUrl, setTokenUrl] = useState(DEFAULT_TOKEN_URL);
const [tokenError, setTokenError] = useState(false);

const isValidInput = () => {
let isValid = true;
Expand All @@ -161,8 +163,9 @@ export const AdvancedUserSelectorScreen: React.FC = () => {
isValid = false;
}

if (!userToken) {
setUserTokenError(true);
// Either credential will do: a static token wins, otherwise one is fetched from the token URL.
if (!userToken && !tokenUrl) {
setTokenError(true);
isValid = false;
}

Expand Down Expand Up @@ -201,14 +204,23 @@ export const AdvancedUserSelectorScreen: React.FC = () => {
value={userId}
/>
<LabeledTextInput
error={userTokenError}
error={tokenError}
label='User Token'
onChangeText={(text) => {
setUserTokenError(false);
setTokenError(false);
setUserToken(text);
}}
value={userToken}
/>
<LabeledTextInput
error={tokenError}
label='Token URL'
onChangeText={(text) => {
setTokenError(false);
setTokenUrl(text);
}}
value={tokenUrl}
/>
<LabeledTextInput
label='Username (optional)'
onChangeText={(text) => {
Expand All @@ -228,8 +240,9 @@ export const AdvancedUserSelectorScreen: React.FC = () => {
await loginUser({
apiKey,
userId,
tokenUrl: tokenUrl || undefined,
userName,
userToken,
userToken: userToken || undefined,
});
} catch (e) {
Alert.alert(
Expand Down
5 changes: 4 additions & 1 deletion examples/SampleApp/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,10 @@ export type AppTheme = Theme & {
export type LoginConfig = {
apiKey: string;
userId: string;
userToken: string;
/** Static token. When omitted, tokens are fetched from `tokenUrl`. */
userToken?: string;
/** Token generator URL; `user_id` is appended. Defaults to `DEFAULT_TOKEN_URL`. */
tokenUrl?: string;
userImage?: string;
userName?: string;
};
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ import { DeliveredMessagePayload, StreamChat } from 'stream-chat';

import AsyncStore from './AsyncStore';

import { getTokenOrProvider } from './tokenProvider';

import { LoginConfig } from '../types';

const messaging = getMessaging();
Expand Down Expand Up @@ -47,7 +49,7 @@ setBackgroundMessageHandler(messaging, async (remoteMessage) => {
}
const chatClient = StreamChat.getInstance(loginConfig.apiKey);
// eslint-disable-next-line no-underscore-dangle -- stream-chat exposes _setToken as a public-but-underscore-prefixed method for background contexts
await chatClient._setToken({ id: loginConfig.userId }, loginConfig.userToken);
await chatClient._setToken({ id: loginConfig.userId }, getTokenOrProvider(loginConfig));

const notification = remoteMessage.data;

Expand Down
39 changes: 39 additions & 0 deletions examples/SampleApp/src/utils/tokenProvider.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import type { TokenOrProvider, TokenProvider } from 'stream-chat';

import type { LoginConfig } from '../types';

export const SAMPLE_APP_API_KEY = 'yjrt5yxw77ev';

/**
* Pronto's token generator — the same one stream-chat-react's vite example uses — scoped to the
* SampleApp's app via `environment`. It signs tokens for {@link SAMPLE_APP_API_KEY} only; a custom
* app needs its own generator URL or a static token.
*/
export const DEFAULT_TOKEN_URL =
'https://pronto.getstream.io/api/auth/create-token?environment=react-native-chat-sample';

/**
* Appends `user_id` by hand rather than through `URL.searchParams`: React Native's `URL` polyfill
* builds a fresh `URLSearchParams` on every `searchParams` read, so a `set()` on it never reaches
* `toString()`.
*/
const buildTokenRequestUrl = (tokenUrl: string, userId: string) =>
`${tokenUrl}${tokenUrl.includes('?') ? '&' : '?'}user_id=${encodeURIComponent(userId)}`;

export const createTokenProvider =
(tokenUrl: string, userId: string): TokenProvider =>
async () => {
const response = await fetch(buildTokenRequestUrl(tokenUrl, userId));
if (!response.ok) {
throw new Error(`Token generator responded with ${response.status}`);
}
const { token } = (await response.json()) as { token?: string };
if (!token) {
throw new Error('Token generator response did not contain a token');
}
return token;
};

/** A static token wins when one is provided; otherwise tokens come from the generator. */
export const getTokenOrProvider = ({ tokenUrl, userId, userToken }: LoginConfig): TokenOrProvider =>
userToken || createTokenProvider(tokenUrl || DEFAULT_TOKEN_URL, userId);
2 changes: 1 addition & 1 deletion package/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@
"path": "0.12.7",
"react-native-markdown-package": "1.8.2",
"react-native-url-polyfill": "^2.0.0",
"stream-chat": "^10.0.0-rc.16",
"stream-chat": "^10.0.0-rc.17",
"use-sync-external-store": "^1.7.0"
},
"peerDependencies": {
Expand Down
14 changes: 7 additions & 7 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -6610,7 +6610,7 @@ __metadata:
react-native-teleport: "npm:^1.1.12"
react-native-web: "npm:^0.21.2"
react-native-worklets: "npm:0.11.1"
stream-chat: "npm:^10.0.0-rc.16"
stream-chat: "npm:^10.0.0-rc.17"
stream-chat-expo: "workspace:^"
stream-chat-react-native-core: "workspace:^"
typescript: "npm:6.0.3"
Expand Down Expand Up @@ -17608,7 +17608,7 @@ __metadata:
react-native-teleport: "npm:^1.1.12"
react-native-video: "npm:^6.19.2"
react-native-worklets: "npm:^0.12.1"
stream-chat: "npm:^10.0.0-rc.16"
stream-chat: "npm:^10.0.0-rc.17"
stream-chat-react-native: "workspace:^"
stream-chat-react-native-core: "workspace:^"
typescript: "npm:6.0.3"
Expand Down Expand Up @@ -18352,7 +18352,7 @@ __metadata:
react-native-worklets: "npm:^0.12.1"
react-test-renderer: "npm:19.2.3"
rimraf: "npm:^6.0.1"
stream-chat: "npm:^10.0.0-rc.16"
stream-chat: "npm:^10.0.0-rc.17"
typescript: "npm:6.0.3"
use-sync-external-store: "npm:^1.7.0"
uuid: "npm:^11.1.0"
Expand Down Expand Up @@ -18426,9 +18426,9 @@ __metadata:
languageName: unknown
linkType: soft

"stream-chat@npm:^10.0.0-rc.16":
version: 10.0.0-rc.16
resolution: "stream-chat@npm:10.0.0-rc.16"
"stream-chat@npm:^10.0.0-rc.17":
version: 10.0.0-rc.17
resolution: "stream-chat@npm:10.0.0-rc.17"
dependencies:
"@stream-io/logger": "npm:^2.0.0"
"@stream-io/state-store": "npm:^1.1.6"
Expand All @@ -18439,7 +18439,7 @@ __metadata:
built: true
husky:
built: true
checksum: 10c0/accb88f0763ff513f9a3899c4a96c32968067056898232ee5ad58540482ee0794fc06d13862904770d2d9fb65907b54b767a57f3f26ce613ef71e22228e7a3a2
checksum: 10c0/f4721fb4db398ff465bf58a918aa4cd4a46085cc57ce305019c65fdda5b710dfaa14af9870bf060894ab35053fe4697e6ca29276323a625bf8b445c9ca274b77
languageName: node
linkType: hard

Expand Down
Loading