Add Native streamed http functionality
This commit is contained in:
@@ -1,5 +1,11 @@
|
|||||||
package co.aiclient.risu;
|
package co.aiclient.risu;
|
||||||
|
import android.os.Bundle;
|
||||||
import com.getcapacitor.BridgeActivity;
|
import com.getcapacitor.BridgeActivity;
|
||||||
|
|
||||||
public class MainActivity extends BridgeActivity {}
|
public class MainActivity extends BridgeActivity {
|
||||||
|
@Override
|
||||||
|
public void onCreate(Bundle savedInstanceState) {
|
||||||
|
registerPlugin(StreamedPlugin.class);
|
||||||
|
super.onCreate(savedInstanceState);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,2 +1,119 @@
|
|||||||
package co.aiclient.risu;public class StreamedPlugin {
|
package co.aiclient.risu;
|
||||||
|
|
||||||
|
import android.util.Base64;
|
||||||
|
|
||||||
|
import com.getcapacitor.JSObject;
|
||||||
|
import com.getcapacitor.Plugin;
|
||||||
|
import com.getcapacitor.PluginCall;
|
||||||
|
import com.getcapacitor.PluginMethod;
|
||||||
|
import com.getcapacitor.annotation.CapacitorPlugin;
|
||||||
|
|
||||||
|
import org.json.JSONException;
|
||||||
|
import org.json.JSONObject;
|
||||||
|
|
||||||
|
import java.io.BufferedInputStream;
|
||||||
|
import java.io.BufferedOutputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.io.OutputStream;
|
||||||
|
import java.net.HttpURLConnection;
|
||||||
|
import java.net.URL;
|
||||||
|
import java.util.Iterator;
|
||||||
|
|
||||||
|
@CapacitorPlugin(name = "streamedFetch")
|
||||||
|
public class StreamedPlugin extends Plugin {
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@PluginMethod()
|
||||||
|
public void streamedFetch(PluginCall call) {
|
||||||
|
String id = call.getString("id");
|
||||||
|
String urlParam = call.getString("url");
|
||||||
|
String bodyString = call.getString("body");
|
||||||
|
JSObject headers = call.getObject("headers");
|
||||||
|
|
||||||
|
|
||||||
|
URL url = null;
|
||||||
|
|
||||||
|
try {
|
||||||
|
url = new URL(urlParam);
|
||||||
|
HttpURLConnection urlConnection = (HttpURLConnection) url.openConnection();
|
||||||
|
byte[] bodyEncodedByte = bodyString.getBytes("UTF-8");
|
||||||
|
byte[] bodyByte = Base64.decode(bodyEncodedByte, Base64.DEFAULT);
|
||||||
|
Iterator<String> keys = headers.keys();
|
||||||
|
urlConnection.setRequestMethod("POST");
|
||||||
|
while(keys.hasNext()) {
|
||||||
|
String key = keys.next();
|
||||||
|
if (headers.get(key) instanceof JSONObject) {
|
||||||
|
urlConnection.setRequestProperty(key, headers.getString(key));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
urlConnection.setRequestProperty("Content-Length", String.valueOf(bodyByte.length));
|
||||||
|
urlConnection.setDoInput(true);
|
||||||
|
OutputStream out = new BufferedOutputStream(urlConnection.getOutputStream());
|
||||||
|
out.write(bodyByte);
|
||||||
|
try {
|
||||||
|
InputStream in = new BufferedInputStream(urlConnection.getInputStream());
|
||||||
|
int resCode = urlConnection.getResponseCode();
|
||||||
|
JSObject resObj = new JSObject();
|
||||||
|
JSObject headerObj = new JSObject();
|
||||||
|
resObj.put("id", id);
|
||||||
|
resObj.put("type", "headers");
|
||||||
|
resObj.put("status", resCode);
|
||||||
|
|
||||||
|
int i = 0;
|
||||||
|
while (true){
|
||||||
|
String headerName = urlConnection.getHeaderFieldKey(i);
|
||||||
|
String headerValue = urlConnection.getHeaderField(i);
|
||||||
|
i++;
|
||||||
|
|
||||||
|
if(headerValue == null){
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if(headerName == null){
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
headerObj.put(headerName, headerValue);
|
||||||
|
}
|
||||||
|
resObj.put("body", headerObj);
|
||||||
|
notifyListeners("streamed_fetch", resObj);
|
||||||
|
|
||||||
|
while (true){
|
||||||
|
int ableBytes = in.available();
|
||||||
|
byte[] buf = new byte[ableBytes];
|
||||||
|
int bytesRead = in.read(buf, 0, ableBytes);
|
||||||
|
if(bytesRead == -1){
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
byte[] encodedBuf = Base64.encode(buf, Base64.DEFAULT);
|
||||||
|
JSObject obj = new JSObject();
|
||||||
|
obj.put("id", id);
|
||||||
|
obj.put("body", encodedBuf);
|
||||||
|
obj.put("type", "chunk");
|
||||||
|
notifyListeners("streamed_fetch", obj);
|
||||||
|
}
|
||||||
|
JSObject endObj = new JSObject();
|
||||||
|
endObj.put("id", id);
|
||||||
|
endObj.put("type", "end");
|
||||||
|
notifyListeners("streamed_fetch", endObj);
|
||||||
|
} finally {
|
||||||
|
urlConnection.disconnect();
|
||||||
|
}
|
||||||
|
} catch (IOException e) {
|
||||||
|
JSObject obj = new JSObject();
|
||||||
|
obj.put("error", String.valueOf(e));
|
||||||
|
call.resolve(obj);
|
||||||
|
return;
|
||||||
|
} catch (JSONException e) {
|
||||||
|
JSObject obj = new JSObject();
|
||||||
|
obj.put("error", String.valueOf(e));
|
||||||
|
call.resolve(obj);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
JSObject ret = new JSObject();
|
||||||
|
ret.put("success", true);
|
||||||
|
call.resolve(ret);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ buildscript {
|
|||||||
mavenCentral()
|
mavenCentral()
|
||||||
}
|
}
|
||||||
dependencies {
|
dependencies {
|
||||||
classpath 'com.android.tools.build:gradle:8.0.0'
|
classpath 'com.android.tools.build:gradle:8.1.3'
|
||||||
classpath 'com.google.gms:google-services:4.3.15'
|
classpath 'com.google.gms:google-services:4.3.15'
|
||||||
|
|
||||||
// NOTE: Do not place your application dependencies here; they belong
|
// NOTE: Do not place your application dependencies here; they belong
|
||||||
|
|||||||
@@ -28,6 +28,7 @@ import * as CapFS from '@capacitor/filesystem'
|
|||||||
import { save } from "@tauri-apps/api/dialog";
|
import { save } from "@tauri-apps/api/dialog";
|
||||||
import type { RisuModule } from "../process/modules";
|
import type { RisuModule } from "../process/modules";
|
||||||
import { listen } from '@tauri-apps/api/event'
|
import { listen } from '@tauri-apps/api/event'
|
||||||
|
import { registerPlugin } from '@capacitor/core';
|
||||||
|
|
||||||
//@ts-ignore
|
//@ts-ignore
|
||||||
export const isTauri = !!window.__TAURI__
|
export const isTauri = !!window.__TAURI__
|
||||||
@@ -1281,7 +1282,7 @@ export class LocalWriter{
|
|||||||
}
|
}
|
||||||
|
|
||||||
let fetchIndex = 0
|
let fetchIndex = 0
|
||||||
let tauriNativeFetchData:{[key:string]:StreamedFetchChunk[]} = {}
|
let nativeFetchData:{[key:string]:StreamedFetchChunk[]} = {}
|
||||||
|
|
||||||
interface StreamedFetchChunkData{
|
interface StreamedFetchChunkData{
|
||||||
type:'chunk',
|
type:'chunk',
|
||||||
@@ -1302,19 +1303,39 @@ interface StreamedFetchEndData{
|
|||||||
}
|
}
|
||||||
|
|
||||||
type StreamedFetchChunk = StreamedFetchChunkData|StreamedFetchHeaderData|StreamedFetchEndData
|
type StreamedFetchChunk = StreamedFetchChunkData|StreamedFetchHeaderData|StreamedFetchEndData
|
||||||
let streamedFetchListening = false
|
interface StreamedFetchPlugin {
|
||||||
|
streamedFetch(options: { id: string, url:string, body:string, headers:{[key:string]:string} }): Promise<{"error":string,"success":boolean}>;
|
||||||
|
addListener(eventName: 'streamed_fetch', listenerFunc: (data:StreamedFetchChunk) => void): void;
|
||||||
|
}
|
||||||
|
|
||||||
listen('streamed_fetch', (event) => {
|
let streamedFetchListening = false
|
||||||
try {
|
let capStreamedFetch:StreamedFetchPlugin|undefined
|
||||||
const parsed = JSON.parse(event.payload as string)
|
|
||||||
const id = parsed.id
|
if(isTauri){
|
||||||
tauriNativeFetchData[id]?.push(parsed)
|
listen('streamed_fetch', (event) => {
|
||||||
} catch (error) {
|
try {
|
||||||
console.error(error)
|
const parsed = JSON.parse(event.payload as string)
|
||||||
}
|
const id = parsed.id
|
||||||
}).then((v) => {
|
nativeFetchData[id]?.push(parsed)
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error)
|
||||||
|
}
|
||||||
|
}).then((v) => {
|
||||||
|
streamedFetchListening = true
|
||||||
|
})
|
||||||
|
}
|
||||||
|
if(Capacitor.isNativePlatform()){
|
||||||
|
capStreamedFetch = registerPlugin<StreamedFetchPlugin>('CapacitorHttp', CapacitorHttp)
|
||||||
|
|
||||||
|
capStreamedFetch.addListener('streamed_fetch', (data) => {
|
||||||
|
try {
|
||||||
|
nativeFetchData[data.id]?.push(data)
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error)
|
||||||
|
}
|
||||||
|
})
|
||||||
streamedFetchListening = true
|
streamedFetchListening = true
|
||||||
})
|
}
|
||||||
|
|
||||||
export async function fetchNative(url:string, arg:{
|
export async function fetchNative(url:string, arg:{
|
||||||
body:string,
|
body:string,
|
||||||
@@ -1326,7 +1347,7 @@ export async function fetchNative(url:string, arg:{
|
|||||||
let headers = arg.headers ?? {}
|
let headers = arg.headers ?? {}
|
||||||
const db = get(DataBase)
|
const db = get(DataBase)
|
||||||
let throughProxi = (!isTauri) && (!isNodeServer) && (!db.usePlainFetch) && (!Capacitor.isNativePlatform())
|
let throughProxi = (!isTauri) && (!isNodeServer) && (!db.usePlainFetch) && (!Capacitor.isNativePlatform())
|
||||||
if(isTauri){
|
if(isTauri || Capacitor.isNativePlatform()){
|
||||||
fetchIndex++
|
fetchIndex++
|
||||||
if(arg.signal && arg.signal.aborted){
|
if(arg.signal && arg.signal.aborted){
|
||||||
throw new Error('aborted')
|
throw new Error('aborted')
|
||||||
@@ -1335,34 +1356,49 @@ export async function fetchNative(url:string, arg:{
|
|||||||
fetchIndex = 0
|
fetchIndex = 0
|
||||||
}
|
}
|
||||||
let fetchId = fetchIndex.toString().padStart(5,'0')
|
let fetchId = fetchIndex.toString().padStart(5,'0')
|
||||||
tauriNativeFetchData[fetchId] = []
|
nativeFetchData[fetchId] = []
|
||||||
let resolved = false
|
let resolved = false
|
||||||
|
|
||||||
let error = ''
|
let error = ''
|
||||||
while(!streamedFetchListening){
|
while(!streamedFetchListening){
|
||||||
await sleep(100)
|
await sleep(100)
|
||||||
}
|
}
|
||||||
invoke('streamed_fetch', {
|
if(isTauri){
|
||||||
id: fetchId,
|
invoke('streamed_fetch', {
|
||||||
url: url,
|
id: fetchId,
|
||||||
headers: JSON.stringify(headers),
|
url: url,
|
||||||
body: arg.body,
|
headers: JSON.stringify(headers),
|
||||||
}).then((res) => {
|
body: arg.body,
|
||||||
const parsedRes = JSON.parse(res as string)
|
}).then((res) => {
|
||||||
if(!parsedRes.success){
|
const parsedRes = JSON.parse(res as string)
|
||||||
error = parsedRes.body
|
if(!parsedRes.success){
|
||||||
resolved = true
|
error = parsedRes.body
|
||||||
}
|
resolved = true
|
||||||
})
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
else if(capStreamedFetch){
|
||||||
|
capStreamedFetch.streamedFetch({
|
||||||
|
id: fetchId,
|
||||||
|
url: url,
|
||||||
|
headers: headers,
|
||||||
|
body: Buffer.from(arg.body).toString('base64'),
|
||||||
|
}).then((res) => {
|
||||||
|
if(!res.success){
|
||||||
|
error = res.error
|
||||||
|
resolved = true
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
let resHeaders:{[key:string]:string} = null
|
let resHeaders:{[key:string]:string} = null
|
||||||
let status = 400
|
let status = 400
|
||||||
|
|
||||||
const readableStream = new ReadableStream<Uint8Array>({
|
const readableStream = new ReadableStream<Uint8Array>({
|
||||||
async start(controller) {
|
async start(controller) {
|
||||||
while(!resolved || tauriNativeFetchData[fetchId].length > 0){
|
while(!resolved || nativeFetchData[fetchId].length > 0){
|
||||||
if(tauriNativeFetchData[fetchId].length > 0){
|
if(nativeFetchData[fetchId].length > 0){
|
||||||
const data = tauriNativeFetchData[fetchId].shift()
|
const data = nativeFetchData[fetchId].shift()
|
||||||
console.log(data)
|
console.log(data)
|
||||||
if(data.type === 'chunk'){
|
if(data.type === 'chunk'){
|
||||||
const chunk = Buffer.from(data.body, 'base64')
|
const chunk = Buffer.from(data.body, 'base64')
|
||||||
|
|||||||
Reference in New Issue
Block a user