I'm currently developing an application in Ionic 5. One of the only things left to do is integrate Square Payments into the app. Currently I load the script using a script service and then run the code to generate the payment form etc:
/// within ngOnInit
this.scriptService.load('square-sandbox').then(async data => {
const payments = await Square.payments(this.applicationId, this.locationId);
//Card payments
let card;
const cardButton = document.getElementById(
'card-button'
);
const backBtn = document.getElementById('toggle-drawer');
try {
card = await this.initializeCard(payments);
} catch (e) {
console.error('Initializing Card failed', e);
return;
}
//googlepayments
let googlePay;
try {
googlePay = await this.initializeGooglePay(payments);
} catch (e) {
console.error('Initializing Google Pay failed', e);
}
async function handlePaymentMethodSubmission(event, paymentMethod) {
event.preventDefault();
try {
const token = await this.tokenize(paymentMethod);
const paymentResults = await this.createPayment(token);
console.log("SUCCESS")
setTimeout(()=>{
window.location.href = "/payment-success";
},250);
console.debug('Payment Success', paymentResults);
} catch (e) {
cardButton.setAttribute("disabled ", "false");
backBtn.setAttribute("disabled ", "false");
console.log('FAILURE');
console.error(e.message);
}
}
if (googlePay !== undefined) {
const googlePayButton = document.getElementById('google-pay-button');
googlePayButton.addEventListener('click', async function (event) {
await handlePaymentMethodSubmission(event, googlePay);
});
}
}).catch(error => console.log(error));
this part all works fine. However after the loading of square.js it seems to take over the application? All console.logs will come from square.js and all routing seems to also go through square.js with angular throwing an error.
As you can see the first console log is thrown out by page.ts, then the rest are by square despite them actually being called from a function within page.ts. The navigation is also now apparently triggered outside the Angular zone. I'm not sure what is going on and hoping someone can point me in the right direction.
EDIT: I forgot to mention window.location.href makes everything work fine again if this helps. But it isn't ideal as it fully refreshes the page.
Thanks
I ran into something similar in NG12. I added zone.js rxjs patching to the polyfills.ts file in angular to resolve
/**************************************************************************> *************************
* Zone JS is required by default for Angular itself.
*/
import 'zone.js/dist/zone'; // Included with Angular CLI.
import 'zone.js/dist/zone-patch-rxjs';
Reference: https://betterprogramming.pub/zone-js-for-angular-devs-573d89bbb890
Related
it's been quite a long time since I've posted here. Just wanted to bounce this off of you as it has been making my brain hurt. So, I have been developing a real time video chat app with WebRTC. Now, I know that the obligatory "it's somewhere in the network stack (NAT)" answer always applies.
As is always the case it seems with WebRTC, it works perfectly in my browser and on my laptop between tabs or between Safari/Chrome. However, over the internet on HTTPS on a site I've created, it is shotty at best. It can accept and display the media stream from my iPhone but it cannot receive the media stream from my laptop. It just shows a black square on the iPhone for the remote video.
Any pointers would be most appreciate though as I've been going crazy. I know that TURN servers are an inevitable aspect of WebRTC but I'm trying to avoid employing that.
So, here is my Session class which handles essentially all the WebRTC related client side session logic:
(The publish method is just an inherited member that emulates EventTarget/EventEmitter functionality and the p2p config is just for Google's public STUN servers)
class Session extends Notifier {
constructor(app) {
super()
this.app = app
this.client = this.app.client
this.clientSocket = this.client.socket
this.p2p = new RTCPeerConnection(this.app.config.p2pConfig)
this.closed = false
this.initialize()
}
log(message) {
if (this.closed) return
console.log(`[${Date.now()}] {Session} ${message}`)
}
logEvent(event, message) {
let output = event
if (message) output += `: ${message}`
this.log(output)
}
signal(family, data) {
if (this.closed) return
if (! data) return
let msg = {}
msg[family] = data
this.clientSocket.emit("signal", msg)
}
initialize() {
this.p2p.addEventListener("track", async event => {
if (this.closed) return
try {
const [remoteStream] = event.streams
this.app.mediaManager.remoteVideoElement.srcObject = remoteStream
} catch (e) {
this.logEvent("Failed adding track", `${e}`)
this.close()
}
})
this.p2p.addEventListener("icecandidate", event => {
if (this.closed) return
if (! event.candidate) return
this.signal("candidate", event.candidate)
this.logEvent("Candidate", "Sent")
})
this.p2p.addEventListener("connectionstatechange", event => {
if (this.closed) return
switch (this.p2p.connectionState) {
case "connected":
this.publish("opened")
this.logEvent("Opened")
break
// A fail safe to ensure that faulty connections
// are terminated abruptly
case "disconnected":
case "closed":
case "failed":
this.close()
break
default:
break
}
})
this.clientSocket.on("initiate", async () => {
if (this.closed) return
try {
const offer = await this.p2p.createOffer()
await this.p2p.setLocalDescription(offer)
this.signal("offer", offer)
this.logEvent("Offer", "Sent")
} catch (e) {
this.logEvent("Uninitiated", `${e}`)
this.close()
}
})
this.clientSocket.on("signal", async data => {
if (this.closed) return
try {
if (data.offer) {
this.p2p.setRemoteDescription(new RTCSessionDescription(data.offer))
this.logEvent("Offer", "Received")
const answer = await this.p2p.createAnswer()
await this.p2p.setLocalDescription(answer)
this.signal("answer", answer)
this.logEvent("Answer", "Sent")
}
if (data.answer) {
const remoteDescription = new RTCSessionDescription(data.answer)
await this.p2p.setRemoteDescription(remoteDescription)
this.logEvent("Answer", "Received")
}
if (data.candidate) {
try {
await this.p2p.addIceCandidate(data.candidate)
this.logEvent("Candidate", "Added")
} catch (e) {
this.logEvent("Candidate", `Failed => ${e}`)
}
}
} catch (e) {
this.logEvent("Signal Failed", `${e}`)
this.close()
}
})
this.app.mediaManager.localStream.getTracks().forEach(track => {
this.p2p.addTrack(track, this.app.mediaManager.localStream)
})
}
close() {
if (this.closed) return
this.p2p.close()
this.app.client.unmatch()
this.logEvent("Closed")
this.closed = true
}
}
I've worked with WebRTC well over a little while now and am deploying a production level website for many-to-many broadcasts so I can happily help you with this answer but don't hit me as I'm about to spoil some of your fun.
The Session Description Protocol you generate would had contained the send/recv IPs of both connecting users. Now because none of you are actually port-forwarded to allow this connection or to act as a host, a TURN would be in fact required to mitigate this issue. For security reasons it's like this and most users will require a TURN if you decide to go this route.
You can skip a TURN server completely but still requiring a server, you'd go the route of sending/receiving RTP and routing it like an MCU/SFU.
These solutions are designed to take in WebRTC produced tracks and output them to many viewers (consumers).
Here's a SFU I use that works great for many-to-many if you can code it. It's Node.JS friendly if you don't know other languages outside JavaScript.
https://mediasoup.org/
I am working on a project that creates a google chrome extension and I am using chrome API's in it. Now, I am trying to work my handleTabUpdate function when tab is updated. However, I am getting Unchecked runtime.lastError: No tab with id: 60
How can I fixed that? Here is my code:
chrome.tabs.onUpdated.addListener(handleTabUpdate)
function handleTabUpdate(tabId, info) {
if (info.status === 'loading') {
store.dispatch({ type: 'RESET_TABHOSTS' })
chrome.tabs.get(tabId, (activeTab) => {
if (tabId === store.getState().currentTab['id']) {
store.dispatch({ type: 'ACTIVE_TAB', payload: activeTab })
}
})
}
}
My guess is the tab you are looking for was closed, so when you try to get it by id the operation fails.
To avoid the error, my suggestion is to first query all tabs and see if a tab with a specific id exists in the result. If it does, run chrome.tabs.get() and with your logic.
Just bumped up against this issue in MV3 and I've tooled a solution that allows a bit more ease when working with tabs.
Functions
const handleRuntimeError = () => {
const error = chrome.runtime.lastError;
if (error) {
throw new Error(error);
}
};
const safeGetTab = async (tabId) => {
const tab = await chrome.tabs.get(parseInt(tabId));
try {
handleRuntimeError();
return tab;
} catch (e){
console.log('safeGetTab', e.message);
}
return {};
};
Implementation
(async () => {
// assumes some tabId
const tab = await safeGetTab(tabId);
})()
This will return a value no matter what. It will either return the tab object or an empty object. Then you can can just do some basic checking in your script to decide how you want to handle that. In my case I can simply ignore the action that would have been taken on that tab and move on.
I am working on a small project, and I have a hard time reaching what I need. I've already requested some help on another matter about this project, but I'm at a loss again, I think this time because I've bitten a bit more than I could chew.
So here goes : I'm using the Musicbrainz API to retrieve informations for a specific track using it's id (it's length, release date, artists, etc ...).
But I'm trying to also show below the details of the track the covers of all the releases where the track is present. This require new requests for each of the release. I manage to retrieve the URLs of the images that I need and push them into an array that I then map / join to add some <img> element so that it can render in the HTML.
However, my issue is that when I click on the button to show more details about a specific track, which in turn fires the function controlTrackDetail in my controller (I've tried to implement some basic MVC architecture as practice ...), the first part of the "rendering" (the general informations from TrackView.render) is fine, but the second part (the covers) is not. I gathered that when I call my CoverView.renderCovers method, the array used as a parameter is still empty, and as such, nothing happens. If I don't empty the array, and click again on my button, it does show all my covers, but only because the URL are those of the previous call to controlTrackDetail.
If anyone has any idea as to how I could tackle this, and only render the covers after all the request in the forEach loop unqReleaseCoversUrl(mbid.id) of API calls is done, that would help me plenty. Below, you'll find the "important" snippets of code to (hopefully) understand my issue.
GET_JSON function (race between a fetch and timeout)
export const GET_JSON = async function (url) {
try {
const res = await Promise.race([
fetch(url),
timeout(CONSTANTS.TIMEOUT_SEC),
]);
const data = await res.json();
if (!res.ok) throw new Error(`${data.message} (${res.status})`);
return data;
} catch (err) {
throw err;
}
};
The model part, where I create the loadTrackDetails function to recover the informations I need.
import { CONSTANTS } from "./config.js";
import { CONVERT_MILLIS_TO_MINS_SECONDS } from "./helpers.js";
import { GET_JSON } from "./helpers.js";
import { SHORTEN_STRING } from "./helpers.js";
import { CONSTRUCT_URL_PART } from "./helpers.js";
/*
https://musicbrainz.org/ws/2/recording/738920d3-c6e6-41c7-b504-57761bb625fd?inc=genres+artists+ratings+releases&fmt=json
loadTrackDetail("738920d3-c6e6-41c7-b504-57761bb625fd");
*/
export const details = {
trackDetails: {},
artistDetails: {},
releaseDetails: {},
coverUrlArray: [],
};
export const loadTrackDetail = async function (id) {
try {
const trackData = await GET_JSON(
encodeURI(
`${CONSTANTS.API_URL}${id}?inc=genres+artists+ratings+releases&fmt=json`
)
);
details.trackDetails = {
trackTitle: trackData.title ?? "No title provided",
trackID: trackData.id,
trackReleaseDate: trackData["first-release-date"] ?? "No date provided",
trackLength: trackData.length
? CONVERT_MILLIS_TO_MINS_SECONDS(trackData.length)
: "No duration provided",
trackArtists: trackData["artist-credit"].length
? trackData["artist-credit"]
: "No information on artists",
trackReleasesBase: trackData["releases"].length
? trackData["releases"]
: "No information on releases",
trackReleasesCleanOne: trackData["releases"].length
? trackData["releases"].map((release) => ({
id: release.id,
title: release.title,
}))
: "No information on releases",
trackGenres: trackData["genres"].length
? trackData["genres"]
: "No information on genres",
trackRating: trackData.rating.value ?? "No rating yet",
};
if (details.trackDetails.trackReleasesCleanOne.length > 0) {
details.trackDetails.trackReleasesCleanOne.forEach((mbid) =>
unqReleaseCoversUrl(mbid.id)
);
}
details.coverUrlArray = details.coverUrlArray.filter(function (element) {
return element !== undefined;
});
console.log(details.coverUrlArray);
} catch (err) {
throw err;
}
};
/*
*/
export const unqReleaseCoversUrl = async function (mbid) {
try {
const coverData = await GET_JSON(
encodeURI(`${CONSTANTS.COVER_API_URL}${mbid}`)
);
console.log(coverData.images);
coverData.images.forEach((image) => {
image.thumbnails["500"]
? details.coverUrlArray.push(image.thumbnails["500"])
: null;
});
} catch (err) {
throw err;
}
};
And finally, the controller part, fired on click on a button
const controlTrackDetail = async function (trackID) {
try {
TrackView.renderSpinner();
await detailsModel.loadTrackDetail(trackID);
// 2) Rendering Recipe
TrackView.render(detailsModel.details.trackDetails);
CoverView.renderSpinner();
CoverView.renderCovers(detailsModel.details.coverUrlArray);
} catch (err) {
console.log(err);
}
};
Like I said, this works ... fine (though I guess it's not really clean ...), aside from the fact that I'm rendering my covers early, and I'm just not sure how to delay it until the URLs are pushed into the arrays.
Thanks for reading me !
PS : here are a few pictures to understand.
On first click, what I see (nothing appears below Cover Arts, where the rendering of the covers should happen) :
After a few milliseconds, the array is filled with the URLs I need :
And when I go back to the results, and click on any button without emptying the array, it is used to render the covers using the URLs that I pushed the first time around (and this array will carry on growing !)
I'm new on electronjs and developing a small application that reads a json file and build a small html form and return the values entered by the user.
So I've developed small scripts in javascript that link to html 'button' tags to call dialogs so that a user can enter directories, files and save the final form. Everything works nicely... on electronjs "^3.1.13". But if I'm updating to a recent version of the lib ("^8.2.5"), then all my cool ShowOpenDialog don't work at all. Any clue of what happens?
Here is the script to open a folder if it helps:
{
let myName = document.currentScript.getAttribute('name');
const ipc = require('electron').ipcRenderer;
let asyncBtn = document.querySelector('#folder-selector-'+myName);
let replyField = document.querySelector('#folder-selector-content-'+myName);
let onButtonClick = function() {
const { dialog } = require('electron').remote;
let dialogOptions = {
title: "Choisir un dossier:",
properties: ['openDirectory','promptToCreate'],
};
dialog.showOpenDialog(
dialogOptions,
fileNames => {
if (fileNames === undefined) {
console.log("No file selected");
} else {
console.log('file:', fileNames[0]);
replyField.value = fileNames[0];
}
})
};
asyncBtn.addEventListener("click", onButtonClick);
}
Thanks a lot for any help.
Apart from the fact that the call to dialog.showOpenDialog has indeed been updated in recent versions of Electron, and returns a promise instead of making use of a callback function, there is another flaw in your updated code: reading the above-mentioned documentation page shows that getCurrentWindow() is not a method of dialog; it can be obtained from remote instead, so you have to add it explicitely:
const { dialog, getCurrentWindow } = require('electron').remote;
then simply call it from inside dialog.showOpenDialog:
dialog.showOpenDialog( getCurrentWindow(), dialogOptions).then(result => {
but this is an error you could have caught yourself by looking at the DevTools's console, which would display:
TypeError: dialog.getCurrentWindow is not a function
Recent version of showOpenDialog receives two arguments: optional BrowserWindow, and options as second argument. It returns promise and not requires callback.
https://github.com/electron/electron/blob/8-x-y/docs/api/dialog.md#dialogshowopendialogbrowserwindow-options
So you need to change you callback logic to promises.
let onButtonClick = function() {
const { dialog } = require('electron').remote;
let dialogOptions = {
title: "Choisir un dossier:",
properties: ['openDirectory','promptToCreate'],
};
dialog.showOpenDialog(
dialogOptions
).then((fileNames)=>{
if (fileNames === undefined) {
console.log("No file selected");
} else {
console.log('file:', fileNames[0]);
replyField.value = fileNames[0];
}
}).catch(err=>console.log('Handle Error',err))
};
asyncBtn.addEventListener("click", onButtonClick);
thanks a lot Vladimir. So I've tried to update my code as explained, updating electron package to version 8.2.5 and modifying the script as you explained but it's not going any better. If I got it well, this code should be correct, but doesn't work on electron 8.2.5. Any error you still see on this?
{
let myName = document.currentScript.getAttribute('name');
const ipc = require('electron').ipcRenderer;
let asyncBtn = document.querySelector('#folder-selector-'+myName);
let replyField = document.querySelector('#folder-selector-content-'+myName);
let onButtonClick = function() {
const { dialog } = require('electron').remote;
let dialogOptions = {
title: "Choisir un dossier:",
properties: ['openDirectory','promptToCreate']
};
dialog.showOpenDialog( dialog.getCurrentWindow(), dialogOptions).then(result => {
if(!result.canceled) {
replyField.value = result.filePaths[0];
}
}).catch(err => {
console.log(err)
})
};
asyncBtn.addEventListener("click", onButtonClick);
}
Ok, finally got it. Apart from the most appreciated help I had, I missed
"webPreferences": {
nodeIntegration: true
}
in the main.js to make it work.
The discovering of the Developer Tools were of great help as well :)
Now everything is fine again. Thanks a lot!
Useage of 'request-native-promise' not correctly chaining to it's subsequent 'then' and 'catch' handlers.
My Protractor Test
// There's a bunch of other imports here
import { browser } from "protractor";
const RequestPromise = require('request-promise-native');
describe('spec mapper app', () => {
let specMapperPage: SpecMapperPage;
let specMapperFVRPage: SpecMapperFieldsValuesReviewPage;
let loginLogoutWorkflow: LoginLogoutWorkflow;
let apiToken: LoginToken;
let tokenUtil: TokenUtil;
let projectRecordsToBeDeleted = [];
let requestHandler;
let logger = new CustomLogger("spec mapper app");
let speccyEndpoints = new SpeccyEndpoints();
beforeAll( () => {
logger.debug("Before All")
loginLogoutWorkflow = new LoginLogoutWorkflow();
loginLogoutWorkflow.login();
tokenUtil = new TokenUtil();
tokenUtil.getToken().then((token:LoginToken) => {
apiToken = token;
requestHandler = new SpeccyRequestHandler(apiToken);
});
});
describe('import/export page', () => {
it('TC2962: I'm a test case', () => {
let testLogger = new CustomLogger("TC2955");
// Test Var setup
... // removed for brevity
// Test Setup
browser.waitForAngularEnabled(false);
// Setup the record to be on the mapper page
let body = speccyEndpoints.generateRevitPostBody(PROJECT_ID, fileName);
requestHandler.postToSpeccy(speccyEndpoints.DELITE_REVIT_POST_URI, body).then((response) => { // EDIT: removed non-existant argument "rejection"
// --> The then handler the promise is STILL not resolving into
// Only made it here like once
console.log("Response is: ");
console.log(response);
// I got this to work once, but now it's not
console.log("Response body is: ");
console.log(response.body);
}).catch(error => {
// --> The catch handler is ALSO NOT resolving
console.log("catch handler executed!");
console.log(error);
});
});
});
});
The test case where things are going wrong. My console.log("Response is: "); is NOT being outputted. I'm not getting error messages as to why.
My Speccy Request Handler Wrapper Class
import * as RequestPromise from "request-promise-native";
import {LoginToken} from "../testObjects/LoginToken";
import {CustomLogger} from "../logging/CustomLogger";
export class SpeccyRequestHandler {
_baseAPIURL = 'http://myapi.net/';
_options = {
method: '',
uri: '',
auth: {
'bearer': ''
},
headers: {
'User-Agent': 'client'
},
"resolveWithFullResponse": true,
body: {},
json: true
};
_logger;
constructor(apiToken: LoginToken) {
this._options.auth.bearer = apiToken.idToken;
this._logger = new CustomLogger(SpeccyRequestHandler.name);
}
getOptions() {
return this._options;
}
postToSpeccy(uri:string, body?) {
this._options.method = 'POST';
this._options.uri = this._baseAPIURL + uri;
if(body) {
this._options.body = body;
}
return RequestPromise(this._options);
}
getFromSpeccy(uri) {
this._options.method = 'GET';
this._options.uri = this._baseAPIURL + uri;
return RequestPromise(this._options);
}
}
This is my Request Handler specific to one of my APIs, the Speccy one, and has some custom aspects to it in the URL and the token passing.
Sources
Request-Promise-Native Github Page
Request-Promise Github page, documentation location
Update Number 1
After the fix #tomalak brought to my attention, my console.log's in the .then(... handler were being executed, the first 5-ish times after I changed over to this, I was getting a roughly 150+ line console log of the response object that contained a body of response I would expect from my request URI. I even got the body out by using response.body. I thought things were fixed and I wasn't using my logger that logs out to file, so I lost my proof. Now when I run this test and this request I do not go into the .then(... handler at all. I'm also not going into the catch. My request is working though, as my resource is created when the post endpoint is hit. Any further suggestions are appreciated.
What would cause something to work sometimes and not others? My only thought is maybe the generic post name in my request handler wasn't being used in lieu of another method higher up the build chain being caught.
Update Number 2
Removed a bunch of stuff to shorten my question. If you need more clarification, ask and I'll add it.
It ended up being a timeout on the end of the API. My response was simply taking too long to get back to me. It wasn't failing, so it never went into the catch. And I had it working at one point because the response taking so long is due to an overabundance of a certain resource in our system in particular. I thought it was me and something I had written wrong. Long story short, suspect your system, even if you think it's perfect or if your devs swear up and down nothing could be broken.
Also, the request-debug module was a nice thing to have to prove that other endpoints, such as the rest testing endpoints at https://jsonplaceholder.typicode.com/ , do work with your code.