Skip to content

Commit

Permalink
Merge pull request #979 from appwrite/feat-react-native-multipart
Browse files Browse the repository at this point in the history
feat: react native multipart
  • Loading branch information
loks0n authored Oct 1, 2024
2 parents c8629aa + 025ea7c commit 0284ff0
Show file tree
Hide file tree
Showing 21 changed files with 487 additions and 155 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,8 @@ jobs:
Deno1303,
DotNet60,
DotNet80,
FlutterStable,
FlutterBeta,
FlutterStable,
Go122,
KotlinJava8,
KotlinJava11,
Expand Down
2 changes: 1 addition & 1 deletion mock-server/app/http.php
Original file line number Diff line number Diff line change
Expand Up @@ -387,7 +387,7 @@
});

App::get('/v1/mock/tests/general/multipart')
->alias('/v1/mock/tests/general/multipartcomplied')
->alias('/v1/mock/tests/general/multipart-compiled')
->desc('Multipart')
->groups(['mock'])
->label('scope', 'public')
Expand Down
2 changes: 0 additions & 2 deletions mock-server/docker-compose.yml
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
services:
mockapi:
container_name: mockapi
ports:
- 8080:80
build:
context: .
args:
Expand Down
17 changes: 14 additions & 3 deletions src/SDK/Language/ReactNative.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,6 @@

namespace Appwrite\SDK\Language;

use Twig\TwigFilter;

