Skip to content

Commit 2680bca

Browse files
authored
[CHA-0] support client-side requests (#213)
* experiment with client-side request * fix StreamRequest * support UserToken * use reflection to set request * extract to UserTokenCallProxy * add UserTokenCallAdapterFactory * create UserServiceFactory * make UserTokenCallProxy generic * refactor namings * add TokenInjectionException * add UserServiceFactorySelector & fix visibility * add docs * add UserServiceFactoryCall * improve UserCall * improve UserServiceFactoryCall * emasure create3 * code clean up * delete CustomTest * fix formatting * fix imports * code clean up * fix compilation * compile fix
1 parent d99c580 commit 2680bca

File tree

13 files changed

+916
-13
lines changed

13 files changed

+916
-13
lines changed

src/main/java/io/getstream/chat/java/models/framework/StreamRequest.java

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import io.getstream.chat.java.exceptions.StreamException;
44
import io.getstream.chat.java.services.framework.Client;
55
import io.getstream.chat.java.services.framework.StreamServiceHandler;
6+
import io.getstream.chat.java.services.framework.UserClient;
67
import java.util.function.Consumer;
78
import org.jetbrains.annotations.NotNull;
89
import org.jetbrains.annotations.Nullable;
@@ -13,6 +14,8 @@ public abstract class StreamRequest<T extends StreamResponse> {
1314

1415
private Client client;
1516

17+
private String userToken;
18+
1619
/**
1720
* Executes the request
1821
*
@@ -53,8 +56,17 @@ public StreamRequest<T> withClient(Client client) {
5356
return this;
5457
}
5558

59+
public StreamRequest<T> withUserToken(final String token) {
60+
this.userToken = token;
61+
return this;
62+
}
63+
5664
@NotNull
5765
protected Client getClient() {
58-
return (client == null) ? Client.getInstance() : client;
66+
Client finalClient = (client == null) ? Client.getInstance() : client;
67+
if (userToken != null && !userToken.isEmpty()) {
68+
return new UserClient(finalClient, userToken);
69+
}
70+
return finalClient;
5971
}
6072
}

src/main/java/io/getstream/chat/java/services/framework/Client.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,10 @@ public interface Client {
77
@NotNull
88
<TService> TService create(Class<TService> svcClass);
99

10+
default @NotNull <TService> TService create(Class<TService> svcClass, String userToken) {
11+
return create(svcClass);
12+
}
13+
1014
@NotNull
1115
String getApiKey();
1216

src/main/java/io/getstream/chat/java/services/framework/DefaultClient.java

Lines changed: 50 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,13 @@
1111
import java.nio.charset.StandardCharsets;
1212
import java.security.Key;
1313
import java.time.Duration;
14-
import java.util.*;
14+
import java.util.Calendar;
15+
import java.util.Date;
16+
import java.util.GregorianCalendar;
17+
import java.util.Properties;
18+
import java.util.TimeZone;
1519
import java.util.concurrent.TimeUnit;
20+
import java.util.function.Function;
1621
import javax.crypto.spec.SecretKeySpec;
1722
import okhttp3.ConnectionPool;
1823
import okhttp3.HttpUrl;
@@ -30,10 +35,13 @@ public class DefaultClient implements Client {
3035

3136
private static final String API_DEFAULT_URL = "https://chat.stream-io-api.com";
3237
private static volatile DefaultClient defaultInstance;
33-
@NotNull private Retrofit retrofit;
3438
@NotNull private final String apiSecret;
3539
@NotNull private final String apiKey;
3640
@NotNull private final Properties extendedProperties;
41+
@NotNull private final Function<Retrofit, UserServiceFactory> serviceFactoryBuilder;
42+
43+
@NotNull Retrofit retrofit;
44+
@NotNull UserServiceFactory serviceFactory;
3745

3846
public static DefaultClient getInstance() {
3947
if (defaultInstance == null) {
@@ -56,6 +64,12 @@ public DefaultClient() {
5664
}
5765

5866
public DefaultClient(Properties properties) {
67+
this(properties, UserServiceFactorySelector::new);
68+
}
69+
70+
public DefaultClient(
71+
@NotNull Properties properties,
72+
@NotNull Function<Retrofit, UserServiceFactory> serviceFactoryBuilder) {
5973
extendedProperties = extendProperties(properties);
6074
var apiKey = extendedProperties.get(API_KEY_PROP_NAME);
6175
var apiSecret = extendedProperties.get(API_SECRET_PROP_NAME);
@@ -74,10 +88,13 @@ public DefaultClient(Properties properties) {
7488

7589
this.apiSecret = apiSecret.toString();
7690
this.apiKey = apiKey.toString();
77-
this.retrofit = buildRetrofitClient();
91+
this.serviceFactoryBuilder = serviceFactoryBuilder;
92+
93+
this.retrofit = buildRetrofitClient(buildOkHttpClient());
94+
this.serviceFactory = serviceFactoryBuilder.apply(retrofit);
7895
}
7996

80-
private Retrofit buildRetrofitClient() {
97+
private OkHttpClient buildOkHttpClient() {
8198
OkHttpClient.Builder httpClient =
8299
new OkHttpClient.Builder()
83100
.connectionPool(new ConnectionPool(5, 59, TimeUnit.SECONDS))
@@ -91,18 +108,33 @@ private Retrofit buildRetrofitClient() {
91108
httpClient.addInterceptor(
92109
chain -> {
93110
Request original = chain.request();
111+
112+
// Check for user token tag
113+
UserToken userToken = original.tag(UserToken.class);
114+
94115
HttpUrl url = original.url().newBuilder().addQueryParameter("api_key", apiKey).build();
95-
Request request =
116+
Request.Builder builder =
96117
original
97118
.newBuilder()
98119
.url(url)
99120
.header("Content-Type", "application/json")
100121
.header("X-Stream-Client", "stream-java-client-" + sdkVersion)
101-
.header("Stream-Auth-Type", "jwt")
102-
.header("Authorization", jwtToken(apiSecret))
103-
.build();
104-
return chain.proceed(request);
122+
.header("Stream-Auth-Type", "jwt");
123+
124+
if (userToken != null) {
125+
// User token present - use user auth
126+
builder.header("Authorization", userToken.value());
127+
} else {
128+
// Server-side auth
129+
builder.header("Authorization", jwtToken(apiSecret));
130+
}
131+
132+
return chain.proceed(builder.build());
105133
});
134+
return httpClient.build();
135+
}
136+
137+
private Retrofit buildRetrofitClient(OkHttpClient okHttpClient) {
106138
final ObjectMapper mapper = new ObjectMapper();
107139
// Use field-based serialization but respect @JsonProperty and @JsonAnyGetter annotations
108140
mapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.NONE);
@@ -117,10 +149,9 @@ private Retrofit buildRetrofitClient() {
117149
Retrofit.Builder builder =
118150
new Retrofit.Builder()
119151
.baseUrl(getStreamChatBaseUrl(extendedProperties))
152+
.client(okHttpClient)
120153
.addConverterFactory(new QueryConverterFactory())
121154
.addConverterFactory(JacksonConverterFactory.create(mapper));
122-
builder.client(httpClient.build());
123-
124155
return builder.build();
125156
}
126157

@@ -130,6 +161,12 @@ public <TService> TService create(Class<TService> svcClass) {
130161
return retrofit.create(svcClass);
131162
}
132163

164+
@Override
165+
@NotNull
166+
public <TService> TService create(Class<TService> svcClass, String userToken) {
167+
return serviceFactory.create(svcClass, new UserToken(userToken));
168+
}
169+
133170
@NotNull
134171
public String getApiSecret() {
135172
return apiSecret;
@@ -143,7 +180,8 @@ public String getApiKey() {
143180
public void setTimeout(@NotNull Duration timeoutDuration) {
144181
extendedProperties.setProperty(
145182
API_TIMEOUT_PROP_NAME, Long.toString(timeoutDuration.toMillis()));
146-
this.retrofit = buildRetrofitClient();
183+
this.retrofit = buildRetrofitClient(buildOkHttpClient());
184+
this.serviceFactory = serviceFactoryBuilder.apply(retrofit);
147185
}
148186

149187
private static @NotNull String jwtToken(String apiSecret) {

0 commit comments

Comments
 (0)