How to properly load gmail api to angular 2 app - angularjs

I am kind of new to angular 2 so ill try to explain the requirement in details.
The app I build has a login page (/login) and has the settings page (/settings).
when the user access the login page the gapi var is initialized properly then the user logs in to the app.
Once the user is in he has the settings page. the issue starts when the user refresh the page, when that happens the gapi var is no longer recognized and become undefined. My though is that the gapi library is not getting loaded and therefore it fails.
I placed the following code in the app index.html file
<script type="text/javascript">
// Client ID and API key from the Developer Console
var CLIENT_ID = '***.apps.googleusercontent.com';
// Array of API discovery doc URLs for APIs used by the quickstart
var DISCOVERY_DOCS = ["https://www.googleapis.com/discovery/v1/apis/gmail/v1/rest"];
// Authorization scopes required by the API; multiple scopes can be
// included, separated by spaces.
var SCOPES = 'https://www.googleapis.com/auth/gmail.readonly';
/**
* On load, called to load the auth2 library and API client library.
*/
function handleClientLoad() {
console.log("handleClientLoad")
gapi.load('client:auth2', initClient);
}
/**
* Initializes the API client library and sets up sign-in state
* listeners.
*/
function initClient() {
gapi.client.init({
discoveryDocs: DISCOVERY_DOCS,
clientId: CLIENT_ID,
scope: SCOPES
}).then(function () {
// Listen for sign-in state changes.
console.log("client init");
gapi.auth2.getAuthInstance().isSignedIn.listen();
// Handle the initial sign-in state.
//updateSigninStatus(gapi.auth2.getAuthInstance().isSignedIn.get());
});
}
</script>
<script async defer src="https://apis.google.com/js/api.js"
onload=this.onload=function(){};handleClientLoad();
onreadystatechange="if (this.readyState === 'complete') this.onload()";>
</script>
To conclude how can I properly load the gapi module to handle the above refresh scenario?
I tried to work with the solution from Best way to wait for 3rd-party JS library to finish initializing within Angular 2 service? however it didnt work, gapi is still undefined.

What I've done is create a custom GoogleService that is responsible for initializing the GAPI client in Angular apps. Instead of interacting with the GAPI client directly, my app interacts with the GoogleService.
For example (using Angular 9.x)
import { Injectable } from '#angular/core';
import { environment } from '../environments/environment';
#Injectable({
providedIn: 'root',
})
export class GoogleService {
private gapiAuth?: Promise<gapi.auth2.GoogleAuth>;
constructor() {
// Chrome lets us load the SDK on demand, but firefox will block the popup
// when loaded on demand. If we preload in the constructor,
// then firefox won't block the popup.
this.googleSDK();
}
async signinGoogle() {
const authClient = (await this.googleSDK()) as gapi.auth2.GoogleAuth;
const googleUser = await authClient.signIn();
const profile = googleUser.getBasicProfile();
return {
type: 'GOOGLE',
token: googleUser.getAuthResponse().id_token as string,
uid: profile.getId() as string,
firstName: profile.getGivenName() as string,
lastName: profile.getFamilyName() as string,
photoUrl: profile.getImageUrl() as string,
emailAddress: profile.getEmail() as string,
};
}
async grantOfflineAccess() {
const authClient: gapi.auth2.GoogleAuth = (await this.googleSDK()) as any;
try {
const { code } = await authClient.grantOfflineAccess();
return code;
} catch (e) {
// access was denied
return null;
}
}
// annoyingly there is some sort of bug with typescript or the `gapi.auth2`
// typings that seems to prohibit awaiting a promise of type `Promise<gapi.auth2.GoogleAuth>`
// https://stackoverflow.com/questions/54299128/type-is-referenced-directly-or-indirectly-in-the-fulfillment-callback-of-its-own
private googleSDK(): Promise<unknown> {
if (this.gapiAuth) return this.gapiAuth;
const script = document.createElement('script');
script.type = 'text/javascript';
script.async = true;
script.defer = true;
script.src = 'https://apis.google.com/js/api.js?onload=gapiClientLoaded';
this.gapiAuth = new Promise<void>((res, rej) => {
(window as any)['gapiClientLoaded'] = res;
script.onerror = rej;
})
.then(() => new Promise(res => gapi.load('client:auth2', res)))
.then(() =>
gapi.client.init({
apiKey: environment.google.apiKey,
clientId: environment.google.clientId,
discoveryDocs: environment.google.discoveryDocs,
scope: environment.google.scopes.join(' '),
}),
)
.catch(err => {
console.error('there was an error initializing the client', err);
return Promise.reject(err);
})
.then(() => gapi.auth2.getAuthInstance());
document.body.appendChild(script);
return this.gapiAuth;
}
}

