1111import java .nio .charset .StandardCharsets ;
1212import java .security .Key ;
1313import 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 ;
1519import java .util .concurrent .TimeUnit ;
20+ import java .util .function .Function ;
1621import javax .crypto .spec .SecretKeySpec ;
1722import okhttp3 .ConnectionPool ;
1823import 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