sdk-for-web
sdk-for-web copied to clipboard
๐ Feature: Allow passing in custom fetch imp
๐ Feature description
In the constructor of the client, it would be nice for users to be able to provide their own fetch implementations.
๐ค Pitch
Why?
Because frameworks like SvelteKit, allow prefetching data on the server and exposing the promise to the client. For this to work and to avoid re-fetching twice (on the server and the client), the appwrite SDK client needs to be able to receive the custom sveltekit fetch implementation. https://svelte.dev/docs/kit/load#Making-fetch-requests
๐ Have you spent some time to check if this issue has been raised before?
- [x] I checked and didn't find similar issue
๐ข Have you read the Code of Conduct?
- [x] I have read the Code of Conduct
I'm open to creating a PR for this if the feature is desired.
@Fractal-Tess i like the idea, are there any other implementations where this would be beneficial to the user than the one Svelte provides?
I would also like you to checkout our sdk generator repository. This repo is read only and any changes introduced are first made over there.
@Fractal-Tess i like the idea, are there any other implementations where this would be beneficial to the user than the one Svelte provides?
I would also like you to checkout our sdk generator repository. This repo is read only and any changes introduced are first made over there.
I'm building a side project in Angular v20 and wanted to implement a custom HTTP cache for API requests. However, I noticed that the AppWrite web SDK's internal Client class uses the standard fetch method for HTTP calls, while Angular applications typically use Angular's HttpClient for requests.
To better integrate with Angular's ecosystem (and leverage features like interceptors and signals), I've created a custom implementation of the AppWrite Client class that uses Angular's HttpClient instead of fetch. I've started by writing a simple cache interceptor for Angular's HTTP requests, but I'd like to have a similar implementation internally the appwrite web sdk.
What do you think?
Here is the simple Client implementation:
@Injectable({
providedIn: 'root',
})
class WrapClient extends Client {
private readonly _httpClient = inject(HttpClient);
constructor() {
super();
this.setEndpoint(appwriteConfig.endpoint); // Set your Appwrite endpoint
this.setProject(appwriteConfig.projectId); // Set your Appwrite project ID
}
override async call(
method: string,
url: URL,
headers?: { [key: string]: string },
params?: Payload,
responseType?: string,
): Promise<any> {
const { uri, options } = this.prepareRequest(method, url, headers, params);
try {
// Create HttpHeaders from options headers
let httpHeaders = new HttpHeaders();
if (options.headers) {
Object.entries(options.headers).forEach(([key, value]) => {
httpHeaders = httpHeaders.set(key, value as string);
});
}
let response: HttpResponse<any>;
// Handle different response types
if (responseType === 'arrayBuffer') {
response = await firstValueFrom(
this._httpClient.request(method.toUpperCase(), uri, {
headers: httpHeaders,
body: options.body,
responseType: 'arraybuffer',
observe: 'response',
withCredentials: options.credentials === 'include',
}),
);
} else {
response = await firstValueFrom(
this._httpClient.request<any>(method.toUpperCase(), uri, {
headers: httpHeaders,
body: options.body,
responseType: 'json',
observe: 'response',
withCredentials: options.credentials === 'include',
}),
);
}
// Handle warnings from response headers
const warnings = response.headers.get('x-appwrite-warning');
if (warnings) {
warnings
.split(';')
.forEach((warning: string) => console.warn('Warning: ' + warning));
}
// Handle cookie fallback
const cookieFallback = response.headers.get('X-Fallback-Cookies');
if (
typeof window !== 'undefined' &&
window.localStorage &&
cookieFallback
) {
window.console.warn(
'Appwrite is using localStorage for session management. Increase your security by adding a custom domain as your API endpoint.',
);
window.localStorage.setItem('cookieFallback', cookieFallback);
}
// Return appropriate data based on response type
if (responseType === 'arrayBuffer') {
return response.body;
}
if (response.headers.get('content-type')?.includes('application/json')) {
return response.body;
}
// For text responses, we need to handle them differently
const textResponse = await firstValueFrom(
this._httpClient.request(method.toUpperCase(), uri, {
headers: httpHeaders,
body: options.body,
responseType: 'text',
observe: 'response',
withCredentials: options.credentials === 'include',
}),
);
return {
message: textResponse.body,
};
} catch (error) {
if (error instanceof HttpErrorResponse) {
// Handle HTTP errors
const contentType = error.headers.get('content-type');
if (
contentType?.includes('application/json') ||
responseType === 'arrayBuffer'
) {
throw new AppwriteException(
error.error?.message || error.message,
error.status,
error.error?.type,
JSON.stringify(error.error),
);
}
throw new AppwriteException(
error.error?.message || error.message,
error.status,
error.error?.type,
error.error?.message || error.message,
);
}
// Handle CORS or other network errors
if (error instanceof Error && error.message.includes('CORS')) {
throw new AppwriteException(
`Invalid Origin. Register your new client (${window.location.host}) as a new Web platform on your project console dashboard`,
403,
'forbidden',
'',
);
}
// Re-throw other errors
throw error;
}
}
}
This can be used as a cache interceptor:
const cache = new Map<
string,
{ response: HttpResponse<any>; timestamp: number }
>();
export function cacheInterceptor(
req: HttpRequest<any>,
next: HttpHandlerFn,
): Observable<HttpEvent<any>> {
debugger;
const cacheKey = `${req.method}:${req.urlWithParams}`;
// Check if the request is cached
if (cache.has(cacheKey)) {
const cachedResponse = cache.get(cacheKey)!;
const isExpired = Date.now() - cachedResponse.timestamp > 10000; // 10 seconds
if (!isExpired) {
return of(cachedResponse.response.clone());
} else {
cache.delete(cacheKey); // Remove expired cache
}
}
// Proceed with the request
return next(req).pipe(
tap((event) => {
if (event instanceof HttpResponse) {
cache.set(cacheKey, { response: event, timestamp: Date.now() });
}
}),
);
}