Related

Fetching 301 Redirect Data In server.js (Express/NextJS)

I'm working on a project using express and next js and I've found a great example of how to setup an array of data for your redirects in your server.js file. However, if it is possible I would like to build a plugin within WordPress that will allow a user to submit data for redirects so that it could be managed by someone without technical knowledge. My question is, is it possible to fetch data within my server.js file to replace the data in this example?
const express = require('express')
const next = require('next')
const { join } = require('path')
const dev = process.env.NODE_ENV !== 'production'
const app = next({ dev })
const handle = app.getRequestHandler()
//This is the data I want to fetch through the WP Rest API
const redirects = [
{ from: '/old-link-1', to: '/new-link-1' },
{ from: '/old-link-2', to: 'https://externalsite.com/new-link-2' },
]
app.prepare().then(() => {
const server = express()
redirects.forEach(({ from, to, type = 301, method = 'get' }) => {
server[method](from, (req, res) => {
res.redirect(type, to)
})
})
server.get('*', (req, res) => {
return handle(req, res)
})
server.listen(3000, err => {
if (err) throw err
console.log('> Ready on http://localhost:3000')
})
})
Yes, I believe it's possible do do something like that.
This library would allow you to make an API request within express: https://github.com/request/request
Executed like so:
var request = require('request');
request('http://www.google.com', function (error, response, body) {
if (!error && response.statusCode == 200) {
console.log(body) // Print the google web page.
}
})
The next step would be to create an endpoint in wordpress with all of the 301's that you want to create:
function my_custom_endpoint(){
return 'Hey look, its some data';
}
// Register the rest route here.
add_action( 'rest_api_init', function () {
register_rest_route( 'yournamespace/v1', 'my_custom_endpoint',array(
'methods' => 'GET',
'callback' => 'my_custom_endpoint'
));
});
Good luck, and happy coding!
So, just incase anyone else stumbles upon the problem of programmatically adding redirects that originate on a WordPress install this is how it can be done. My tech stack is React, Next.js with an Express server pulling in data from a WordPress install that is living elsewhere in the webs.
WordPress:
1) Create a blank WordPress plugin (Google is your friend)
2) Create an activation hook within your plugin to create a database(Again, Google with 'to_url' and 'from_url' for each entry.
3) Register a Rest Route within plugin (Like described above with Tanner's answer)
This Rest Route should be pulling your info from the database and returning it as an array in this format:
[
{ 'from': '/about', 'to': '/about-us' },
{ 'from': '/test3', 'to': '/banks/testing-page' },
]
4) Create a plugin admin page with a form that allows the users to add entries to this database. As your database grows your response from the rest api will grow and your redirects will seamlessly be included in your project.
5) In your React server.js you're going to need the following setup
const request = require("request");
let redirects;
request('https://yourwebsite.com/wp-json/YOUR-ROUTE/v2/redirects', function (error, response, body) {
if (!error && response.statusCode == 200) {
redirects = JSON.parse(body);
}
})
redirects.forEach(({ from, to, type = 301, method = 'get' }) => {
server[method](from, (req, res) => {
res.redirect(type, to)
})
});
Caveats: Make sure when you're manipulating data with forms in php you're taking proper precautions to sanitize and escape everything.

Error: Timeout - Async callback was not invoked within timeout specified by jasmine.DEFAULT_TIMEOUT_INTERVAL

