Skip to content

Commit a3998d8

Browse files
Merge pull request #947 from appwrite/fix-sync-node-and-web
fix: sync node and web
2 parents 947e439 + e9cd680 commit a3998d8

File tree

3 files changed

+197
-202
lines changed

3 files changed

+197
-202
lines changed

src/SDK/Language/Web.php

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -252,9 +252,11 @@ public function getGenerics(string $model, array $spec, bool $skipFirst = false)
252252
public function getReturn(array $method, array $spec): string
253253
{
254254
if ($method['type'] === 'webAuth') {
255-
return 'void | URL';
256-
} elseif ($method['type'] === 'location') {
257-
return 'URL';
255+
return 'Promise<void | string>';
256+
}
257+
258+
if ($method['type'] === 'location') {
259+
return 'string';
258260
}
259261

260262
if (array_key_exists('responseModel', $method) && !empty($method['responseModel']) && $method['responseModel'] !== 'any') {

templates/web/src/client.ts.twig

Lines changed: 126 additions & 71 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
import { Models } from './models';
2-
import { Service } from './service';
32

43
/**
54
* Payload type representing a key-value pair with string keys and any values.
@@ -48,7 +47,7 @@ type RealtimeRequest = {
4847
/**
4948
* Realtime event response structure with generic payload type.
5049
*/
51-
export type RealtimeResponseEvent<T extends unknown> = {
50+
type RealtimeResponseEvent<T extends unknown> = {
5251
/**
5352
* List of event names associated with the response.
5453
*/
@@ -215,7 +214,7 @@ type Realtime = {
215214
/**
216215
* Type representing upload progress information.
217216
*/
218-
export type UploadProgress = {
217+
type UploadProgress = {
219218
/**
220219
* Identifier for the upload progress.
221220
*/
@@ -284,17 +283,18 @@ class {{spec.title | caseUcfirst}}Exception extends Error {
284283
* Client that handles requests to {{spec.title | caseUcfirst}}
285284
*/
286285
class Client {
286+
static CHUNK_SIZE = 1024 * 1024 * 5;
287+
287288
/**
288289
* Holds configuration such as project.
289290
*/
290291
config = {
291292
endpoint: '{{ spec.endpoint }}',
292293
endpointRealtime: '',
293-
{% for header in spec.global.headers %}
294+
{%~ for header in spec.global.headers %}
294295
{{ header.key | caseLower }}: '',
295-
{% endfor %}
296+
{%~ endfor %}
296297
};
297-
298298
/**
299299
* Custom headers for API requests.
300300
*/
@@ -303,9 +303,9 @@ class Client {
303303
'x-sdk-platform': '{{ sdk.platform }}',
304304
'x-sdk-language': '{{ language.name | caseLower }}',
305305
'x-sdk-version': '{{ sdk.version }}',
306-
{% for key,header in spec.global.defaultHeaders %}
306+
{%~ for key,header in spec.global.defaultHeaders %}
307307
'{{key}}': '{{header}}',
308-
{% endfor %}
308+
{%~ endfor %}
309309
};
310310

311311
/**
@@ -337,14 +337,14 @@ class Client {
337337
return this;
338338
}
339339

340-
{% for header in spec.global.headers %}
340+
{%~ for header in spec.global.headers %}
341341
/**
342342
* Set {{header.key | caseUcfirst}}
343343
*
344-
{% if header.description %}
345-
{{header.description|comment2}}
344+
{%~ if header.description %}
345+
* {{header.description}}
346346
*
347-
{% endif %}
347+
{%~ endif %}
348348
* @param value string
349349
*
350350
* @return {this}
@@ -354,8 +354,7 @@ class Client {
354354
this.config.{{ header.key | caseLower }} = value;
355355
return this;
356356
}
357-
358-
{% endfor %}
357+
{%~ endfor %}
359358

360359
private realtime: Realtime = {
361360
socket: undefined,
@@ -540,40 +539,18 @@ class Client {
540539
}
541540
}
542541

543-
/**
544-
* Call API endpoint with the specified method, URL, headers, and parameters.
545-
*
546-
* @param {string} method - HTTP method (e.g., 'GET', 'POST', 'PUT', 'DELETE').
547-
* @param {URL} url - The URL of the API endpoint.
548-
* @param {Headers} headers - Custom headers for the API request.
549-
* @param {Payload} params - Request parameters.
550-
* @returns {Promise<any>} - A promise that resolves with the response data.
551-
*
552-
* @typedef {Object} Payload - Request payload data.
553-
* @property {string} key - The key.
554-
* @property {string} value - The value.
555-
*/
556-
async call(method: string, url: URL, headers: Headers = {}, params: Payload = {}): Promise<any> {
542+
prepareRequest(method: string, url: URL, headers: Headers = {}, params: Payload = {}): { uri: string, options: RequestInit } {
557543
method = method.toUpperCase();
558544

559-
560545
headers = Object.assign({}, this.headers, headers);
561546

562547
let options: RequestInit = {
563548
method,
564549
headers,
565-
credentials: 'include'
566550
};
567551

568-
if (typeof window !== 'undefined' && window.localStorage) {
569-
const cookieFallback = window.localStorage.getItem('cookieFallback');
570-
if (cookieFallback) {
571-
headers['X-Fallback-Cookies'] = cookieFallback;
572-
}
573-
}
574-
575552
if (method === 'GET') {
576-
for (const [key, value] of Object.entries(Service.flatten(params))) {
553+
for (const [key, value] of Object.entries(Client.flatten(params))) {
577554
url.searchParams.append(key, value);
578555
}
579556
} else {
@@ -583,15 +560,17 @@ class Client {
583560
break;
584561

585562
case 'multipart/form-data':
586-
let formData = new FormData();
587-
588-
for (const key in params) {
589-
if (Array.isArray(params[key])) {
590-
params[key].forEach((value: any) => {
591-
formData.append(key + '[]', value);
592-
})
563+
const formData = new FormData();
564+
565+
for (const [key, value] of Object.entries(params)) {
566+
if (value instanceof File) {
567+
formData.append(key, value, value.name);
568+
} else if (Array.isArray(value)) {
569+
for (const nestedValue of value) {
570+
formData.append(`${key}[]`, nestedValue);
571+
}
593572
} else {
594-
formData.append(key, params[key]);
573+
formData.append(key, value);
595574
}
596575
}
597576

@@ -601,45 +580,121 @@ class Client {
601580
}
602581
}
603582

604-
try {
605-
let data = null;
606-
const response = await fetch(url.toString(), options);
583+
return { uri: url.toString(), options };
584+
}
585+
586+
async chunkedUpload(method: string, url: URL, headers: Headers = {}, originalPayload: Payload = {}, onProgress: (progress: UploadProgress) => void) {
587+
const file = Object.values(originalPayload).find((value) => value instanceof File);
588+
589+
if (file.size <= Client.CHUNK_SIZE) {
590+
return await this.call(method, url, headers, originalPayload);
591+
}
592+
593+
let start = 0;
594+
let response = null;
607595

608-
const warnings = response.headers.get('x-{{ spec.title | lower }}-warning');
609-
if (warnings) {
610-
warnings.split(';').forEach((warning: string) => console.warn('Warning: ' + warning));
596+
while (start < file.size) {
597+
let end = start + Client.CHUNK_SIZE; // Prepare end for the next chunk
598+
if (end >= file.size) {
599+
end = file.size; // Adjust for the last chunk to include the last byte
611600
}
612601

613-
if (response.headers.get('content-type')?.includes('application/json')) {
614-
data = await response.json();
615-
} else {
616-
data = {
617-
message: await response.text()
618-
};
602+
headers['content-range'] = `bytes ${start}-${end-1}/${file.size}`;
603+
const chunk = file.slice(start, end);
604+
605+
let payload = { ...originalPayload, file: new File([chunk], file.name)};
606+
607+
response = await this.call(method, url, headers, payload);
608+
609+
if (onProgress && typeof onProgress === 'function') {
610+
onProgress({
611+
$id: response.$id,
612+
progress: Math.round((end / file.size) * 100),
613+
sizeUploaded: end,
614+
chunksTotal: Math.ceil(file.size / Client.CHUNK_SIZE),
615+
chunksUploaded: Math.ceil(end / Client.CHUNK_SIZE)
616+
});
619617
}
620618

621-
if (400 <= response.status) {
622-
throw new {{spec.title | caseUcfirst}}Exception(data?.message, response.status, data?.type, data);
619+
if (response && response.$id) {
620+
headers['x-{{spec.title | caseLower }}-id'] = response.$id;
623621
}
624622

625-
const cookieFallback = response.headers.get('X-Fallback-Cookies');
623+
start = end;
624+
}
626625

627-
if (typeof window !== 'undefined' && window.localStorage && cookieFallback) {
628-
window.console.warn('{{spec.title | caseUcfirst}} is using localStorage for session management. Increase your security by adding a custom domain as your API endpoint.');
629-
window.localStorage.setItem('cookieFallback', cookieFallback);
630-
}
626+
return response;
627+
}
628+
629+
async redirect(method: string, url: URL, headers: Headers = {}, params: Payload = {}): Promise<string> {
630+
const { uri, options } = this.prepareRequest(method, url, headers, params);
631+
632+
const response = await fetch(uri, {
633+
...options,
634+
redirect: 'manual'
635+
});
636+
637+
if (response.status !== 301 && response.status !== 302) {
638+
throw new {{spec.title | caseUcfirst}}Exception('Invalid redirect', response.status);
639+
}
640+
641+
return response.headers.get('location') || '';
642+
}
631643

632-
return data;
633-
} catch (e) {
634-
if (e instanceof {{spec.title | caseUcfirst}}Exception) {
635-
throw e;
644+
async call(method: string, url: URL, headers: Headers = {}, params: Payload = {}, responseType = 'json'): Promise<any> {
645+
const { uri, options } = this.prepareRequest(method, url, headers, params);
646+
647+
let data: any = null;
648+
649+
const response = await fetch(uri, options);
650+
651+
const warnings = response.headers.get('x-{{ spec.title | lower }}-warning');
652+
if (warnings) {
653+
warnings.split(';').forEach((warning: string) => console.warn('Warning: ' + warning));
654+
}
655+
656+
if (response.headers.get('content-type')?.includes('application/json')) {
657+
data = await response.json();
658+
} else if (responseType === 'arrayBuffer') {
659+
data = await response.arrayBuffer();
660+
} else {
661+
data = {
662+
message: await response.text()
663+
};
664+
}
665+
666+
if (400 <= response.status) {
667+
throw new {{spec.title | caseUcfirst}}Exception(data?.message, response.status, data?.type, data);
668+
}
669+
670+
const cookieFallback = response.headers.get('X-Fallback-Cookies');
671+
672+
if (typeof window !== 'undefined' && window.localStorage && cookieFallback) {
673+
window.console.warn('{{spec.title | caseUcfirst}} is using localStorage for session management. Increase your security by adding a custom domain as your API endpoint.');
674+
window.localStorage.setItem('cookieFallback', cookieFallback);
675+
}
676+
677+
return data;
678+
}
679+
680+
static flatten(data: Payload, prefix = ''): Payload {
681+
let output: Payload = {};
682+
683+
for (const [key, value] of Object.entries(data)) {
684+
let finalKey = prefix ? prefix + '[' + key +']' : key;
685+
if (Array.isArray(value)) {
686+
output = { ...output, ...Client.flatten(value, finalKey) };
687+
} else {
688+
output[finalKey] = value;
636689
}
637-
throw new {{spec.title | caseUcfirst}}Exception((<Error>e).message);
638690
}
691+
692+
return output;
639693
}
640694
}
641695

642696
export { Client, {{spec.title | caseUcfirst}}Exception };
643697
export { Query } from './query';
644-
export type { Models, Payload };
698+
export type { Models, Payload, UploadProgress };
699+
export type { RealtimeResponseEvent };
645700
export type { QueryTypes, QueryTypesList } from './query';

0 commit comments

Comments
 (0)