twilio flex queued callbacks and voicemail react app not creating task - plugins

This is the exact step noted from this readme section.twilio queue callback flex plugin
There it is instructed to update accountSid variable. But there is no accountSid variable in appConfig.js (code 1). Assuming, I should add the extra variable I did as showed in code 1 (comment ) below.But, the react app didn't work properly,it didn't show any call baack option or voicemail in the task (image added)
Step 3: Open appConfig.js with your text editor and update the accountSid variable with your account SID:
var accountSid = 'ACXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX'
//code 1
//assuming here I have to add this line "var accountSid =
//'ACXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX'"
var appConfig = {pluginService: {
enabled: true,
url: '/plugins',} ,ytica: false, logLevel: 'info', showSupervisorDesktopView: true,};

Twilio developer evangelist here.
You are right that you need to add the accountSid to the appConfig.js file. It should look like this:
var accountSid = 'ACXXXXX';
var appConfig = {
pluginService: {
enabled: true,
url: '/plugins',
},
ytica: false,
logLevel: 'info',
showSupervisorDesktopView: true,
};
I wonder if you don't see a proper voicemail recording or way to callback because the number they called from, +266696687, is invalid. More than invalid, it is in fact an anonymous number. The numbers 266696687 type out "ANONYMOUS" on a phone keyboard.

Related

Tinymce filenaming causes images to be overwritten when saved

Using TinyMce editor on a PHP-project for creating worklogs. The user can take a screenshot (ex greenshot, snipping tool) and paste this into the Tinymce editor. Tinymce will name the screenshot mceclip0.png (pic1), mceclip1.png (pic2) etc. The user saves the worklog and the images is uploaded to a folder on webserver with those names. So far so good.
When the user want to edit this worklog later and paste in a new image he/she forgot to add, this image will be name... mceclip0.png (pic3). If the user then saves this, oh oh, the first image from the first initial creating of the worklog will be overwritten. So pic1 will now look the same as pic3.
Here is when the first two pictures are added:
And then the user wants to add a third picture, this happens:
Below is the code that is used and from Tinymce. I've tried to change parameters according to documentation with no luck. Some say this is solved by
images_reuse_filename: true
But this is not the case for me. If I take a snipping tool of something and save it to disk it will be named "screenshot.png". In the Tinymce editor it changes to mceclip0.png anyways.
I was thinking I want to append date and time to the "mceclip.png" name, but I can't figure out how to do so. Would this be a solution?
Thanks in advance!
tinymce.init({
selector: 'textarea',
height: 600,
plugins: 'image code paste',
paste_data_images: true,
image_file_types: 'jpg,webp,png',
toolbar: 'undo redo | link image | code',
automatic_uploads: true,
images_upload_url: 'fileUpload.php',
images_reuse_filename: true,
images_upload_handler: function (blobInfo, success, failure) {
var xhr, formData;
xhr = new XMLHttpRequest();
xhr.withCredentials = false;
xhr.open('POST', 'fileUpload');
xhr.onload = function () {
var json;
if (xhr.status !== 200) {
failure('HTTP Error: ' + xhr.status);
return;
}
json = JSON.parse(xhr.responseText);
if (!json || typeof json.location != 'string') {
failure('Invalid JSON: ' + xhr.responseText);
return;
}
success(json.location);
};
formData = new FormData();
formData.append('file', blobInfo.blob(), blobInfo.filename());
xhr.send(formData);
}
});
Seems like there is no way of changing the filename since it is a blob and set in clipboard.js in the TinyMce-plugin if I understand this correctly. Meaning the only way this is solvable (as far as I can see) is to change this serverside. I created then just a concatenation of unix timestamp and the filename to make it unique.
Now the user can go back and edit the worklogs, adding screenshots without overwriting existing screenshots.

Changing Badge Notification count with Pushy in Capacitor/Cordova on iOS

