"Internal" Error When Submitting Form With Firebase onCall Function on IOS Safari - ios

I am trying to submit a form but I get an "internal" error after submit on IOS Safari. Happened on two separate devices. I'm using Firebase functions onCall function. Client code:
var contactForm =
window.firebase.functions().httpsCallable('contactForm');
let result = await contactForm({ accountUID, foldersFilter,
firstName, lastName, email, cellNumber, dobDay, dobMonth })
And server code:
exports.contactForm = functions.https.onCall(( data, context ) => {
return contactForm.contactForm( data, context )
});
This function is called via form. The form works great on chrome, safari desktop, but for some reason it gets an internal errror sometimes when testing on IOS device. At first I thought it only happened when I was using autofill, but I've tested more and I get the same error when not using autofill too.
The confusing thing is my function code is actually never being called (I don't see any firebase function logs). Here is my console in safari:
The network connection was lost.
Fetch API cannot load https://us-central1-projectId.cloudfunctions.net/contactForm due to access control checks
Failed to load resource: The network connection was lost.
internal
Why won't this form submit on ios safari?

I fixed the issue. Turns out it has something to do with Google Cloud Functions being IPv4, and Safari requiring IPv6. I suspect this will become a bigger issue moving forward. I'm having to move all onCall Firebase functions to https triggers. In order to make https triggers work, you have to use a custom domain in Firebase hosting and rewrite to your function endpoint.
{
"hosting": {
...
"rewrites": [
{
"source": "/api/contactForm",
"function": "contactForm"
}
}
and so now instead of calling https://us-central1-projectId.cloudfunctions.net/contactForm to trigger my api. I call https://customdomain.com/api/contactForm

Related

WKWebView iOS 16 Web Share API on file: protocol

I am attempting to use navigator.share in a file running in WKWebView on iOS 16. The file is loaded into the web view using a file: protocol path like so:
uiView.loadFileURL(Bundle.main.url(forResource: "index", withExtension: "html", subdirectory: "web")!, allowingReadAccessTo: Bundle.main.bundleURL)
I have this setting on:
configuration.preferences.setValue(true, forKey: "allowFileAccessFromFileURLs")
So I am able to load file: protocol files into the web view, but I don't seem to be able to use the Web Share API. I get this error:
NotAllowedError: The request is not allowed by the user agent or the platform in the current context, possibly because the user denied permission.
However, when I point the web view at this HTTPS URL, it works:
https://mdn.github.io/dom-examples/web-share
This leads me to believe the Web Share API is not working because the HTML file loaded over the file: protocol is not viewed as secure and thus the JavaScript runtime don't treat the code on it as if it was running in a secure context.
As a result, I believe the access to the navigator.share API is forbidden.
Is there a way to configure WKWebView to allow access to secure context JS/DOM/web APIs without using a custom scheme (and adding an extra layer atop just loading my file from the bundle)?
The problem was not with the file: protocol or secure context.
I looked through WebKit source code for navigator.share and found that NotAllowedError gets returned whenever there don't seem to be conditions met for transient activation.
https://github.com/WebKit/WebKit/blob/d9bcb08521ddb86af9a713213d15488712919143/Source/WebCore/page/Navigator.cpp#L172
void Navigator::share(Document& document, const ShareData& data, Ref<DeferredPromise>&& promise)
{
if (!document.isFullyActive()) {
promise->reject(InvalidStateError);
return;
}
if (!validateWebSharePolicy(document)) {
promise->reject(NotAllowedError, "Third-party iframes are not allowed to call share() unless explicitly allowed via Feature-Policy (web-share)"_s);
return;
}
if (m_hasPendingShare) {
promise->reject(InvalidStateError, "share() is already in progress"_s);
return;
}
auto* window = this->window();
if (!window || !window->consumeTransientActivation()) {
promise->reject(NotAllowedError);
return;
}
if (!canShare(document, data)) {
promise->reject(TypeError);
return;
}
Transient activation is the thing where some web APIs will only run in response to a user gesture; the user gesture is either a part of the call stack leading up to the API call invocation or a user gesture has happened recently.
More about transient activation here:
https://developer.mozilla.org/en-US/docs/Glossary/Transient_activation
In my case I did have a user gesture there but also a WebRTC peer connection ICE gathering flow which while seemingly quick I guess lasted too long and it reset the transient activation flag preventing me from using the Web Share API at the end of the ICE gathering phase.
The fix is to prepare whatever you need for the Web Share API data payload in advance and in response to the user gesture, such as a button click, call the web API straight away.

Issue with IAccessTokenProvider on iOS devices

In my Blazor WebAssembly, I call the API to fetch some data. The user must be authenticated against the Identity Server. In the header of the request, I add the user token.
In the page, the user has to select some dropdown list and then press the submit button. On the click, the application calls the API with the following function (the result conversion is omitted).
private readonly IAccessTokenProvider _accessToken;
public APIService(HttpClient httpClient, IAccessTokenProvider accessToken)
{
_httpClient = httpClient;
_accessToken = accessToken;
}
public async Task<APIResponse> GetAttributeAsync(APIRequest apirequest)
{
var tokenResult = await _accessToken.RequestAccessToken();
tokenResult.TryGetToken(out var token);
HttpRequestMessage request = new HttpRequestMessage(
HttpMethod.Post, $"typing");
request.Headers.Authorization =
new System.Net.Http.Headers.AuthenticationHeaderValue(
"Bearer", token.Value);
// ...
return APIResponse;
}
Then, it hides the form and display the result from the API. There is a back button, that hides the result and shows again the form.
The web application is working fine as I expected. Then, I wanted to try the web application on iOS (iPhone or iPad). I fill the form, submit the request and display the data as expected. Then I click the button to display again the form and send another request: when I sent the request, the function above generates an error
Arg_NullReferenceException
If I refresh the page, the web application is working again. The behaviour is consistent in iOS with Safari, Edge and Chrome.
I guess, for some reasons, the RequestAccessToken() is not able the second time to retrieve the user token.
Have someone else had this issue? How can I fix it?
Update
After a day of testing, what I can say is that on Windows the function RequestAccessToken() finds the token every time I ask to retrieve it. On iOS devices, the application can read the token only once and then the token is removed. I don't know if it is correct, but I added a small cache in the Local Storage to save the token and reuse it.
Is it the correct way to approach the problem?

Google Home. Problem regarding configure a Lock device

Intro:
was created a Google Smart Home project
was configured a proxy server via ngrok to redirect the Google request to my local machine
I develop an IoT project that has the ability to open/close a lock. I need to implement Google integration to use the Google Assistant to control the user locks. I have implemented OAuth Server for Google. Also I have implemented some controllers to handle Google Action Intents: SYNC, QUERY and EXECUTE. Google send a request with the SYNC intent and App response a payload that contain devices list with specific settings. Instance:
{
requestId: 'requestIdOfGoogle', // contains in the request body
payload: {
agentUserId: 'userId123', // matches user id inside app system
devices: [
{
id: 1,
type: 'action.devices.types.LOCK', // device type
traits: ['action.devices.traits.LockUnlock'], // feature that has a device
name: {
name: 'Kos Lock'
},
willReportState: true,
roomHint: 'Main Door',
deviceInfo: { // Test data
manufacturer: 'smart-home-inc',
model: 'hs1234',
hwVersion: '3.2',
swVersion: '11.4'
}
}
]
}
}
Then Google send requests to my server with QUERY intent to get info about state of a devices, instance
{
requestId: 'requestIdOfGoogle', // contains in the request body
payload: {
devices: {
1: {
status: 'SUCCESS',
online: true,
isLocked: true,
// isJammed - Boolean. Whether the device is currently jammed and therefore its
// locked state cannot be determined.
isJammed: false
}
}
}
}
But after sending a response a test lock isn't configured and a user can't control one with Google Assistant.
enter image description here
I have tried to add other traits for a lock but it didn't help me. Also I have the same problem when I try to configure a Door device. But when I send to Google a Light device it works successfully. When you use the LockUnlock trait then Google Doc recommends to setup secondary user verification but it's optional.
I don't understand that do incorrect. If someone faced such a problem and solved it then could you help me, please
Prerequisites:
use node ^14.0.0
programming language - js
Touch controls are not supported for every device, and locks are not a device type that can be controlled directly. But they will still respond to voice commands.

Events not being logged in with AppEventsLogger

I'm trying to use react-native-fbsdk for Facebook analytics. When I go to the events debugging page to check if it works, I can see a couple events like App Activation and App Installs and other stuff like Completed App Session. Of these, I'm manually logging App Activation whereas the rest is I think the sdk provides by default.
The problem is, it's not logging many other custom events that I'm trying to log.
How do I debug this? Thanks in advance.
When you send the custom events, are you setting the page_scoped_user_id correctly? Here is a raw JSON cutom event, per docs.
{
url : "https://graph.facebook.com/<app_id>/activities",
form: {
event: 'CUSTOM_APP_EVENTS',
custom_events: JSON.stringify([{
_eventName: "fb_mobile_purchase",
_valueToSum: 55.22,
_fb_currency: 'USD'
}]),
advertiser_tracking_enabled: 0,
application_tracking_enabled: 0,
extinfo: JSON.stringify(['mb1']),
page_id: <page_id>,
page_scoped_user_id: recipientId
}
}
The last value, recipientId, is misleading. If you want to log something the user sent, you would want to log the senderId property of that incoming message.

Testing for off-line in a Worklight app

What is the best way to test if a Worklight app is off-line?
After I use the WL.Device.startAcquisition( ... ) api to start stuff off, I am currently using:
WL.Device.Geo.acquirePosition(function(pos) {
console.log("***** Aquired position ***** " + JSON.stringify(pos));
}, function(error) {
console.log(JSON.stringify("***** Unable to aquire position ***** " + error.code + ' : ' + error.message));
// call method to asynchronously - periodicallyCheckIfOnline( ... );
}, {timeout: 5000});
And if I determine that I am offline, I then use the watchPosition api to periodically test for a new connection.
navHandle = navigator.geolocation.watchPosition(onSuccess, onError, { timeout: 5000 });
Once I get the connection back I then clear the watch.
navigator.geolocation.clearWatch(navHandle);
Is this the best way of doing it or are there better Worklight APIs to use for this.
Note: I am trying to test this in a Mobile Browser Simulator scenario hence the short timeouts.
On startup, use something like
WL.Client.connect({onSuccess:onConnectSuccess,onFailure:onConnectFailure,timeout:number_of_ms});
to check if you have initial connectivity.
To detect any further changes in connectivity, you can use the
WL.Client.setHeartBeatInterval(number_of_s) API.
This will 'ping' the worklight server every number_of_s seconds and fire the WL.Events.WORKLIGHT_IS_DISCONNECTED and WL.Events.WORKLIGHT_IS_CONNECTED events, to which you attach callbacks to as described in the reading-worthy tutorial linked to by #Leandro David.
NOTE : if you need to use network to transfer heavy data, do a double check : once you know you have connectivity to the worklight server, use the WL.Device.getNetworkInfo API to check the connection quality before sending/receiving data.
There is a tutorial about dealing with online/offline mode in the worklight geting started material ("Working Offline" link):
http://www.ibm.com/developerworks/mobile/worklight/getting-started.html#GS_work_offline
It tells the best way to use Worklight API to deal with online/offline connection
To summarize, I believe this is the most important part:
Active detection of connectivity
Connectivity loss can be detected in two locations in your application code:
– Application initialization – WL.Client.init() method, typically called from initOptions.js file
– Adapter procedure invocation – WL.Client.invokeProcedure() method
-To add connectivity failure detection in either location, add the onConnectionFailure property and specify a callback function to be invoked if connectivity fails
var wlInitOptions = {
onConnectionFailure: function (data){
connectionFailure(data);
},
or
WL.Client.invokeProcedure(invocationData, {
onSuccess: successHandlerFunction,
onConnectionFailure: connectionFailure,
timeout: 1000
});
Passive detection – Offline and online events
Each time the Worklight framework attempts to access the Worklight Server, it might detect that the application switched from offline to online status or vice versa.
In both cases, JavaScript events are fired:
– WL.Events.WORKLIGHT_IS_DISCONNECTED event is fired when connectivity to the Worklight Server fails
– WL.Events.WORKLIGHT_IS_CONNECTED event is fired when connectivity to the Worklight Server is restored
You can add event listeners to these events and specify the callback functions to handle them.
document.addEventListener(WL.Events.WORKLIGHT_IS_CONNECTED, connectDetected, false);
document.addEventListener(WL.Events.WORKLIGHT_IS_DISCONNECTED, disconnectDetected, false);
Note: WL.Events.WORKLIGHT_IS_DISCONNECTED and WL.Events.WORKLIGHT_IS_CONNECTED are namespace constants, not strings
There are more details available in the tutorial above

Resources