class ReactNative extends Web
{
/**
Expand Down Expand Up @@ -65,6 +63,16 @@ public function getFiles(): array
'destination' => 'src/query.ts',
'template' => 'react-native/src/query.ts.twig',
],
[
'scope' => 'default',
'destination' => 'src/payload.ts',
'template' => 'react-native/src/payload.ts.twig',
],
[
'scope' => 'default',
'destination' => 'src/multipart.ts',
'template' => 'react-native/src/multipart.ts.twig',
],
[
'scope' => 'default',
'destination' => 'README.md',
Expand Down Expand Up @@ -145,8 +153,9 @@ public function getTypeName(array $parameter, array $spec = []): string
return $this->getTypeName($parameter['array']) . '[]';
}
return 'string[]';
case self::TYPE_PAYLOAD:
case self::TYPE_FILE:
return '{name: string, type: string, size: number, uri: string}';
return 'Payload';
}

return $parameter['type'];
Expand Down Expand Up @@ -179,6 +188,7 @@ public function getParamExample(array $param): string
case self::TYPE_OBJECT:
$output .= '{}';
break;
case self::TYPE_PAYLOAD:
case self::TYPE_FILE:
$output .= "await pickSingle()";
break;
Expand All @@ -197,6 +207,7 @@ public function getParamExample(array $param): string
case self::TYPE_STRING:
$output .= "'{$example}'";
break;
case self::TYPE_PAYLOAD:
case self::TYPE_FILE:
$output .= "await pickSingle()";
break;
Expand Down
6 changes: 3 additions & 3 deletions templates/deno/src/client.ts.twig
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { {{ spec.title | caseUcfirst}}Exception } from './exception.ts';
import { Payload } from './payload.ts';
import * as multipart from './multipart.ts';
import { getBoundary, parse as parseMultipart } from './multipart.ts';

export interface Params {
[key: string]: any;
Expand Down Expand Up @@ -128,12 +128,12 @@ export class Client {
}

if (response.headers.get('content-type')?.includes('multipart/form-data')) {
const boundary = multipart.getBoundary(
const boundary = getBoundary(
response.headers.get("content-type") || ""
);

const body = new Uint8Array(await response.arrayBuffer());
const parts = multipart.parse(body, boundary);
const parts = parseMultipart(body, boundary);
const partsObject: { [key: string]: any } = {};

for (const part of parts) {
Expand Down
12 changes: 4 additions & 8 deletions templates/node/src/client.ts.twig
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { fetch, FormData, Blob } from 'node-fetch-native-with-agent';
import { getBoundary, parse as parseMultipart} from 'parse-multipart-data';
import { createAgent } from 'node-fetch-native-with-agent/agent';
import { Models } from './models';
import { Payload } from './payload';
import * as multipart from 'parse-multipart-data';

type Params = {
[key: string]: any;
Expand Down Expand Up @@ -285,15 +285,11 @@ class Client {
} else if (responseType === 'arrayBuffer') {
data = await response.arrayBuffer();
} else if (response.headers.get('content-type')?.includes('multipart/form-data')) {
const chunks = [];
for await (const chunk of (response.body as AsyncIterable<any>)) {
chunks.push(chunk instanceof Buffer ? chunk : Buffer.from(chunk));
}
const body = Buffer.concat(chunks);
const boundary = multipart.getBoundary(
const body = await response.arrayBuffer();
const boundary = getBoundary(
response.headers.get("content-type") || ""
);
const parts = multipart.parse(body, boundary);
const parts = parseMultipart(Buffer.from(body), boundary);
const partsObject: { [key: string]: any } = {};

for (const part of parts) {
Expand Down
2 changes: 1 addition & 1 deletion templates/react-native/package.json.twig
Original file line number Diff line number Diff line change
Expand Up @@ -26,14 +26,14 @@
},
"devDependencies": {
"@rollup/plugin-typescript": "8.3.2",
"playwright": "1.15.0",
"rollup": "2.75.4",
"serve-handler": "6.1.0",
"tslib": "2.4.0",
"typescript": "4.7.2"
},
"dependencies": {
"expo-file-system": "16.0.8",
"parse-multipart-data": "^1.5.0",
"react-native": "^0.73.6"
},
"peerDependencies": {
Expand Down
43 changes: 38 additions & 5 deletions templates/react-native/src/client.ts.twig
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
import { Models } from './models';
import { Service } from './service';
import { Platform } from 'react-native';
import { getBoundary, parse as parseMultipart} from './multipart';
import { Service } from './service';
import { Payload } from './payload';
import { Models } from './models';


type Payload = {
type Params = {
[key: string]: any;
}

Expand Down Expand Up @@ -345,7 +348,7 @@ class Client {
}
}

async call(method: string, url: URL, headers: Headers = {}, params: Payload = {}): Promise<any> {
async call(method: string, url: URL, headers: Headers = {}, params: Params = {}): Promise<any> {
method = method.toUpperCase();

headers = Object.assign({}, this.headers, headers);
Expand Down Expand Up @@ -397,6 +400,36 @@ class Client {

if (response.headers.get('content-type')?.includes('application/json')) {
data = await response.json();
} else if (response.headers.get('content-type')?.includes('multipart/form-data')) {
const boundary = getBoundary(
response.headers.get("content-type") || ""
);

const body = new Uint8Array(await response.arrayBuffer());
const parts = parseMultipart(body, boundary);
const partsObject: { [key: string]: any } = {};

for (const part of parts) {
if (!part.name) {
continue;
}
if (part.name === "responseBody") {
partsObject[part.name] = Payload.fromBinary(part.data, part.filename);
} else if (part.name === "responseStatusCode") {
partsObject[part.name] = parseInt(part.data.toString());
} else if (part.name === "duration") {
partsObject[part.name] = parseFloat(part.data.toString());
} else if (part.type === 'application/json') {
try {
partsObject[part.name] = JSON.parse(part.data.toString());
} catch (e) {
throw new Error(`Error parsing JSON for part ${part.name}: ${e instanceof Error ? e.message : 'Unknown error'}`);
}
} else {
partsObject[part.name] = part.data.toString();
}
}
data = partsObject;
} else {
data = {
message: await response.text()
Expand Down Expand Up @@ -425,4 +458,4 @@ class Client {
}

export { Client, {{spec.title | caseUcfirst}}Exception };
export type { Models, Payload };
export type { Models, Params };
3 changes: 2 additions & 1 deletion templates/react-native/src/index.ts.twig
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,13 @@ export { Client, {{spec.title | caseUcfirst}}Exception } from './client';
{% for service in spec.services %}
export { {{service.name | caseUcfirst}} } from './services/{{service.name | caseDash}}';
{% endfor %}
export type { Models, Payload, RealtimeResponseEvent, UploadProgress } from './client';
export type { Models, Params, RealtimeResponseEvent, UploadProgress } from './client';
export type { QueryTypes, QueryTypesList } from './query';
export { Query } from './query';
export { Permission } from './permission';
export { Role } from './role';
export { ID } from './id';
export { Payload } from './payload';
{% for enum in spec.enums %}
export { {{ enum.name | caseUcfirst }} } from './enums/{{enum.name | caseDash}}';
{% endfor %}
2 changes: 2 additions & 0 deletions templates/react-native/src/models.ts.twig
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { Payload } from './payload';

export namespace Models {
{% for definition in spec.definitions %}
/**
Expand Down
Loading

0 comments on commit 0284ff0

Please sign in to comment.