Outlook AddIn GetAsync successful but returns nothing - javascript

I've got an Outlook Add In that was developed using the Office Javascript API.
It looks at the new email being composed & does things based on who it's going to: https://learn.microsoft.com/en-us/office/dev/add-ins/reference/objectmodel/requirement-set-1.3/office.context.mailbox.item
The code correctly returns the TO email when you 'select' the email from the suggested email list... screenshots shown # bottom of this thread
To debug the Javascript, I use C:\Windows\SysWOW64\F12\IEChooser.exe
It was working fine until last week. Is it possible a Windows update broke functionality?
I'm the only person with access to the code. It hadn't been modified for months.
When debugger is running, getAsync correctly returns the 'TO' value. I needed to write the response to a global variable to prove the values were 'undefined' while not in debug.
var resultObjects;
var resultObjects2;
var strMessages = '';
var strTo = '';
var mailbox;
var mailitem;
(function () {
"use strict";
// The Office initialize function must be run each time a new page is loaded.
Office.initialize = function (reason) {
$(document).ready(function () {
mailbox = Office.context.mailbox;
mailitem = mailbox.item;
mailitem.to.getAsync(function (result) {
if (result.status === 'failed') {
strMessages = 'FAILED';
} else {
strMessages = 'SUCCESS';
strTo = result.value[0];
resultObjects = result;
resultObjects2 = result.value;
}
});
loadApp();
});
};
})();
Here are the values of the variables, when the app is loaded & debugger is not running
EDIT
If you 'select' the TO email so that it is bolded... the code works correctly. If you leave the typed-in-text field without selecting the suggested email, it does not work. The same behavior is true for both the Outlook Web Application (# https://outlook.office.com) and the desktop outlook application.
Does not work
Does Work

The Office.context.mailbox.item.to.getAsync API will only return resolved recipients. If the TO email address is not resolved (as in the first screenshot titled "Does not Work"), then API will not return the email address until it is resolved (in both desktop and OWA).
You can use the RecipientsChanged Event, to get newly resolved recipients after you have queried for to.getAsync. This event would fire when a recipient is newly resolved.

Related

Cypress with react and google API services - how to stub autocomplete

I am trying to test a react webapp (created in a separate project), that contains a popup where there's an input containing a google auto-complete for cities:
(I changed text because of language)
I have in "search city" a text input where if data is inserted, google searches for cities and returns results (eg I search Rome, Italy):
When I press "save data" there's a function that checks google results, then closes the popup:
in a file:
export const useGoogleApiDesktop = () => {
let autocompleteService
if (window.google && window.google.maps) {
autocompleteService = new window.google.maps.places.AutocompleteService()
}
}
in another file (the one called):
const googleApi = useGoogleApiDesktop()
const onSubmitClick = useCallback(async () => {
[...]
const res: GoogleApiPlacesResponse = await googleApi.autocompleteService.getPlacePredictions({
input: addressComputed,
types: ['(cities)'],
componentRestrictions: { country: 'it' }
})
}, [])
When I use it in plain browser, everything works fine;
but if I try to launch it with cypress to test it, it returns me this error:
I am trying to avoid this error and simply go on and close the popup, since during my tests I do not need to write anything on that line; I only need to write something on the other textareas and close the popup.
Since I couldn't do it, I've tried to stub that call, but I am totally new in using cy.stub() and does not work:
function selectAddress(bookingConfig) {
// opens the popup
cy.get('.reservationsWhereAdd').click()
// trying to add the google library
const win = cy.state('window')
const document = win.document
const script = document.createElement('script')
script.src = `https://maps.googleapis.com/maps/api/js?key=[myApiKey]&libraries=places&language=it`
script.async = true
// this is commented since I don't think I need it
// window.initMap = function () {
// // JS API is loaded and available
// console.log('lanciato')
// }
// Append the ‘script’ element to ‘head’
document.head.appendChild(script)
// type something in some fields
cy.get('#street').type(bookingConfig.street)
cy.get('#streetNumber').type(bookingConfig.streetNum)
cy.get('#nameOnTheDoorbell').type(bookingConfig.nameOnTheDoorbell)
cy.get('#addressAlias').type(bookingConfig.addressAlias)
// this correctly finds and prints the object
console.log('--->', win.google.maps.places)
cy.stub(googleApi.autocompleteService, 'getPlacePredictions')
// this closes the popup
cy.get('.flex-1 > .btn').click()
}
this cy.stub however does not works, and I don't get why: it says
googleApi is not defined
Any idea on how to solve this? Thanks!
UPDATE:
After the error, working with the cypress window, I manually closed the popup, reopened it, filled the fields, and clicked on save data. It worked, so I added a cy.wait(1000) just after opening the popup and it works for 95% of the times (9 times on 10). Any Idea on how to "wait for loading the google api, then fill the fields"?
As the update block said, I discovered that the problem was that it kept really long time to load the google API, because it's not local and needs time to be retrieved.
So at first I just put a cy.wait(2000) before executing my code; but this couldn't be the answer: what happens if I run the code on a slow network? Or if it takes more time for my application to load?
So, i created a command, that first waits for the google API to load; if it fails to load after 5 attempts, the test fails.
Then, after that, my code is being executed. This way my test won't fail really easily.
Here's the code:
in cypress/support/command.js
Cypress.Commands.add('waitForGoogleApi', () => {
let mapWaitCount = 0
const mapWaitMax = 5
cyMapLoad()
function cyMapLoad() {
mapWaitCount++
cy.window().then(win => {
if (typeof win.google != 'undefined') {
console.log(`Done at attempt #${mapWaitCount}:`, win)
return true
} else if (mapWaitCount <= mapWaitMax) {
console.log('Waiting attempt #' + mapWaitCount) // just log
cy.wait(2000)
cyMapLoad()
} else if (mapWaitCount > mapWaitMax) {
console.log('Failed to load google api')
return false
}
})
}
})
in file you want to use it:
cy.waitForGoogleApi().then(() => {
// here comes the code to execute after loading the google Apis
})

jquery hide/show is not working as expected in node js

I am new to node js, I am building one application for learning purpose.
I stuck in a thing, where I have to do show/hide of success and error message.
Here I have used jquery plugin in node js.
Below is the code :
const jsdom = require("jsdom");
const { JSDOM } = jsdom;
const { window } = new JSDOM();
const { document } = (new JSDOM('')).window;
global.document = document;
global.jQuery = require('jquery')(window);
global.$ = global.jQuery;
registration code :
doRegistration : (req , res) => {
let email = req.body.email;
let password = req.body.password;
let name = req.body.name;
let confirm_password = req.body.confirm_password;
let encryptedString = cryptr.encrypt(req.body.password);
if(email== '' || password == '' || name =='' || confirm_password =='')
{
message = "Fields are empty";
let list2 = [];
list2.name = '';
list2.email = '';
if(name!='')
{
list2.name =name;
}
if(email!='')
{
list2.email = email;
}
req.flash('error', message);
setTimeout(function(){ $("#err").hide(); },3000); //not working
res.locals.message = req.flash();
res.render('registration.ejs' ,{
code : '204',
title: "Registration",
details :list2,
});
}
}
setTimeout(function(){ $("#err").hide(); },3000); this code has been added to hide the error message .Error message is not getting hide.
Is there anything wrong in my code.
Please suggest.
Thank you
You have a bunch of code (in your first code block) which sets up a DOM and jQuery. You don't seem to ever populate the document with any data, but that's not your biggest problem.
In your registration code, you collect some data, inject it into registration.ejs and send the result to the client.
Three seconds later (by which time the HTTP request is finished, and the browser has whatever your sent), you call a jQuery function. This function operates on whatever global.document is.
global.document doesn't appear to have anything to do with registration.ejs and, even if it did, would change what was currently on the server without touching whatever is on the browser.
You can't write server-side JS that travels back in time and changes what the server sent to the browser three seconds earlier.
You could instead include a <script> element in the template file and run your jQuery client-side.
You can't write server-side code which initiates a connection to the browser (HTTP works with the browser initiating a communication with a request and the server providing a response) telling it to display a different document instead.
You could use WebSockets (or less elegant approaches like Comet or polling) so that when the page loads in the browser it initiates a connection to the server and then signal the browser to run some client-side code which hides the element. You still need to do the hiding with client-side code, this just allows you to determine when that happens on the server. There's no need for anything that complex if you just want to wait a short time.

best way to retrieve URLs from a mail body

The following line was making add-in non-responsive on Mac clients. The add-in works absolutely fine on mac client if we remove this line and execute the function someFunction directly.
Office.context.mailbox.item.body.getAsync("html", someFunction);
We used body.getAsync() because we need to extract all the URLs in the mail body by processing the html and those urls containing certain IDs.
tried using the below but didn't give the expected URLS.
var links = Office.context.mailbox.item.getEntities().urls;
I am also trying the following
Office.initialize = function (reason) {
$(document).ready(function () {
app.initialize();
Office.context.mailbox.item.body.getAsync("html", processHtmlBody);
});
};
function processHtmlBody(asyncResult) {
var htmlParser = new DOMParser().parseFromString(asyncResult.value, "text/html");
var links = htmlParser.getElementsByTagName("a");
}
Is there a better alternative to fetch the URLs from the mail body.
Note that getAsync is part of the 1.3 Mailbox Requirement set, which Outlook for Mac doesn't currently support:
https://dev.outlook.com/reference/add-ins/tutorial-api-requirement-sets.html
Otherwise using Entities is your only option, but getElementsByTagName would probably work best (if you had access to the email body).
We tried both alternatives/workarounds with makeEwsRequestAsync() and getEntities().urls to retrieve the URLs from the mailbody on a Mac client. But both were not successful:
makeEwsRequestAsync(): "no data error".
getEntities().urls : did not give the intended result/URLs
So for Mac client, we ended up changing the logic and added conditional logic which did not include any code to retrieve the URLs.
if (Office.context.requirements.isSetSupported("mailbox", 1.3))
{
//conditional code
}
I had the same problem and I resolved it in a similar way:
return new Promise((resolve, reject) => {
mailbox.item.body.getAsync(window.Office.CoercionType.Html, asyncResult => {
if (asyncResult.status == window.Office.AsyncResultStatus.Succeeded) {
var $dom = $(asyncResult.value);
var url = $dom.find('#x_hiddenURL').text();
resolve(url);
} else {
reject(new Error('Failed to load email body'));
}
});
});

Zombiejs empty HTML

I'm deploying a Zombiejs application to Openshift, but Zombie seems to be unable to fetch the HTML.
I have an object (called Poker) that maintains the headless browser and does things with it. One of the methods, called init, logs in to a website and returns the "initialized" browser.
Poker.prototype.init = function (email, password, ip) {
var self = this;
var browser;
// Have to use the ip that Openshift provides
// See SO question http://goo.gl/n2TfMC
if (ip) {
browser = Zombie.create({
'localAddress': ip
});
} else {
browser = Zombie.create();
}
return new Promise(function (resolve, reject) {
browser
.visit('http://some.login/page')
.then(function () {
// Some debugging stuff :p
console.log('body: ');
console.log(browser.html('body'));
// Fill in the credentials
browser.fill('email', email);
browser.fill('password', password);
return browser.pressButton('Log In');
})
.done(function() {
// Logged in, new page loaded
// Check if login was successful
var title = browser.text('title');
console.log(title);
});
});
}
The console remains empty after printing body:, anden Zombie attempts to fill in the email address, I receive this error:
Possibly unhandled TypeError: Cannot use 'in' operator to search for 'compareDocumentPosition' in null
at /var/lib/openshift/[app-id]/app-root/runtime/repo/node_modules/zombie/node_modules/jsdom/node_modules/nwmatcher/src/nwmatcher-noqsa.js:267:43
at module.exports (/var/lib/openshift/[app-id]/app-root/runtime/repo/node_modules/zombie/node_modules/jsdom/node_modules/nwmatcher/src/nwmatcher-noqsa.js:37:7)
at addNwmatcher (/var/lib/openshift/[app-id]/app-root/runtime/repo/node_modules/zombie/node_modules/jsdom/lib/jsdom/selectors/index.js:6:27)
at HTMLDocument.<anonymous> (/var/lib/openshift/[app-id]/app-root/runtime/repo/node_modules/zombie/node_modules/jsdom/lib/jsdom/selectors/index.js:18:29)
at HTMLDocument.querySelectorAll (/var/lib/openshift/[app-id]/app-root/runtime/repo/node_modules/zombie/node_modules/jsdom/lib/jsdom/level1/core.js:63:53)
at Browser.queryAll (/var/lib/openshift/[app-id]/app-root/runtime/repo/node_modules/zombie/lib/zombie/browser.js:348:26)
at Browser.field (/var/lib/openshift/[app-id]/app-root/runtime/repo/node_modules/zombie/lib/zombie/browser.js:591:17)
at Browser._findOption (/var/lib/openshift/[app-id]/app-root/runtime/repo/node_modules/zombie/lib/zombie/browser.js:662:18)
at Browser.select (/var/lib/openshift/[app-id]/app-root/runtime/repo/node_modules/zombie/lib/zombie/browser.js:692:19)
at /var/lib/openshift/[app-id]/app-root/runtime/repo/poker.js:61:17
After I saw this, I tried to visit a different page (Google) through Zombie, but it returned empty HTML as well.
I took a look at some other StackOverflow questions about the compareDocumentPosition error, but I think the one I'm having is related to deployment on Openshift rather than an issue with the HTML of the page I'm visiting.
I'm using Node.js v0.10.25 and Zombie v2.2.1.
Can it be a CORS problem? .login is not a valid TLD and rules vary per TLD.
Are you sure the variable "browser" you are trying to work with is an Object, that error can occur if the variable is a string.

Gdata JavaScript Authsub continues redirect

I am using the JavaScript Google Data API and having issues getting the AuthSub script to work correctly. This is my script currently:
google.load('gdata', '1');
function getCookie(c_name){
if(document.cookie.length>0){
c_start=document.cookie.indexOf(c_name + "=");
if(c_start!=-1){
c_start=c_start + c_name.length+1;
c_end=document.cookie.indexOf(";",c_start);
if(c_end==-1) c_end=document.cookie.length;
return unescape(document.cookie.substring(c_start, c_end));
}
}
return "";
}
function main(){
var scope = 'http://www.google.com/calendar/feeds/';
if(!google.accounts.user.checkLogin(scope)){
google.accounts.user.login();
} else {
/*
* Retrieve all calendars
*/
// Create the calendar service object
var calendarService = new google.gdata.calendar.CalendarService('GoogleInc-jsguide-1.0');
// The default "allcalendars" feed is used to retrieve a list of all
// calendars (primary, secondary and subscribed) of the logged-in user
var feedUri = 'http://www.google.com/calendar/feeds/default/allcalendars/full';
// The callback method that will be called when getAllCalendarsFeed() returns feed data
var callback = function(result) {
// Obtain the array of CalendarEntry
var entries = result.feed.entry;
//for (var i = 0; i < entries.length; i++) {
var calendarEntry = entries[0];
var calendarTitle = calendarEntry.getTitle().getText();
alert('Calendar title = ' + calendarTitle);
//}
}
// Error handler to be invoked when getAllCalendarsFeed() produces an error
var handleError = function(error) {
alert(error);
}
// Submit the request using the calendar service object
calendarService.getAllCalendarsFeed(feedUri, callback, handleError);
}
}
google.setOnLoadCallback(main);
However when I run this the page redirects me to the authentication page. After I authenticate it send me back to my page and then quickly sends me back to the authenticate page again. I've included alerts to check if the token is being set and it doesn't seem to be working. Has anyone has this problem?
I was having the same problem so I built this function
function login() {
var scope = "http://www.google.com/calendar/feeds/";
if(!google.accounts.user.checkLogin(scope)) {
if(google.accounts.user.getStatus() == 0) {
var token = google.accounts.user.login();
}
}
}
I added the check to google.accounts.user.getStatus() if it's 1 that means the application is in the process of logging in and if it is 2 that means the applications is logged in. You can also pass a scope to the getStatus method.
the problem is that setting the cookie takes a little while when google redirects back to your site. However, the callback runs immediately, and there is no cookie by that time to verify authentication, so it again redirects back to google. Try using setTimeout or something to run the authentication check after a second or so to be sure.
You should pass the scope to the login method too.
Sometimes you can end up with an orphaned cookie in your browser - which will keep getting fed back to Google.
What I'm doing now, is doing a checkLogin before I perform my login call, and if it returns true I explicitly call logOut().
The logOut call will remove any cookies which Google had rejected but left in your browser. The reason it seems to keep going in a loop is because the cookie is there, but even on reauth, it doesn't produce a new one because you already have one. But unfortunately for our sake, the one that's there is invalid.

Categories

Resources