I am trying to test my Ionic app with Jasmine. This is my test suit.
beforeEach(() => {
auth = new Authentication(<any>new HttpMock(), <any>new StorageMock())
user = new MockUser();
header = new Headers({'uid': '1'});
resp = new Response( new ResponseOptions({body: {name: user.name }, headers: header}))
});
it('facebok login ',(done)=>{
spyOn(auth.http,'post').and.returnValue(HttpMock.returnValue(resp));
spyOn(Facebook,'login').and.returnValue(Promise.resolve({authResponse: {accessToken: 1}}))
auth.facebookLogin().then((res)=>{
expect(auth.http.post.calls.argsFor(0)).toEqual([CONFIG.url.facebookUrl,{}])
expect(res.authResponse.accessToken).toEqual(1);
done();
},(err)=>{
done();
});
});
My HttpMock class to mock http calls looks like this.
export class HttpMock {
static returnValue(data){
return Observable.create((observer) => {
observer.next(data);
})
}
}
The relevant part in the service I am testing is,
facebookLogin(): Promise<any>{
let permissions = ["public_profile","email"];
return Facebook.login(permissions)
.then( (response) => {
let token = { access_token: response.authResponse.accessToken };
return this.login( token ,'facebookUrl').toPromise();
}).catch( this.handleError);
login(data , urlKey): Observable<any>{
return this.http.post(CONFIG.url[urlKey], data)
.map( (res: Response) => this.saveUserInfo(res) ).catch( this.handleError)
}
saveUserInfo(res: Response): Response{
let userInfo = this.getUserInfo(res);
this.user = userInfo;
this.storage.set('user', userInfo);
return res;
}
The facebookLogin method goes like this. Access Facebook class login method which returns a promise. With information from the promise, I make http post request and save the returned data and then convert observable to promise with toPromise. In the test I spy on Facebook.login to return a resolving promise and spyOn http.post to return a successful observable. This is working fine in my app.But I am unable to run the test as it give the following error.
Error: Timeout - Async callback was not invoked within timeout specified by jasmine.DEFAULT_TIMEOUT_INTERVAL.
The code runs fine till the last point in http.post.map but then is not being run in the test. I think the problem is with the toPromise in the service.
Any kind of hep would be appreciated.
From my limited knowledge on Observable , I believe the problem with the approach was due to the fact that toPromise didnt get the value from observer.next(data). I assume subscription is necessary for that. The simple approach with Observable.of worked for me. You can import it from import 'rxjs/add/observable/of'

Can't retrieve anonymous authenticated user's info on first login

Logic: users select a few items on the homepage, then click a 'confirm' button which starts a Firebase $signInAnonymously() auth flow. Their selection is stored under their users/{uid} branch in the database tree and they are redirected to a checkout page that retrieves their selection and asks for more information to proceed.
Issue: when the user lands on the checkout page for the first time their auth state cannot be retrieved (so their selection doesn't appear). However when they refresh the page, everything works as expected for all subsequent attempts (their user info is stored and now retrievable)
Code:
User auth and selection setter/getter factory userService
var auth = $firebaseAuth();
var usersRef = $firebaseRef.users; // custom ref in app config
// Authenticate anonymously to create user session
function startSession() {
return auth.$signInAnonymously()
.then(function(user) {
return user;
})
.catch(function(error) {
console.log(error);
});
}
// Check authentication state before everything loads
function checkAuthState() {
return $q(function(resolve) {
var unsubscribe = auth.$onAuthStateChanged(function(user) {
if (user) {
unsubscribe();
resolve(user);
}
else {
console.log('User unidentified');
}
});
});
}
// Save user's menu selection into selection node of firebase
function saveSelection(items, user) {
var selectionRef = usersRef.child(user.uid).child('selection');
for (var i = 0, item; !!(item = items[i]); i++) {
var id = item.id;
if (item.selected) {
selectionRef.child(id).update(item);
}
else if (typeof(selectionRef.child(id)) !== 'undefined') {
selectionRef.child(id).remove();
}
}
}
// Get user's selection from firebase
function getSelection(user) {
var selectionRef = usersRef.child(user.uid).child('selection');
return $q(function(resolve) {
var selection = $firebaseArray(selectionRef);
resolve(selection);
});
}
Menu controller:
var menu = this;
menu.saveMenu = saveMenu;
// Save menu selection and create anonymous user on firebase
function saveMenu() {
var items = menu.items;
return userService.startSession()
.then(function(user) {
return userService.saveSelection(items, user);
});
}
Checkout controller:
// Get selected menu items from user data
function getCheckoutItems() {
return userService.checkAuthState()
.then(function(user) {
return userService.getSelection(user);
})
.then(function(selection) {
checkout.items = selection;
return checkout.items;
})
.catch(function(error) {
console.log(error);
});
}
I've looked through dozens of posts on SO before asking this. Here are a couple of the similar ones I've found:
Apparent race condition getting firebase.User from controller in Firebase 3.x
Handle asynchronous authentication in Firebase on page reload to get list that needs user's uid
I've also looked through the reference on GitHub to set it up:
https://github.com/firebase/angularfire/blob/master/docs/reference.md#firebaseauth
NB: I'm using the new version of Firebase with Angular 1.5.8:
"firebase": "^3.4.1",
"angularfire": "^2.0.2"
UPDATE
Got it. I had to add a resolve to my /checkout route to wait for authentication before loading elements on the page... Figured it out thanks to this answer by David East from the Firebase team.
resolve: {
// controller will not be loaded until $waitForSignIn resolves
"firebaseUser": function($firebaseAuthService) {
return $firebaseAuthService.$waitForSignIn();
}
}

How to prevent new anonymous authentication UID after browser refresh in Firebase

I am using anonymous authentication in Firebase with Angular. The goal is to have one UID associated with a user until the browser is closed. I would like to use the same UID even if the page is refreshed. However, when I use the code below, a new UID and token is created every time a user refreshes the page. How do I prevent this from happening?
myApp.factory('fbAuth', function($firebaseAuth) {
var ref = new Firebase('https://xxxxxxxx.firebaseio.com');
ref.authAnonymously(function (error, authData) {
if (error) {
console.log('Login Failed!', error);
} else {
console.log('Authenticated successfully with payload:', authData);
}
},
{remember: 'sessionOnly'
});
});
myApp.controller('ProjectListCtrl', function(Projects, fbAuth) {
var projectList = this;
projectList.projects = Projects;
});

I am using MEAN.IO stack, why can't I hit my data-access layer using require?

So, I am using mean.io and for some reason, my routes.js never hits my 'index.all' method, or the 'exports.all' function, even though I require the functions from the server-side controller. Also, my routing is done using angular-ui-router. Does anybody know how to call a backend method from routing in MEAN.IO? I keep using:
'use strict';
module.exports = function(System, app, auth, database) {
// Home route
var index = require('../controllers/index');
app.route('/test').get(index.all);
app.route('/')
.get(index.render);
};
I would like to hit 'index.all' but even if I navigate to /test, it still gets
index.render. Does anybody know why?
Here is the controllers file:
'use strict';
var mean = require('meanio');
var mongoose = require('mongoose');
var Composition = mongoose.model('Composition');
exports.render = function(req, res) {
console.log(req.user);
var modules = [];
// Preparing angular modules list with dependencies
for (var name in mean.modules) {
modules.push({
name: name,
module: 'mean.' + name,
angularDependencies: mean.modules[name].angularDependencies
});
}
function isAdmin() {
return req.user && req.user.roles.indexOf('admin') !== -1;
}
// Send some basic starting info to the view
res.render('index', {
user: req.user ? {
name: req.user.name,
_id: req.user._id,
username: req.user.username,
roles: req.user.roles
} : {},
modules: modules,
isAdmin: isAdmin,
adminEnabled: isAdmin() && mean.moduleEnabled('mean-admin')
});
};
exports.all = function(req, res) {
console.log(req.user);
Composition.find({user: req.user}, 'title description').sort('-created').populate('user', 'name username').exec(function(err, compositions) {
if (err) {
return res.jsonp(500, {
error: 'Cannot list the compositions'
});
}
res.jsonp(compositions);
});
};
Is this a front-end or backend problem? Thanks for any advice that might be helpful.
You are navigating. So are you hitting the link in the browser url? Then you should try localhost:3000/test instead of localhost:3000/#!/test.
The urls of the form localhost:3000:/#!/<something> are angular routes. Look up angular routing and views. It is better to use angular views than server side rendering. Do angular routing for test and add a view corresponding to it. Fetch the dynamic data in the view using the regular $http.get calls.
Check this tutorial for routing and adding views in angular

Resources