I have a Capacitor/Cordova app using Pushy (https://pushy.me) to handle push notifications. Sending push notifications to devices seems to work fine, and as part of that transaction I can set the app's notification count on the icon when the app is closed.
However, Pushy only seems to have an JS option to clear the counter Pushy.clearBadge(); rather than change it to a specific number from within the app when it's running. The scenario being if a user has read one message, but leaves two unread and then closes the app, I want the counter to be correct.
Digging through the PushyPlugin.swift code, the function for Pushy.clearBadge(); looks like this:
#objc(clearBadge:)
func clearBadge(command: CDVInvokedUrlCommand) {
// Clear app badge
UIApplication.shared.applicationIconBadgeNumber = 0;
// Always success
self.commandDelegate!.send(
CDVPluginResult(
status: CDVCommandStatus_OK
),
callbackId: command.callbackId
)
}
Which is tantalisingly close to giving me what I need, if only I could pass an integer other than zero in that line UIApplication.shared.applicationIconBadgeNumber = 0;
My knowledge of Swift is zilch, other than recognising familiar programming syntax. I thought I'd have a hack at it and tried adding this to the PushyPlugin.swift file:
#objc(setBadge:)
func setBadge(command: CDVInvokedUrlCommand) {
// Clear app badge
UIApplication.shared.applicationIconBadgeNumber = 100;
// Always success
self.commandDelegate!.send(
CDVPluginResult(
status: CDVCommandStatus_OK
),
callbackId: command.callbackId
)
}
But the app coughed up Pushy.setBadge is not a function when I tried to give that a spin (note, I was testing with 100 just to see what would happen, ideally I'd want to pass an integer to that newly hacked in function).
So what I've learnt so far is I know nothing about Swift.
Am I sort of on the right lines with this, or is there a better way to set the badge counter?
Ok, so I spotted in the pushy-cordova/www folder a Pushy.js file which I needed to add my new function to
{
name: 'setBadge',
noError: true,
noCallback: true,
platforms: ['ios']
},
Then, a bit of trial and error with the PushyPlugin.swift function I added to this:
#objc(setBadge:)
func setBadge(command: CDVInvokedUrlCommand) {
UIApplication.shared.applicationIconBadgeNumber = command.arguments[0] as! Int;
// Always success
self.commandDelegate!.send(
CDVPluginResult(
status: CDVCommandStatus_OK
),
callbackId: command.callbackId
)
}
Then this can be called in my Capacitor project with Pushy.setBadge(X); (X being the int value you want to display on your icon).

Protractor login to gmail fails headless, and works when not headless

I have a routine that logs into gmail using Protractor, that is called from the middle of my script (which is why some things that appear unnecessary are there), but I have isolated it as much as I can. When I run it not headless it passes. When I run it headless, it fails. I looked at the related posts, and they did not seem to be Protractor specific, and they did seem to parallel my code here.
Here is the code:
const EC = ExpectedConditions;
beforeAll(function(){
});
beforeEach(function() {
//because I am using gmail after sending an email from an angular app with a link to get back into one
browser.waitForAngularEnabled(true);
browser.ignoreSynchronization = false;
});
afterEach(function() {
browser.waitForAngularEnabled(true);
browser.ignoreSynchronization = false;
});
var gmailLogin = function(){
browser.waitForAngularEnabled(false);//gmail screens not angular
browser.ignoreSynchronization = true;
browser.sleep(2000);//because ignore sync takes time to settle in
browser.driver.manage().timeouts().implicitlyWait(10000);//set in config, but seems to work only if here
browser.get("https://mail.google.com/mail");
browser.wait(EC.titleContains("Gmail"), 10000, "wait for gmail page");
$('[data-g-label="Sign in"]').click().then(
//this sometimes appears and sometimes is skipped, so ignore result
function(retval){},function(err){}
)
var variousInput = element(by.id('identifierId'));
browser.wait(EC.presenceOf(variousInput), 10000, "wait for identier ID prompt").then(
function(retVal){
var variousInput2 = browser.driver.findElement(by.id('identifierId'));
variousInput2.sendKeys("myemail here");
variousInput2=browser.driver.findElement(by.id("identifierNext"));
variousInput2.click();
variousInput2 = browser.driver.findElement(by.name('password'));
variousInput2.sendKeys('my password here');
variousInput2=browser.driver.findElement(by.id("passwordNext"));
variousInput2.click();
},
function(err){}//assume not found because cookie still around, proceed to next step
)
browser.wait(EC.titleContains("Inbox"), 10000, "wait for inbox");
}
describe('runs gmail test for so', function() {
it('tests gmail', function() {
gmailLogin();
expect(browser.getTitle()).toContain('Inbox');
}, 2 * 60 * 1000); //should always come up within 2 minutes
}); //end of describe
And here is the headed configuration file:
exports.config = {
directConnect: true,
allScriptsTimeout: 120000,
getPageTimeout: 60000,
// Capabilities to be passed to the webdriver instance.
capabilities: {
'browserName': 'chrome',
chromeOptions: {
//args: ["--headless","--disable-gpu","--no-sandbox"]
},
// Framework to use. Jasmine is recommended.
framework: 'jasmine',
// Spec patterns are relative to the current working directory when
// protractor is called.
specs: [
'./so.ts'
],
// Options to be passed to Jasmine.
jasmineNodeOpts: {
showColors: true,
defaultTimeoutInterval: 180000
},
beforeLaunch: function() {
},
onPrepare() {
browser.manage().window().setSize(1600, 1000);
browser.driver.manage().timeouts().implicitlyWait(15000);
}
}
}
and here is the headless (you can see I threw the kitchen sink at the options).
exports.config = {
directConnect: true,
allScriptsTimeout: 60000,
getPageTimeout: 30000,
// Capabilities to be passed to the webdriver instance.
capabilities: {
'browserName': 'chrome',
chromeOptions: {
args: ["--headless","--disable-gpu","--window-size=1600,1000","--disable-infobars","--disable-extensions","--auth-server-whitelist","--remote-debugging-port=9222"]
},
// Framework to use. Jasmine is recommended.
framework: 'jasmine',
// Spec patterns are relative to the current working directory when
// protractor is called.
specs: [
'./so.ts'
],
// Options to be passed to Jasmine.
jasmineNodeOpts: {
showColors: true,
defaultTimeoutInterval: 180000
},
beforeLaunch: function() {
},
onPrepare() {
// screen size set in chrome options
browser.driver.manage().timeouts().implicitlyWait(15000);
}
}
}
If there is some kind of underlying, undocumented wisdom about which locators work or do not work headless, I would love to know.
Thanks,
jk
SLIGHT UPDATE: I cleaned up the code to use only explicit waits and straight Protractor (which is how I had it originally before reading pieces on the web that were based in other languages). Here is the revised version, which still passes not headless and fails headless (I also removed the implicit wait setting in OnPrepare() and all but the first three chrome options when doing headless).
var gmailLogin = function() {
browser.waitForAngularEnabled(false); //gmail screens not angular
browser.ignoreSynchronization = true;
browser.sleep(2000); //because ignore sync takes time to settle in
browser.get("https://mail.google.com/mail");
browser.wait(EC.titleContains("Gmail"), 10000, "wait for gmail page");
$('[data-g-label="Sign in"]').click().then(
//this sometimes appears and sometimes is skipped, so ignore result
function(retval) {},
function(err) {}
);
var variousInput = element(by.id('identifierId'));
browser.wait(EC.presenceOf(variousInput), 10000, "wait for identifier ID prompt").then(
function(retVal) {
var variousInput2 = element(by.id('identifierId'));
variousInput2.sendKeys("email address");
variousInput2 = element(by.id("identifierNext"));
variousInput2.click();
variousInput2 = element(by.name('password'));
browser.wait(EC.presenceOf(variousInput2), 10000, "wait for password prompt");
browser.wait(EC.visibilityOf(variousInput2), 10000, "wait for password prompt");
variousInput2.sendKeys('my password');
variousInput2 = element(by.id("passwordNext"));
variousInput2.click();
},
function(err) {} //assume not found because cookie still around, proceed to next step
)
browser.wait(EC.titleContains("Inbox"), 10000, "wait for inbox");
}
BIGGER UPDATE: this may be something funky about headless after all. I added the following lines right before waiting for the identifier ID element(by.tagName('html')).getText().then(function(text){console.log(text);});
in not headless mode, that produced
Sign in
to continue to Gmail
Email or phone
Forgot email?
Not your computer? Use Guest mode to sign in privately.
Learn more
NEXT
Create account
‪English (United States)‬
HelpPrivacyTerms
in headless, it gave
One account. All of Google.
Sign in to continue to Gmail
Find my account
Create account
One Google Account for everything Google
About Google Privacy Terms Help
followed by a long list of languages from Afrikaans to ‪繁體中. So it seems almost as if in headless the browser has forgotten where it lives (at the very least the addition of One account all of Google and the languages says it is not apples to apples). It makes me wonder if then IdentifierId might also have a different name in such a case.
One last update for now:
To debug I added the following code when that first page loads:
var inputs=element.all(by.tagName('input'));
inputs.each(function(element,index){
element.getAttribute("Id").then(function(text){console.log('input '+index+' '+text);})
})
not headless, we get:
input 0 identifierId
input 1 null
input 2 ca
input 3 ct
input 4 pstMsg
input 5 checkConnection
input 6 checkedDomains
but headless we get:
input 0 null
input 1 null
input 2 null
input 3 null
input 4 null
input 5 null
input 6 null
input 7 null
input 8 null
input 9 null
input 10 null
input 11 profile-information
input 12 session-state
input 13 null
input 14 _utf8
input 15 bgresponse
input 16 Email
input 17 Passwd-hidden
input 18 next
So Protractor is right that it cannot find by ID identifierID. But how come?
FINAL:
So depending on headless or not, google was redirecting to two different urls with two different sets of Ids and names. I posted revised code that handles both in my answer.
Thanks for the guidance, all.
So it turns out that Google will redirect the mail service request to two different versions of its interface depending on whether one goes in headless or not. I rewrote the code to handle either one. I also tried to simplify where I could, including no more implicit waits and adding more chaining (I also dipped my toes into ES6 as encouraged by Oleksii's comment).
const EC = ExpectedConditions;
beforeAll(function(){
});
beforeEach(function() {
//because I am using gmail after sending an email from an angular app with a link to get back into one
browser.waitForAngularEnabled(true);
browser.ignoreSynchronization = false;
});
afterEach(function() {
browser.waitForAngularEnabled(true);
browser.ignoreSynchronization = false;
});
var waitForIds = (id1,id2)=>{//waits for one of two ids, assumes they must exist or else it is an error
var elm = element.all(by.xpath("//*[#id = '"+id1+"' or #id = '"+id2+"']")).first();
browser.wait(EC.presenceOf(elm), 30000, "wait for "+id1+" or "+ id2);
return elm;
}
var gmailLogin = () => {
browser.waitForAngularEnabled(false); //gmail screens not angular
browser.ignoreSynchronization = true;
browser.sleep(2000); //because ignore sync takes time to settle in
browser.get("https://accounts.google.com/ServiceLogin?service=mail");
browser.sleep(2000);
element(by.id('gbqfq')).isPresent().then((present) => {
//if present, we are already on the inbox screen, because we found the search pane
if (!present) { //still work to do to get there
browser.wait(EC.titleContains("Gmail"), 10000, "wait for a gmail page");
$('[data-g-label="Sign in"]').click().then(
//this sometimes appears and sometimes is skipped, so ignore result
(retval) => {}, (err) => {}
);
waitForIds('Email', 'identifierId').sendKeys("my email here");
waitForIds("identifierNext", "next").click();
waitForIds('Passwd', 'password').getAttribute('id').then((text) => {
element(by.name(text)).sendKeys('my password here');
waitForIds("signIn", "passwordNext").click();
})
}
})
browser.wait(EC.titleContains("Inbox"), 10000, "wait for inbox");
}
describe('runs gmail test for so', function() {
it('tests gmail', function() {
gmailLogin();
expect(browser.getTitle()).toContain('Inbox');
}, 2 * 60 * 1000); //should always come up within 2 minutes
}); //end of describe
UPDATE: I am accepting this because it addresses my question of what happens and why and how to address is directly. I totally accept that despite answering the question, there are better ways to go about what I actually wanted to do (getting the href from an email) either by catching the email on its way out or by using the gmail api.

Actions-On-Google NodeJS v2 alpha: More than one conv.close()

Will we be able to use conv.close() multiple times within an intent to provide more than just a single element on exit?
Similar to how you can provide multiple conv.ask() in the one intent.
Or can you include more than one 'new element' in a conv.close() tag?
Yes, of course to both ways! At least it's how it works currently during the alpha (which can change depending on feedback).
conv.ask and conv.close are implemented almost identically just that conv.close sets expectUserResponse to false which means you don't expect more responses from the user and the mic will be closed.
This means you can use conv.close just like conv.ask and call it multiple times.
For example, this code:
const { dialogflow } = require('actions-on-google')
const app = dialogflow()
app.intent('Default Welcome Intent', conv => {
conv.close(`Here's a cat image`)
conv.close(new Image({
url: 'https://developers.google.com/web/fundamentals/accessibility/' +
'semantics-builtin/imgs/160204193356-01-cat-500.jpg',
alt: 'A Cat',
}))
})
when the IntentHandler function is done executing (or if it returns a Promise, when the Promise is resolved), constructs a RichResponse based on the response fragments you provided and sends it back to Dialogflow or the Google Assistant.
It closes the mic and shows this as a result in the simulator.
Alternatively, conv.ask and conv.close also allow you to call it with an arbitrary number of response arguments. So this code will also work identical to the example before:
app.intent('Default Welcome Intent', conv => {
conv.close(`Here's a cat image`, new Image({
url: 'https://developers.google.com/web/fundamentals/accessibility/' +
'semantics-builtin/imgs/160204193356-01-cat-500.jpg',
alt: 'A Cat',
}))
})

Extjs file upload progress

I have seen form file upload example in ExtJS4 and i need customize progress of the file upload.
I see waitMsg config property, but i don't want use that and i don't want use extjs 3rd party plugins.
So, how i can get simply current upload progress from upload form in extjs?
The waitMsg uses a message box with an infinitely auto-updating progress bar. So you can't just create a progressbar that displays the current upload.
You could create your own Ext.ProgressBar and estimate the upload time and when its done you set it to the max value. But I guess you don't want that.
To answer your question: You cannot simply track the current upload progress.
If you really need this user experience you can try a 3rd party component.
To quote the docs:
File uploads are not performed using normal "Ajax" techniques, that is
they are not performed using XMLHttpRequests. Instead the form is
submitted in the standard manner with the DOM element
temporarily modified to have its target set to refer to a dynamically
generated, hidden which is inserted into the document but
removed after the return data has been gathered.
To show real progress you can use onprogress callback of XMLHttpRequest:
Ext.override(Ext.data.request.Ajax, {
openRequest: function (options) {
var xhr = this.callParent(arguments);
if (options.progress) {
xhr.upload.onprogress = options.progress;
}
return xhr;
}
});
Then use like here:
Ext.Ajax.request({
url: '/upload/files',
rawData: data,
headers: { 'Content-Type': null }, //to use content type of FormData
progress: function (e) {
console.log('progress', e.loaded / e.total);
}
});
See online demo here.
buttons: [{
text: 'Upload',
handler: function () {
var form = this.up('form').getForm();
if (form.isValid()) {
form.submit({
url: '/upload/file',
waitMsg: 'Uploading your file...',
success: function (f, a) {
var result = a.result,
data = result.data,
name = data.name,
size = data.size,
message = Ext.String.format('<b>Message:</b> {0}<br>' +
'<b>FileName:</b> {1}<br>' +
'<b>FileSize:</b> {2} bytes',
result.msg, name, size);
Ext.Msg.alert('Success', message);
},
failure: function (f, a) {
Ext.Msg.alert('Failure', a.result.msg);
}
});
}
}
}]
Live demo with progress window is here