chrome.tabs.sendMessage - why callback fails? - javascript

I have been experimenting with messaging APIs in Chrome extensions. I wanted to send a message from the background script to the content script and get some response back. I prepared a function just for that. Here are the two versions of that funcion:
V1:
public static async sendToContentScript<R>(tabId: number, command: CommandToContent): Promise<R> {
const payload: CommandBody = { command };
return await chrome.tabs.sendMessage(tabId, payload);
}
V2:
public static async sendToContentScript<R>(tabId: number, command: CommandToContent): Promise<R> {
return new Promise((resolve, _) => {
const payload: CommandBody = { command };
chrome.runtime.sendMessage(payload, function (response: R) {
resolve(response);
return true;
});
});
}
Version V1 works, while versions V2 does not. It throws the following error:
Unchecked runtime.lastError: Could not establish connection. Receiving end does not exist.
I've read a few posts about that error and it's not the first time that I see it. However, I did not find any explanation that would really "click" for me, I basically don't understand why V1 works and V2 does not. It would be great if you could help me understand that.

Related

How to feed a String to a Google Cloud Function as input?

I need a Google Cloud Function that I can call from my Android app. The function should call Vision API Safe Search and return the results to the app.
My problem: The function I have is simple and it works. But ONLY from the Cloud Test-Console. Not from the android app. When I call the function from my app, with the exact same JSON as input, it says FirebaseFunctionsException: INVALID_ARGUMENT
Function:
exports.starte = async (req, res) => {
try {
let pfad = req.body.pfad;
console.log('Input is: ' + pfad);
// Imports the Google Cloud client libraries
const vision = require('#google-cloud/vision');
// Creates a client
const client = new vision.ImageAnnotatorClient();
const visionRequest = {
features: [
{ type: 'SAFE_SEARCH_DETECTION' },
],
image: { source: { imageUri: pfad } },
};
const [result] = await client.annotateImage(visionRequest);
const detections = result.safeSearchAnnotation;
console.log('Ergebnis: ' + result);
console.log('Ergebnis: ' + detections);
res.status(200).send(detections);
} catch(err) {
console.log('Error: ' + err.message);
const errorJSON = { message: 'unknown' };
res.status(400).send(errorJSON);
}
};
Android code:
Map<String, Object> data = new HashMap<>();
data.put("pfad", path);
FirebaseFunctions.getInstance()
.getHttpsCallable("nsfw_test2")
.call(data)
.continueWith(new Continuation<HttpsCallableResult, String>() {
#Override
public String then(#NonNull Task<HttpsCallableResult> task) throws Exception {
try {
Object result = task.getResult().getData();
Log.v(TAG, "CloudFunktion: Worked!");
return result.toString();
} catch (Exception e) {
e.printStackTrace();
Log.v(TAG, "CloudFunktion: Failed!");
return e.getMessage();
}
}
});
Example JSON input that works in the Cloud Console, but not in Android:
{"pfad":"gs:\/\/xxx.appspot.com\/photos\/xxx"}
Error I get in Android:
com.google.android.gms.tasks.RuntimeExecutionException: com.google.firebase.functions.FirebaseFunctionsException: INVALID_ARGUMENT
at com.google.android.gms.tasks.zzw.getResult(com.google.android.gms:play-services-tasks##18.0.1:3)
When I check the logs in Google Cloud, it logs: Input is: undefined (When running from the app)
When I run it from the Cloud Console instead, it works and prints the path I passed.
I have absolutely no idea why this happens. I have now checked easily 30 websites and stackoverflow questions on this. To me it seems that my Android code is correct.
But why doesn't the function read the input then? Why is the input undefined? The input in the android app is not null, I log the input before I pass it in, and it is the correct file.
According to this document, the invalid argument is because we failed to specify an argument correctly:
public static final FirebaseFunctionsException.Code INVALID_ARGUMENT
Client specified an invalid argument. Note that this differs from
FAILED_PRECONDITION. INVALID_ARGUMENT indicates arguments that are
problematic regardless of the state of the system (e.g., an invalid
field name).
You need to create callable functions, instead of using an HTTP triggered Cloud Function.
The Callable functions require to have the following format:
exports.addMessage = functions.https.onCall((data, context) => {
// ...
});
It's not the same as an HTTP triggered Cloud Function (which is what you are using). You can still access it if you use an HTTP request from the Android app, but you need to use a Callable Cloud Function.

Socket hangup due to wrong handling of promises

I have script to move data from one platform to another. The source db allows only 100 records to be fetched in a single request. So I created a routine to fetch by batches of 100 which works fine I guess.
Now I try to process each records of 100 and do the necessary transformations (which involves axios call to get certain data) and create a record in firebase firestore.
Now when I run this migration in firebase express node, I get socket hang up ECONNRESET.
I know this is caused by wrong handling of promises.
Here is what my code looks like:
import { scrollByBatches } from "../helpers/migrations/apiScroll";
import { createServiceLocation } from "../helpers/locations";
const mapServiceLocationData = async (serviceLocation: any, env: string) => {
try {
const migratedServiceLocation: any = {
isMigrated: true,
id: serviceLocation._id,
};
if (serviceLocation.list?.length) {
await Promise.all(serviceLocation.ids.map(async (id: string) => {
const { data } = await dbEndPoint.priceMultiplier({ id }); // error says socket hangup on this call
let multiplierUnit;
let serviceType;
if (data.response._id) {
multiplierUnit = data.response;
const result = await dbEndPoint.serviceType({ id: multiplierUnit.service_custom_service_type }); // error says socket hangup on this call
if (result.data.response._id) {
serviceType = result.data.response.type_text;
migratedServiceLocation.logs = [...multiplierUnit.history_list_text, ...migratedServiceLocation.logs];
}
}
}));
}
await createServiceLocation(migratedServiceLocation); // create record in destination db
} catch (error) {
console.log("Error serviceLocation: ", serviceLocation._id, JSON.stringify(error));
}
return null; // is this even necessary?
};
export const up = async () => {
try {
// get 100 docs from source db => process it.. => fetch next 100 => so on...
await scrollByBatches(dbEndPoint.serviceLocation, async (serviceLocations: any) => {
await Promise.all(
serviceLocations.map(async (serviceLocation: any) => {
await mapServiceLocationData(serviceLocation);
})
);
}, 100);
} catch (error) {
console.log("Error", JSON.stringify(error));
}
return null; // is this even necessary?
};
The error I get in firebase functions console is:
For clarity on how the fetch by batches looks like:
const iterateInBatches = async (endPoint: any, limit: number, cursor: number, callback: any, resolve: any, reject: any) => {
try {
const result = await endPoint({ limit, cursor });
const { results, remaining }: any = result.data.response;
if (remaining >= 0) {
await callback(results);
}
if ((remaining)) {
setTimeout(() => {
iterateInBatches(endPoint, limit, (cursor + limit), callback, resolve, reject);
}, 1000); // wait a second
} else {
resolve();
}
} catch (err) {
reject(err);
}
};
export const scrollByBatches = async (endPoint: any, callback: any, limit: number, cursor: number = 0) => {
return new Promise((resolve, reject) => {
iterateInBatches(endPoint, limit, cursor, callback, resolve, reject);
});
};
What am I doing wrong? I have added comments in the code sections for readability.
Thanks.
There are two cases when socket hang up gets thrown:
When you are a client
When you, as a client, send a request to a remote server, and receive no timely response. Your socket is ended which throws this error. You should catch this error and decide how to handle it: whether to retry the request, queue it for later, etc.
When you are a server/proxy
When you, as a server, perhaps a proxy server, receive a request from a client, then start acting upon it (or relay the request to the upstream server), and before you have prepared the response, the client decides to cancel/abort the request.
I would suggest a number of possibilities for you to try and test that might help you solve your issue of ECONNRESET :
If you have access to the source database, you could try looking
there for some logs or metrics. Perhaps you are overloading the
service.
Quick and dirty solution for development: Use longjohn, you get long
stack traces that will contain the async operations. Clean and
correct solution: Technically, in node, whenever you emit an 'error'
event and no one listens to it, it will throw the error. To make it
not throw, put a listener on it and handle it yourself. That way you
can log the error with more information.
You can also set NODE_DEBUG=net or use strace. They both provide you
what the node is doing internally.
You could restart your server and run the connection again, maybe
your server crashed or refused the connection most likely blocked by
the User Agent.
You could also try running this code locally, instead of in cloud
functions to see if there is a different result. It's possible that
the RSG/google network is interfering somehow.
You can also have a look at this GitHub issue and stackoverflow
thread to see the common fixes for the ECONNRESET issue and see if
those help resolve the issue.

Web USB: An operation that changes interface state is in progress error

Suddenly I get an error on a web usb device that connects with my Angular app.
The error reads: An operation that changes interface state is in progress.
Edit: more code:
Selecting device & opening connection:
getDeviceSelector() {
return navigator.usb
.requestDevice(this.options)
.then((selectedDevice) => {
this.device = selectedDevice;
return this.device.open(); // Begin a session.
});
}
Communicating with the device (Raspberry Pi)
Start communication with the web-usb on the Pi:
connectedDevice
.then(() => this.device.selectConfiguration(1)) // Select configuration #1 for the device.
.then(() => this.device.claimInterface(0)) // Request exclusive control over interface #0.
.then(() => {
// Read data every 40 ms
this.interval = interval(40).subscribe(async () => {
await this.read();
});
})
Handle the reading of all the data that is being send:
async read() {
const result = await this.readOneLine();
this.readCallbacks.forEach((callback) => {
callback(result);
});
}
readOneLine() {
return this.device.transferIn(1, 8 * 1024).then(
(result) => {
return new Uint8Array(result.data.buffer);
},
(error) => {
console.error(error);
}
);
}
From there on, we use the readCallbacks function to pass the data we got from to device to a custom event that is been fired.
The error might be related to the new Chrome update, but I can not find changes to the navigator.usb or any other USB related mechanics.
New info will be added as soon as I have it!
In my case, the problem only occurred on some Windows laptops. I had to #1 safely remove the USB-device and #2 plug it in another port. After that, the connection was normal just like before (also on the original USB-port).
It suddenly happened on different computers and at the same time, but we were unable to see the exact environment that caused this issue.

Parse Cloud Code Defines with Parse 3.0.0

I'm trying to return values from Parse.Cloud.define functions in cloud code using Parse. I'm using Parse 3.0.0 and I can get it to return values from simple cloud code defines but not complex ones.
I'm coding client side iOS in Objective-C.
Here's the cloud code function (I don't care if this is unsafe, I'm not changing it)
Parse.Cloud.define("doStuff", (request) => {
const query = new Parse.Query(Parse.User);
query.equalTo("username", request.params.username);
query.first({useMasterKey:true})
.then((results) => {
Parse.User.requestPasswordReset(results.get("email"))
.then(() => {
return "good work";
}).catch((error) => {
});
})
.catch((error) => {
});
});
This works just fine, it sends the email to the user as expected by using the User's username field.
In iOS I'm calling it like this:
[PFCloud callFunctionInBackground:#"doStuff" withParameters:#{#"username" : cleanEntryData} block:^(NSString * object, NSError * error) {
if (!error) {
NSLog(#"success %#", object);
} else {
NSLog(#"error %#", error);
}
}];
This call works in iOS and the email is successfully sent to the user for password reset. However, here's the problem.
when I call
NSLog(#"success %#", object);
the value in Xcode debug window is
success (null)
I expect it to be
success good work
When I a simple cloud code define like so:
Parse.Cloud.define("testing", (req) => {
return "very good";
});
with iOS like so:
[PFCloud callFunctionInBackground:#"testing" withParameters:#{#"nothing" : #"nothing"} block:^(NSString * object, NSError * error) {
if (!error) {
NSLog(#"success %#", object);
} else {
}
}];
then i get the result in Xcode debugger that i'm looking for
success very good
i don't know why the "doStuff" cloud code define is not returning the string "good work" when the function is clearly executing and sending the email as it should. I've read both the Parse 3.0.0 and JS 2.0.0 guides and they aren't very descriptive on how this should work with Parse Cloud defines. I'm not a JS coder, I only code in mobile, so I'm probably doing something stupid. Any help would be great. Thanks.
There's no issue in your iOS code, the issue lies in the cloud code, so you'll need to change the cloud code, since it's not necessarily unsafe, but rather flawed.
The issue is that you are nesting Promises inside each other instead of chaining them together, hence the single nested return value is lost in the several nested layers.
Parse.Cloud.define("doStuff", (request) => {
const query = new Parse.Query(Parse.User);
query.equalTo("username", request.params.username);
return query.first({useMasterKey:true})
.then((results) => {
return Parse.User.requestPasswordReset(results.get("email"));
}).then(() => {
return "good work";
})
});

React-Native modules for Android int value returned late?

I have a native Android app that now has a React-Native part to it.
In short, i have a list view in native Android/Java. When i tap a row, i start a new Activity which leads to the React-Native code.
The components make an API call, but before it does it, my code goes through the React Native module to get a value from the User Preferences in the Java/Android settings for the header so that it can make the proper API call.
The React Native API call in /actions/index.js:
export const toggleBlocked = (user_id) =>
async dispatch => {
try {
let headers = await getHeaders();
const response = await fetch('http://my.domain.com/api/get_data?user_id=`${user_id}`', { headers, method: 'GET' });
if (response.status == 200) {
// do stuff
} else {
ToastAndroid.show("Looks like the response wasnt good :(", ToastAndroid.LONG, ToastAndroid.CENTER);
}
} catch (error) {
ToastAndroid.show("An Error Occurred :(", ToastAndroid.LONG, ToastAndroid.CENTER);
}
};
function getHeaders() {
let userId = UserService.getUserId()
console.log("2. React-Native | getHeaders... user_id is " + userId)
return (
new Headers({
Cookie: `abcxyz=${userId}`,
user_id: userId,
"User-Agent": "android",
"Content-Type": "application/json",
charset: "utf-8",
"app-version": "9.1.23",
Accept: "application/json"
})
)
}
Back in native Android/Java, i have my modules and packages set up. Here is UserServiceModule.java:
#ReactMethod
public int getUserId() {
SharedPreferences settings = mContext.getSharedPreferences(Constants.SettingsPreference, Context.MODE_PRIVATE);
int userId = settings.getUserId();
Log.d(TAG, "1. Java/Android code. UserID is " + userId);
return userId;
}
When i run the code, user_id is always null. I even expected my logs to show in order (see the numbered logs), but they're reverse??
"1. Java/Android code. UserID is 123"
"2. React-Native | getHeaders... user_id is 123"
Any insights would be great please. Thanks.
UPDATE / SOLUTION
Here is the updated changes to get things working correctly:
#ReactMethod
public void getUserId(final Promise promise) {
SharedPreferences settings = mContext.getSharedPreferences(Constants.SettingsPreference, Context.MODE_PRIVATE);
int userId = settings.getUserId();
Log.d(TAG, "1. Java/Android code. UserID is " + userId);
promise.resolve(Integer.toString(userId));
}
#ReactMethod functions are always suppose to return void and are asynchronous. The reason you are getting null is because your JS code is written synchronously. It initializes the variable userId as null and sets it with the return value of the asynchronous call. But because the code you written was synchronous, it will go to the next line before the react method comes back with the value.
You will need to do an async await like you did with your dispatch method. However that won't be enough as react methods return a void value. Quite frankly I don't know how a method not found error was not triggered instead. Perhaps #ReactMethod ignores the return value you defined in Java?
Regardless, after setting up the async await. Do something similar to what they did here:
https://facebook.github.io/react-native/docs/native-modules-android.html#callbacks
Set isBlockingSynchronousMethod = true in #ReactMethod
example:
#ReactMethod(isBlockingSynchronousMethod = true)
public String getString(){
return "awesome string";
}
in js file:
var someString = await NativeModule.ModuleName.getString();

Categories

Resources