Can an interceptor be assigned to one intent only?
Can I redirect an interceptor to another intent?
I created a respose interceptor that check if user won the game and redirect from interceptor to another intent. The APL template of intent before interceptor is displayed but the audio that alexa speak is from the intent i redirected. And the APL template of redirected intent is not displayed.
Can an interceptor be assigned to one intent only?
Yes you can, you need to use the same logic as the function canHandle within your interceptor:
canHandle(handlerInput: Alexa.HandlerInput) {
// reuse this code
return (
Alexa.getRequestType(handlerInput.requestEnvelope) === "IntentRequest" &&
Alexa.getIntentName(handlerInput.requestEnvelope) === "HelloWorldIntent"
);
},
Can I redirect an interceptor to another intent?
Not exactly but you can do it differently. An interceptor does not reply to the request. It just intercept the request so your Intent can handle it. The intent is replying to request.
So you can put the logic on the session in the interceptor and the intent will know if the user has won the game or not.
Interceptor
const MyAwesomeInterceptor = {
process(handlerInput) {
const { attributesManager, requestEnvelope } = handlerInput;
const sessionAttributes = attributesManager.getSessionAttributes();
// Code logic ...
sessionAttributes.hasWon = true
}
}
Intent
const HelloWorldIntentHandler = {
canHandle(handlerInput) {
return (
Alexa.getRequestType(handlerInput.requestEnvelope) === "IntentRequest" &&
Alexa.getIntentName(handlerInput.requestEnvelope) === "HelloWorldIntent"
);
},
handle(handlerInput) {
const { attributesManager } = handlerInput;
const sessionAttributes = attributesManager.getSessionAttributes();
if (sessionAttributes.hasWon) {
return handlerInput.responseBuilder.speak("You won").getResponse();
} else {
return handlerInput.responseBuilder.speak("You lost").getResponse();
}
},
};
I recommend you to check the doc to have a better understanding on how Alexa work.
Related
The Current code looks like does Cache first Strategy, How to modify it use Network first and than fallback to cache if network fails ?
async function onFetch(event) {
let cachedResponse = null;
if (event.request.method === 'GET') {
// For all navigation requests, try to serve index.html from cache
// If you need some URLs to be server-rendered, edit the following check to exclude those URLs
//const shouldServeIndexHtml = event.request.mode === 'navigate';
console.log("onFetch : " + event.request.url.toLowerCase());
const shouldServeIndexHtml = event.request.mode === 'navigate';
const request = shouldServeIndexHtml ? 'index.html' : event.request;
const cache = await caches.open(cacheName);
cachedResponse = await cache.match(request);
}
return cachedResponse || fetch(event.request);
}
if (event.request.url.indexOf('/api') != -1) {
try {
// Network first
var response = await fetch(event.request);
// Update or add cache
await cache.put(event.request, response.clone());
// Change return value
cachedResponse = response;
}
catch (e)
{
}
}
You can add something like this after:
cachedResponse = await cache.match(request);
This should always load api requests from the network first, since it's not part of the cache initially. Every time the cache is renewed for this request. If the request fails, the cached value will be used.
I am trying to redirect a user to the login if he isn't authenticated. I hardcoded the jwt for now. This works, but I only get an error saying Error [ERR_HTTP_HEADERS_SENT]: Cannot set headers after they are sent to the client.
Since the function works I don't know what is wrong and couldn't really find an answer to it either. This is my code for reference:
function redirectUser(ctx, location) {
if (ctx.req) {
ctx.res.writeHead(302, { Location: location });
ctx.res.statusCode = 302;
ctx.res.setHeader(302, location);
ctx.res.end();
return { props: {} };
} else {
Router.push(location);
}
}
// getInitialProps disables automatic static optimization for pages that don't
// have getStaticProps. So article, category and home pages still get SSG.
// Hopefully we can replace this with getStaticProps once this issue is fixed:
// https://github.com/vercel/next.js/discussions/10949
MyApp.getInitialProps = async (ctx) => {
const jwt = false;
// Calls page's `getInitialProps` and fills `appProps.pageProps`
const appProps = await App.getInitialProps(ctx);
// Fetch global site settings from Strapi
const global = await fetchAPI("/global");
if (!jwt) {
if (ctx?.ctx.pathname === "/account") {
redirectUser(ctx.ctx, "/login");
}
}
// Pass the data to our page via props
return { ...appProps, pageProps: { global } };
};
Any help would be much appreciated.
The error "Error: Can't set headers after they are sent." means that you're already in the body, but some other function tried to set a header or statusCode. In your case it is the function ctx.res.setHeader(302, location); that's causing the issue.
After writeHead, the headers are baked in and you can only call res.write(body), and finally res.end(body).
You do not need to use setHeader when you are already using the writehead method.
Read here more about the writehead
So your redirectUser could be like :
function redirectUser(ctx, location) {
if (ctx.req) {
ctx.res.writeHead(302, { Location: location });
ctx.res.end();
return { props: {} };
} else {
Router.push(location);
}
}
I've been trying to get my refresh token to work for a while now, and I hope I'm close. My token refreshes and triggers a subsequent 200 call to whatever call caused the 401, but my the data on my page doesn't refresh.
When an access token expires, the following happens:
After the 401, the GetListofCompanyNames returns 200 with a list of names using the correct updated access token. However, my dropdown does not refresh.
My interceptor:
app.factory('authInterceptorService',['$q', '$location', 'localStorageService', '$injector', function($q, $location, localStorageService, $injector) {
return {
request: function(config) {
config.headers = config.headers || {};
var authData = localStorageService.get('authorizationData');
if (authData) {
config.headers.Authorization = 'Bearer ' + authData.token;
}
return config;
},
responseError: function(rejection) {
//var promise = $q.reject(rejection);
var authService = $injector.get('authService');
if (rejection.status === 401) {
// refresh the token
authService.refreshToken().then(function() {
// retry the request
var $http = $injector.get('$http');
return $http(rejection.config);
});
}
if (rejection.status === 400) {
authService.logOut();
$location.path('/login');
}
return $q.reject(rejection);
}
};
}
]);
My return statement on the 401 rejection looks suspect here, but I'm not sure what to replace it with. Thereby my question is: How can I get my page to refresh it's data when I make the new call?
Update:
This gets me past when the 200 returns and I can get a dropdown to refresh, but I lose any state on the page (ex. selected dropdown) with the below.
authService.refreshToken().then(function() {
var $state = $injector.get('$state');
$state.reload();
});
Back to the drawing board!
Try putting up your retry call in $timeout, it should work.
Here's the updated code:
app.factory('authInterceptorService',['$q', '$location', 'localStorageService', '$injector', function($q, $location, localStorageService, $injector) {
return {
request: function(config) {
config.headers = config.headers || {};
var authData = localStorageService.get('authorizationData');
if (authData) {
config.headers.Authorization = 'Bearer ' + authData.token;
}
return config;
},
responseError: function(rejection) {
//var promise = $q.reject(rejection);
var authService = $injector.get('authService');
if (rejection.status === 401) {
// refresh the token
authService.refreshToken().then(function() {
// retry the request
return $timeout(function() {
var $http = $injector.get('$http');
return $http(rejection.config);
}});
}
if (rejection.status === 400) {
authService.logOut();
$location.path('/login');
}
return $q.reject(rejection);
}
};
}
]);
$timeout returns a promise that is completed with what is returned
from the function parameter, so we can conveniently just return the
$http call wrapped in $timeout.
Thanks.
I think you may want to change up how you go about this. One way to go about this would be to inject the $rootScope into your authInterceptorService and then once you successfully refresh the token, call something like $rootScope.broadcast('tokenRefreshed').
I don't quite know how you have set up the view and controller that handles your dropdown, but I would set up a listener for that 'tokenRefreshed' event. From here, you can do another call to GetListofCompanyNames. If you do it this way you can easily control and ensure that the model gets updated.
My final solution:
app.factory('authInterceptorService', ['$q', '$location', 'localStorageService', '$injector', function($q, $location, localStorageService, $injector) {
var $http;
var retryHttpRequest = function(config, deferred) {
$http = $http || $injector.get('$http');
$http(config).then(function(response) {
deferred.resolve(response);
},
function(response) {
deferred.reject(response);
});
}
return {
request: function(config) {
config.headers = config.headers || {};
var authData = localStorageService.get('authorizationData');
if (authData) {
config.headers.Authorization = 'Bearer ' + authData.token;
}
return config;
},
responseError: function(rejection) {
var deferred = $q.defer();
if (rejection.status === 401) {
var authService = $injector.get('authService');
authService.refreshToken().then(function() {
retryHttpRequest(rejection.config, deferred);
},
function () {
authService.logOut();
$location.path('/login');
deferred.reject(rejection);
});
} else {
deferred.reject(rejection);
}
return deferred.promise;
}
};
}
]);
Copied almost 1 for 1 from https://github.com/tjoudeh/AngularJSAuthentication/blob/master/AngularJSAuthentication.Web/app/services/authInterceptorService.js .
This one transparently handles all requests and refreshes them when necessary. It logs out users when the refresh token is expired and passes errors along to the controllers by properly rejecting them. However, it doesn't seem to work with multiple in flight requests, I'll look into that when I get a use case for it in my system.
I'm using Firebase Cloud Messaging + Service worker to handle background push notifications.
When the notification (which contains some data + a URL) is clicked, I want to either:
Focus the window if it's already on the desired URL
Navigate to the URL and focus it if there is already an active tab open
Open a new window to the URL if neither of the above conditions are met
Points 1 and 3 work with the below SW code.
For some reason point #2 isn't working. The client.navigate() promise is being rejected with:
Uncaught (in promise) TypeError: Cannot navigate to URL: http://localhost:4200/tasks/-KMcCHZdQ2YKCgTA4ddd
I thought it might be due to a lack of https, but from my reading it appears as though localhost is whitelisted while developing with SW.
firebase-messaging-sw.js:
// Give the service worker access to Firebase Messaging.
// Note that you can only use Firebase Messaging here, other Firebase libraries
// are not available in the service worker.
importScripts('https://www.gstatic.com/firebasejs/3.5.3/firebase-app.js');
importScripts('https://www.gstatic.com/firebasejs/3.5.3/firebase-messaging.js');
// Initialize the Firebase app in the service worker by passing in the
// messagingSenderId.
firebase.initializeApp({
'messagingSenderId': 'XXXX'
});
const messaging = firebase.messaging();
messaging.setBackgroundMessageHandler(payload => {
console.log('[firebase-messaging-sw.js] Received background message ', payload);
let notificationData = JSON.parse(payload.data.notification);
const notificationOptions = {
body: notificationData.body,
data: {
clickUrl: notificationData.clickUrl
}
};
return self.registration.showNotification(notificationData.title,
notificationOptions);
});
self.addEventListener('notificationclick', event => {
console.log('[firebase-messaging-sw.js] Notification OnClick: ', event);
// Android doesn’t close the notification when you click on it
// See: http://crbug.com/463146
event.notification.close();
// This looks to see if the current is already open and
// focuses if it is
event.notification.close();
let validUrls = /localhost:4200/;
let newUrl = event.notification.data.clickUrl || '';
function endsWith(str, suffix) {
return str.indexOf(suffix, str.length - suffix.length) !== -1;
}
event.waitUntil(
clients.matchAll({
includeUncontrolled: true,
type: 'window'
})
.then(windowClients => {
for (let i = 0; i < windowClients.length; i++) {
let client = windowClients[i];
if (validUrls.test(client.url) && 'focus' in client) {
if (endsWith(client.url, newUrl)) {
console.log('URL already open, focusing.');
return client.focus();
} else {
console.log('Navigate to URL and focus', client.url, newUrl);
return client.navigate(newUrl).then(client => client.focus());
}
}
}
if (clients.openWindow) {
console.log('Opening new window', newUrl);
return clients.openWindow(newUrl);
}
})
);
});
The vast majority of my SW code is taken from:
https://gist.github.com/vibgy/0c5f51a8c5756a5c408da214da5aa7b0
I'd recommend leaving out includeUncontrolled: true from your clients.matchAll().
The WindowClient that you're acting on might not have the current service worker as its active service worker. As per item 4 in the specification for WindowClient.navigate():
If the context object’s associated service worker client’s active
service worker is not the context object’s relevant global object’s
service worker, return a promise rejected with a TypeError.
If you can reproduce the issue when you're sure the client is currently controlled by the service worker, then there might be something else going on, but that's what I'd try as a first step.
This worked for me:
1- create an observable and make sure not to call the messaging API before it resolves.
2- register the service worker yourself, and check first if its already registered
3- call event.waitUntil(clients.claim()); in your service worker
private isMessagingInitialized$: Subject<void>;
constructor(private firebaseApp: firebase.app.App) {
navigator.serviceWorker.getRegistration('/').then(registration => {
if (registration) {
// optionally update your service worker to the latest firebase-messaging-sw.js
registration.update().then(() => {
firebase.messaging(this.firebaseApp).useServiceWorker(registration);
this.isMessagingInitialized$.next();
});
}
else {
navigator.serviceWorker.register('firebase-messaging-sw.js', { scope:'/'}).then(
registration => {
firebase.messaging(this.firebaseApp).useServiceWorker(registration);
this.isMessagingInitialized$.next();
}
);
}
});
this.isMessagingInitialized$.subscribe(
() => {
firebase.messaging(this.firebaseApp).usePublicVapidKey('Your public api key');
firebase.messaging(this.firebaseApp).onTokenRefresh(() => {
this.getToken().subscribe((token: string) => {
})
});
firebase.messaging(this.firebaseApp).onMessage((payload: any) => {
});
}
);
}
firebase-messaging-sw.js
self.addEventListener('notificationclick', function (event) {
event.notification.close();
switch (event.action) {
case 'close': {
break;
}
default: {
event.waitUntil(clients.claim());// this
event.waitUntil(clients.matchAll({
includeUncontrolled: true,
type: "window"
}).then(function (clientList) {
...
clientList[i].navigate('you url');
...
}
}
}
}
On my .NET Web API 2 server, I am using OWIN for authentication. I have followed Taiseer's tutorial and successfully implemented an access token refresh mechanism.
I would like to know if there are any impacts on anything if clients refresh their access tokens frequently, e.g. refresh once every 5 minutes on average.
I am asking this question because I have a button on my page, when user clicks it, the data on that page is sent to different endpoints. These endpoints are marked with the attribute [Authorize].
Previously, when I send a request to a single protected endpoint, I can check if the response is 401 (unauthorized). If so, I can refresh the user's access token first, then resend the rejected request with the new token. However, I don't know how can the same thing be done this time, as there are so many requests being sent at once. The aforementioned method is implemented in my AngularJS interceptor. It can handle a single but not multiple rejected unauthorized requests.
FYI, here is the code for my interceptor, which is found and modified from a source on GitHub.
app.factory('authInterceptor', function($q, $injector, $location, localStorageService) {
var authInterceptor = {};
var $http;
var request = function(config) {
config.headers = config.headers || {};
var jsonData = localStorageService.get('AuthorizationData');
if (jsonData) {
config.headers.Authorization = 'Bearer ' + jsonData.token;
}
return config;
}
var responseError = function(rejection) {
var deferred = $q.defer();
if (rejection.status === 401) {
var authService = $injector.get('authService');
authService.refreshToken().then(function(response) {
_retryHttpRequest(rejection.config, deferred);
}, function() {
authService.logout();
$location.path('/login');
deferred.reject(rejection);
});
} else {
deferred.reject(rejection);
}
return deferred.promise;
}
var _retryHttpRequest = function(config, deferred) {
$http = $http || $injector.get('$http');
$http(config).then(function(response) {
deferred.resolve(response);
}, function(response) {
deferred.reject(response);
});
}
authInterceptor.request = request;
authInterceptor.responseError = responseError;
return authInterceptor;
});