From 20db7922b02bb835e21ddf74c3f8ab88867529ac Mon Sep 17 00:00:00 2001 From: dshukertjr Date: Fri, 26 Apr 2024 10:11:42 +0900 Subject: [PATCH 1/4] feat: Add SSE support to invoke method --- .../lib/src/functions_client.dart | 68 ++++++------------- .../supabase/lib/src/auth_http_client.dart | 1 + 2 files changed, 22 insertions(+), 47 deletions(-) diff --git a/packages/functions_client/lib/src/functions_client.dart b/packages/functions_client/lib/src/functions_client.dart index 9282ce00..f0330e66 100644 --- a/packages/functions_client/lib/src/functions_client.dart +++ b/packages/functions_client/lib/src/functions_client.dart @@ -3,7 +3,6 @@ import 'dart:convert'; import 'package:functions_client/src/constants.dart'; import 'package:functions_client/src/types.dart'; import 'package:http/http.dart' as http; -import 'package:http/http.dart'; import 'package:yet_another_json_isolate/yet_another_json_isolate.dart'; class FunctionsClient { @@ -52,7 +51,6 @@ class FunctionsClient { }) async { final bodyStr = body == null ? null : await _isolate.encode(body); - late final Response response; final uri = Uri.parse('$_url/$functionName'); final finalHeaders = { @@ -60,59 +58,35 @@ class FunctionsClient { if (headers != null) ...headers }; - switch (method) { - case HttpMethod.post: - response = await (_httpClient?.post ?? http.post)( - uri, - headers: finalHeaders, - body: bodyStr, - ); - break; - - case HttpMethod.get: - response = await (_httpClient?.get ?? http.get)( - uri, - headers: finalHeaders, - ); - break; - - case HttpMethod.put: - response = await (_httpClient?.put ?? http.put)( - uri, - headers: finalHeaders, - body: bodyStr, - ); - break; - - case HttpMethod.delete: - response = await (_httpClient?.delete ?? http.delete)( - uri, - headers: finalHeaders, - ); - break; - - case HttpMethod.patch: - response = await (_httpClient?.patch ?? http.patch)( - uri, - headers: finalHeaders, - body: bodyStr, - ); - break; - } + final request = http.Request(method.name, uri); + finalHeaders.forEach((key, value) { + request.headers[key] = value; + }); + if (bodyStr != null) request.body = bodyStr; + final response = await (_httpClient?.send(request) ?? request.send()); final responseType = (response.headers['Content-Type'] ?? response.headers['content-type'] ?? 'text/plain') .split(';')[0] .trim(); - final data = switch (responseType) { - 'application/json' => response.bodyBytes.isEmpty + final dynamic data; + + if (responseType == 'application/json') { + final bodyBytes = await response.stream.toBytes(); + data = bodyBytes.isEmpty ? "" - : await _isolate.decode(utf8.decode(response.bodyBytes)), - 'application/octet-stream' => response.bodyBytes, - _ => utf8.decode(response.bodyBytes), - }; + : await _isolate.decode(utf8.decode(bodyBytes)); + } else if (responseType == 'application/octet-stream') { + data = await response.stream.toBytes(); + } else if (responseType == 'text/event-stream') { + data = response.stream; + } else { + final bodyBytes = await response.stream.toBytes(); + data = utf8.decode(bodyBytes); + } + if (200 <= response.statusCode && response.statusCode < 300) { return FunctionResponse(data: data, status: response.statusCode); } else { diff --git a/packages/supabase/lib/src/auth_http_client.dart b/packages/supabase/lib/src/auth_http_client.dart index bc594d8d..1d000647 100644 --- a/packages/supabase/lib/src/auth_http_client.dart +++ b/packages/supabase/lib/src/auth_http_client.dart @@ -10,6 +10,7 @@ class AuthHttpClient extends BaseClient { @override Future send(BaseRequest request) async { + print('auth client send'); if (_auth.currentSession?.isExpired ?? false) { try { await _auth.refreshSession(); From 980d8823e9dde3fc256a530860c0fd29ce957525 Mon Sep 17 00:00:00 2001 From: dshukertjr Date: Fri, 26 Apr 2024 10:25:40 +0900 Subject: [PATCH 2/4] Add code sample in the comment docs --- .../functions_client/lib/src/functions_client.dart | 14 ++++++++++++++ packages/supabase/lib/src/auth_http_client.dart | 1 - 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/packages/functions_client/lib/src/functions_client.dart b/packages/functions_client/lib/src/functions_client.dart index f0330e66..fd2cc8cf 100644 --- a/packages/functions_client/lib/src/functions_client.dart +++ b/packages/functions_client/lib/src/functions_client.dart @@ -43,6 +43,20 @@ class FunctionsClient { /// [headers]: object representing the headers to send with the request /// /// [body]: the body of the request + /// + /// ```dart + /// // Call a standard function + /// final response = await supabase.functions.invoke('hello-world'); + /// print(response.data); + /// + /// // Listen to Server Sent Events + /// final response = await supabase.functions.invoke('sse-function'); + /// response.data + /// .transform(const Utf8Decoder()) + /// .listen((val) { + /// print(val); + /// }); + /// ``` Future invoke( String functionName, { Map? headers, diff --git a/packages/supabase/lib/src/auth_http_client.dart b/packages/supabase/lib/src/auth_http_client.dart index 1d000647..bc594d8d 100644 --- a/packages/supabase/lib/src/auth_http_client.dart +++ b/packages/supabase/lib/src/auth_http_client.dart @@ -10,7 +10,6 @@ class AuthHttpClient extends BaseClient { @override Future send(BaseRequest request) async { - print('auth client send'); if (_auth.currentSession?.isExpired ?? false) { try { await _auth.refreshSession(); From e9f2217b4ed7763ae0b54ffa061ef6095d1ccfca Mon Sep 17 00:00:00 2001 From: dshukertjr Date: Fri, 26 Apr 2024 11:12:07 +0900 Subject: [PATCH 3/4] Add test --- .../functions_client/test/custom_http_client.dart | 7 +++++++ .../functions_client/test/functions_dart_test.dart | 11 +++++++++++ 2 files changed, 18 insertions(+) diff --git a/packages/functions_client/test/custom_http_client.dart b/packages/functions_client/test/custom_http_client.dart index 3050c107..74db4b28 100644 --- a/packages/functions_client/test/custom_http_client.dart +++ b/packages/functions_client/test/custom_http_client.dart @@ -15,6 +15,13 @@ class CustomHttpClient extends BaseClient { "Content-Type": "application/json", }, ); + } else if (request.url.path.endsWith('sse')) { + return StreamedResponse( + Stream.fromIterable(['a', 'b', 'c'].map((e) => utf8.encode(e))), 200, + request: request, + headers: { + "Content-Type": "text/event-stream", + }); } else { return StreamedResponse( Stream.value(utf8.encode(jsonEncode({"key": "Hello World"}))), diff --git a/packages/functions_client/test/functions_dart_test.dart b/packages/functions_client/test/functions_dart_test.dart index f2bcce0a..89f0e867 100644 --- a/packages/functions_client/test/functions_dart_test.dart +++ b/packages/functions_client/test/functions_dart_test.dart @@ -1,3 +1,5 @@ +import 'dart:convert'; + import 'package:functions_client/src/functions_client.dart'; import 'package:functions_client/src/types.dart'; import 'package:test/test.dart'; @@ -45,5 +47,14 @@ void main() { final res = await client.invoke('function1'); expect(res.data, {'key': 'Hello World'}); }); + + test('Listen to SSE event', () async { + final res = await functionsCustomHttpClient.invoke('sse'); + expect( + res.data.transform(const Utf8Decoder()), + emitsInOrder( + ['a', 'b', 'c'], + )); + }); }); } From 96c31dc419e969ec6857a5a32f9157754d91e6f1 Mon Sep 17 00:00:00 2001 From: dshukertjr Date: Fri, 26 Apr 2024 20:47:26 +0900 Subject: [PATCH 4/4] Update the comment on data of FunctionResponse and export ByteStream --- packages/functions_client/lib/functions_client.dart | 2 ++ packages/functions_client/lib/src/types.dart | 3 +++ 2 files changed, 5 insertions(+) diff --git a/packages/functions_client/lib/functions_client.dart b/packages/functions_client/lib/functions_client.dart index 4e6e466a..3c2be314 100644 --- a/packages/functions_client/lib/functions_client.dart +++ b/packages/functions_client/lib/functions_client.dart @@ -1,4 +1,6 @@ library functions_client; +export 'package:http/http.dart' show ByteStream; + export 'src/functions_client.dart'; export 'src/types.dart'; diff --git a/packages/functions_client/lib/src/types.dart b/packages/functions_client/lib/src/types.dart index 32f45098..68d03208 100644 --- a/packages/functions_client/lib/src/types.dart +++ b/packages/functions_client/lib/src/types.dart @@ -1,6 +1,8 @@ import 'dart:convert'; import 'dart:typed_data'; +import 'package:http/http.dart'; + enum HttpMethod { get, post, @@ -14,6 +16,7 @@ class FunctionResponse { /// - 'text/plain': [String] /// - 'octet/stream': [Uint8List] /// - 'application/json': dynamic ([jsonDecode] is used) + /// - 'text/event-stream': [ByteStream] final dynamic data; final int status;