I am trying to do a simple google home integration with my home server. My only goal is to have it say "Hey google, turn on pool lights" and "Hey Google, turn off pool lights". Both should be mapped to https://mywebsite.com/fullfillment. Once it hits my express server which is running actions-on-google (code below) I have sample code that should properly respond.
Right now whenever I try to create a webhook fulfillment, I just get an error. What am I missing here to just have google home interact with my home server? I've watched countless tutorial and read articles but none really work with remote webhooks, most are fulfillment through firebase or through the inline editor. I have setup OAth as well in the activity linking but I still get the same error "We're sorry but something went wrong. Please try again".
I have also made sure my app and activity controls are enabled as well. I'm just trying to get the action to recognize the webhook and not through this error, if the error at least had some information I could move forward. The "Logs in Google Cloud" also show nothing at all. I have also changed the invocation to a long more custom name and still same error.
Below is a video showing me running through creation of a new Smart Home action on a fresh google account with a postman clip hitting my home server with the correct payload. I've tried this with multiple variations of the webhook url, ngrok was just the latest attempt.
https://youtu.be/Txv1HmP0yW4
Code for my express server below.
const express = require('express');
const bodyParser = require('body-parser');
const {
dialogflow,
actionssdk,
Image,
Table,
Carousel,
} = require('actions-on-google');
const app = dialogflow({
debug: true
});
app.intent('Default Welcome Intent', (conv) => {
conv.ask('How are you?');
});
app.intent('bye', (conv) => {
conv.close('See you later!');
});
app.catch((conv, error) => {
console.error(error);
conv.ask('I encountered a glitch. Can you say that again?');
});
app.fallback((conv) => {
conv.ask(`I couldn't understand. Can you say that again?`);
});
const expressApp = express().use(bodyParser.json());
expressApp.post('/fulfillment', app);
expressApp.listen(1349, () => {
console.log("Test on port 1349");
})
If you have created an Actions on Google Project using dialogflow or Assistant conversational action, it won’t work as a Smart Home integration anymore. Please create a new project in the Actions on Google Console, selecting Smart Home as the project type.
Once you create your action, if you intend to build your own server handling smart home requests, a good way to start could be with the Smart Home Washer codelab. You can replace the fulfillment url to your own server, keeping the firebase database and everything else the same. Once you verify you can handle fulfillment requests, you can carry over the rest of the pieces (OAuth) to your server as well. If you get stuck debugging the smart home fulfillments, you can take a look at the Troubleshooting guide.
Related
I have an e-commerce customer who uses Shopify APIs to create an order (a customer who purchased an item).
From a created command (which can contain different items), I must generate a file with some attributes like weight or the destination address.
I was drowning in their public documentation and honestly can't get over it (I don't have much programming experience).
I would like to know which APIs (or services...) I have to request in your opinion please?
Given what I'm being asked, I don't know if I can find all this info with a single API or I have to call several methods to build my file little by little...
For example, I have no idea, really no idea to know if a given command is fragile or not. I don't even know if they communicate such information by their API.
Thanks in advance
If you want realtime (as soon as the order is created, the file is created) : You need to register a webhook.
A Shopify webhook is simply a script that, on a certain event, send the event's data to a link.
Example : An order placed webhook (predefined Shopify webhook) will send the order's data to an URL in JSON format as soon as the order is placed.
So you will need a server running 24/7 (get a VPS for less than 5 bucks a month). The server will listen at an ip at a certain port.
You will setup Shopify's webhook to send data to that ip / port.
Step 1: Create your server
You will need any backend framework for this. I choose NodeJs + Express framework, because it is so easy to do with it.
Here is a sample code :
const express = require('express');
const bodyParser = require('body-parser');
const app = express();
const port = 3000;
app.use(bodyParser.json());
app.post('/', function (req, res) {
let body = req.body;
console.log(body);
});
let server = app.listen(port, function () {
let host = server.address().address;
console.log('Example app listening at http://%s:%s', host, port); //Copy this to your clipboard, you will need it in step 2
});
Step 2: Register Shopify Webhook
Go to your client's store, add /admin/settings/notifications to the url.
Scroll to the bottom of the page, click Create Webhook, chose Order creation as an event, JSON format, Lastest as API version. Paste the IP / Port you copied earlier into URL.
Step 3: Check the data sent by Shopify
From here, you can check the data Shopify sent you. It will be printed by your NodeJs server.
It is in JSON format, so you can do all kinds of stuff with it. Filter important info, and output to file for example.
If you want to check everytime your script starts and not realtime : you need to create a custom app in your client's store, and use GraphQL
Shopify won't let you check for certain orders by order number, and the limit of their API is 250. Using GraphQL, you can do this, but you need to know GraphQL (a little more advanced).
You can check their docs at shopify.dev
Using Nuxt 2.15.3, with a Rails backend.
I'm trying to create a Google OAuth workflow in my app, and I'm having some trouble with the steps after getting the access code. Once the user authenticates with Google and gets redirected back with the access code in the URL params, I send a request to my own backend to save the access/refresh tokens in the User model.
NOTE: this google Auth flow is separate from my normal app sign-in/sign-up flow. I'm only using some Google APIs, so this has nothing to do with making a user account via Google OAuth, I'm only requesting access to some APIs in the user's Google account, namely the My Business API.
Now, my backend has a boolean google_authenticated field on the User table which gets set to true if the access/refresh token exists, which automatically gets sent to Nuxt as $auth.user.google_authenticated. This feature works fine, but my problem is that the page where the users get redirected to has a v-if that checks for this google_authenticated flag.
The template looks like this, obviously simplified for the sake of the question
<template>
<div v-if="googleAuthenticated">...</div>
<div v-else><a href="googleapis.com">Authenticate</button></div>
</template>
export default {
data() {
return {
googleAuthenticated: this.$auth.user.googleAuthorized,
};
},
async mounted() {
const accessCode = this.$route.query.code;
await this.$axios
.post("/users/google_oauth", {
access_code: accessCode,
})
.then((response) => {
this.$auth.fetchUser();
});
}
}
So as you can see, what I'm trying to do is to refresh the $auth.user.googleAuthorized automatically on mount when the user gets to the page with the code in the URL params. The problem is that $auth.user doesn't seem to be reactive, and the user needs to either navigate to another page or refresh the current page to get these changes to show up and for the v-if to trigger and to show the other div.
I can see from the dev console that the fetchUser() method does indeed get called, and I can see from the Vuex store that the auth/SET function has been called and that the $auth.user.googleAuthorized flag is set to true as well.
According to the Nuxt Docs, the $auth module is reactive, but I'm failing to see it. What can I do here to make these changes stick properly?
So it turns out it's a race condition (sorta).
What's happening is that once I tell my own API to get the access tokens from Google, that request obviously takes some non-zero amount of time. As such, Vue is waiting to get the 200 from my own API indicating that I've fetched the access token and can continue with using Google APIs.
So basically, by the time Nuxt is calling fetchUser() again, it's actually getting the user without the googleAuthorized flag set to true, because that just happens a tiny bit too slowly. It's not really something noticeable when you're just looking at dev tools though, since everything seems to be happening instantaneously more or less, but the timings are extremely close, which is the reason why it would sometimes work, and other times it wouldn't without a refresh.
I am building an app using Nativescript-Vue that requires authentication of users in order to use the app. I have a RESTful backend that functions appropriately as tested with Postman.
JWT Tokens are implemented with a perpetual life but require refreshing every 5 minutes (refresh functionality - in a vaccuum -- working appropriately).
Using Axios.js for web calls.
I am stuck on how to implement basic logic for determining if the user is logged in. All Axios calls return a Promise. I have read the extended "Promise" responses a bunch, but it's not sinking into my head how to do what I want. In a nutshell, I need to pause code execution until the API can authenticate the user and this is not computing for me.
Code as follows:
app.js
// Import VUE library
import Vue from "nativescript-vue";
// This is my user handler
import {UserServices} from "./assets/js/UserServices.js"
// imported Components
import Login from "./components/Login";
import Home from "./components/Home.vue"
let user = new UserServices();
let loggedIn = user.checkAuthStatus();
new Vue({
render: h => h('Frame', [
h(
loggedIn ? Home : Login
)
])
}).$start();
This isn't working because user.checkAuthStatus() is an async function that returns a promise and thus I cannot get a boolean value returned. I know this is the problem, but I left that in the code so that the intended result can be understood. What I don't understand is how to rewrite the code so that the designed flow is feasible using Promises.
Core logic is designed to be:
Check the user's logged-in status via user.checkAuthStatus(). This routine checks for a valid token (valid meaning it exists and is not expired). If it is expired, the token is refreshed via a call to this.refresh() from the UserServices controller.
If a value of "true" is returned from user.checkAuthStatus() the Vue app should load the Home component (aka user is logged in), else the user should be required to login.
I can only imagine that this is a simple situation thousands of people have successfully overcome, but my brain isn't working thru it. I get why JS needs to continue running so as not to stop the progress of the code (and that's the point of a Promise, I think?), but sometimes the code just needs to stop and wait it seems, like in a user authentication scenario.
Any help drilling down on the specifics on how to address my challenge? Please and thank you.
I would do like this:
Check if a locally stored token exists, valid and not expired, if yes go to homepage else redirect to login.
So this type of error is being reported on a lot of community boards over the course of the last year with no acceptable answer we could find. We have just started our journey integrating with Google Home and created a Home Automation Action and we are getting a similar error …
{
insertId: "10wvnj2fyb1thy"
logName: "projects/bitdog-home-f69bd/logs/actions.googleapis.com%2Factions"
Show matching entries
Hide matching entries
Add field to summary line
receiveTimestamp: "2018-12-06T13:28:13.939975519Z"
resource: {
labels: {
action_id: "SMART_HOME_SYNC"
project_id: "bitdog-home-f69bd"
version_id: ""
}
type: "assistant_action"
}
severity: "ERROR"
textPayload: "SYNC: Request ID 742344561631232315 failed with code: OPEN_AUTH_FAILURE"
timestamp: "2018-12-06T13:28:13.931998358Z"
}
This shows on Google Home app as "Couldn't update the setting, check your connection"
The OAuth service logs show a successful account linking and a successful refresh_token request. Google does not attempt a SYNC call to the Action handler from what we can tell.
We have other systems using the OAuth server and they are working well and we are little lost on how to proceed to debug this issue. We created a support ticket today but I don't feel confident that we will get meaningful help.
We have also tried using the Google Home app on Android and iOS. We have tried changing the default browser from Chrome to Firefox. Nothing has changed the outcome. We also made sure that our access_token was in JWT format to see if google was sensitive to token size or format and nothing worked. We even made sure that the Google Home app user matched the user logged into the browser.
Help!
I did get it working. It was already working with an Amazon Echo Skill but it seems that Google's implementation (OpenAuth) is a bit more strict. I changed my access_token from a proprietary encrypted token format to a legit signed JWT token. I also removed expires_in from the response and it started working, not sure if it was the access_token JWT token format or removal of expires_in. I'm happy I can move on. If I get a chance, I will test to see which change made it work and comment here again.
Thank you.
To anyone with this problem–
I had to take multiple steps to resolve this issue, which are not clearly outlined in any documentation.
As per Google support:
Please adjust your account linking implementation from implicit to auth code flow then perform test again.
On the documentation for OAuth account linking, it says there are two methods of authentication: implicit and auth code. Apparently, only the auth code flow works for smart home.
I am using the Actions on Google Node.js library. While poking through the documentation, I found that:
[The SYNC request fulfillment] should return a valid response or a Promise that resolves to valid response.
The problem is that I was doing a database operation (which took time), so I couldn't simply return a value when it was ready; I had to return a Promise insead, then fulfill that promise later.
Hopefully this is helpful to anyone stuck on this reoccurring issue! Basically, check your auth flow and make SYNC is returning a valid JS object on time.
I was facing the same issue from last 2 weeks and was wonder when saw it is a 3 steps problem.
Check your SYNC intent is properly parsed
Incorrect Response Structure (Verify here-Smart Home SYNC Data Validator)
Device Response time-out should be less than 5 sec.
You can check Link
My problem started when I connected by Sonoff Bridge.
So I got it working by removing my 'Sonoff Bridge' and connecting it to Google Home. (All mu light are now working). Added the Bridge again to Sonoff and using IFTTT to connect to my Bridge
So we're running into something we dont really understand: When we try to login to facebook, using an account that is already linked (has both Google and Facebook providers), we get the error: auth/account-exists-with-different-credential'. This is using v4.12.1 of the JS SDK.
Code
async loginFacebook() {
const facebookProvider = this.getFacebookProvider();
await this.auth.signInWithRedirect(facebookProvider);
// The error is thrown here, and then caught by the .catch(), which is unexpected, because as mentioned, the accounts are already linked.
const result = await this.afAuth.auth.getRedirectResult()
.catch(this.linkIfDuplicateAccount);
// ... rest of code left out, as its irrelevant.
}
Now before the accounts were linked, we'd expect the above error to be thrown; but after they are linked!? Why does that happen?
A bit of bonus info. Our initial implementation was based on Firebase JS API auth - account-exists-with-different-credential and it seemed to work, only recently did we notice the above.
Also, when we log out a user, we purge the local indexdb database, as our devices can be shared between multiple users, but I don't see that being the problem. Mentioned anyway.