Google login in Angular14 with #abacritt/angularx-social-login and the newer Google GIS library - oauth-2.0

I want to setup google login in my Angular14 web application with #abacritt/angularx-social-login#1.2.5. I see that the old Google Sign-In library for Web is deprecated and I want to use the newer one Google Identity Services library. My login is working but I want to do it in redirect mode and not in "pop-up" mode. How I can do that ?
NB: I use in my login page the button asl-google-signin-button.
You can find attach my used code:
app.module.ts:
import { GoogleLoginProvider, SocialAuthServiceConfig, SocialLoginModule } from '#abacritt/angularx-social-login';
#NgModule({
declarations: [
...
],
imports: [
...
],
providers: [
{
provide: 'SocialAuthServiceConfig',
useValue: {
autoLogin: false,
providers: [
{
id: GoogleLoginProvider.PROVIDER_ID,
provider: new GoogleLoginProvider(
'my_client_id'
)
}
],
} as SocialAuthServiceConfig
}
],
bootstrap: [AppComponent],
})
export class AppModule { }
login.component.html:
<div class="google-btn">
<asl-google-signin-button
type='standard'
shape="rectangular"
size='medium'
theme="filled_blue"
text="signin_with"
locale="en-GB"
size="large"
logo_alignment="left">
</asl-google-signin-button>
</div>
login.component.ts:
ngOnInit(): void {
this.authService.connectionGoogle().subscribe({
next: (data: User) => {
console.log(data)
this.router.navigateByUrl('home');
},
error: (error: any) => {
console.log(error)
this.router.navigateByUrl('unauthorized')
}
})
}
auth.service.ts:
connectionGoogle(): Observable<User> {
return this.socialAuthService.authState.pipe(
concatMap((res: SocialUser) => {
return this.googleLoginAndGetUser(res.idToken)
}),
tap(res => this.setUser(res))
)
}
googleLoginAndGetUser(data: string): Observable<User> {
return this.googleLogin(data).pipe(
concatMap((res: LoginResponse) => {
this.setAccessToken(res.access_token);
this.setRefreshToken(res.refresh_token);
const parseJwt = JSON.parse(atob(res.access_token.split('.')[1]));
const idUser = parseJwt.sub;
return this.getUser(idUser)
)
})
)
}
googleLogin(idToken: string) {
const obj = {'idToken': idToken};
return this.httpClient.post<any>(this.url_user + 'glogin', obj)
}
getUser(idUser: number): Observable<User> {
return this.httpClient.get<User>(this.url_user + idUser)
}
and the screen of the client ID of my application :
what you can suggest me to do redirect mode ?
Don't hesitate if you know other libraries that allow to do that.
Thanks in advance.

Related

NestJS Standalone app can't inject Sequelize instance using connection token

I'm still quite new to NestJS. I'm trying to implement a standalone app that connect to both external/remote source DB and the app DB.
Now I got stuck at Nest can't resolve dependencies of the SourceDbQueryService (ModuleRef, ?). Please make sure that the argument {{token}} at index [1] is available in the EtlModule context.
The {{token}} here is supposedly a string returned from getConnectionToken(connectionName), ex.: sourceDbConnection when connectionName = sourceDb
Here are my modules setup example:
/src/db/source-db-module.ts
#Module({
imports: [
ConfigModule,
LoggingModule,
SequelizeModule.forRootAsync({
imports: [ConfigModule],
inject: [SourceDbConfig],
useFactory: (config: SourceDbConfig) => {
return {
...config,
name: SourceDbConfig.DefaultConnectionName,
autoLoadModels: false,
}
},
}),
],
exports: [SequelizeModule],
})
export class SourceDbModule {}
/src/jobs/etl-module.ts
#Module({
imports: [
ConfigModule,
LocalDbModule,
/** Contains Local repositories with decorated Models, using connection from LocalDbModule */
RepositoryModule,
SourceDbModule,
SequelizeModule.forFeature([], SourceDbConfig.DefaultConnectionName),
],
providers: [
{
provide: SourceDbQueryService,
inject: [ModuleRef, getConnectionToken(SourceDbConfig.DefaultConnectionName)],
useFactory(moduleRef: ModuleRef, sequelize: Sequelize) {
return new SourceDbQueryService(moduleRef, sequelize)
},
},
],
exports: [SourceDbQueryService],
})
export class EtlModule {}
/src/jobs/test-query-source-db.ts
async function bootstrap(): Promise<void> {
try {
const appContext = await NestFactory.createApplicationContext(EtlModule)
appContext.init()
const sourceDb = appContext.get(SourceDbQueryService)
const totalRecordsCount = await sourceDb.count({
// ...filters,
})
console.log(
`retrieved source DB results: (total items: ${totalItemsCount})`
)
appContext.close()
} catch (err) {
console.error(err)
process.exit(-1)
}
}
bootstrap()
Please help, what am I missing here?
Thanks!
Update: Workaround
For now I'm using a workaround by providing Sequelize instance directly from my own factory like this:
/src/db/source-db-module.ts
#Module({
imports: [
ConfigModule,
LoggingModule,
],
providers: [
// WORKAROUND: For SequelizeModule.forRootAsync() injection by connection token not working
{
provide: getConnectionToken(SourceDbConfig.DefaultConnectionName),
inject: [SourceDbConfig],
useFactory(config: SourceDbConfig) {
const { host, port, username, password, database, dialect } = config
return new Sequelize({
host,
port,
username,
password,
database,
dialect,
})
},
},
{
provide: SourceDbQueryService,
inject: [ModuleRef, SourceDbConfig, getConnectionToken(SourceDbConfig.DefaultConnectionName)],
useFactory(moduleRef: ModuleRef, config: SourceDbConfig, sequelize: Sequelize) {
const { schema, viewName } = config
return new SourceDbQueryService(moduleRef, sequelize, { schema, viewName })
},
},
],
exports: [
getConnectionToken(SourceDbConfig.DefaultConnectionName),
SourceDbQueryService,
],
})
export class SourceDbModule {}
/src/jobs/etl-module.ts
#Module({
imports: [
ConfigModule,
LocalDbModule,
/** Contains Local repositories with decorated Models, using connection from LocalDbModule */
RepositoryModule,
SourceDbModule,
],
})
export class EtlModule {}

NestJS E2E tests with Jest. Injected service returns undefined (only tests)

I have a problem with the end-to-end testing of my users module. I want to validate if there is a "companyCode" when a user makes a GET request in /users and sends this code in the query params. This validator searches the database if this company code exists, if it does not exist it returns an error. The problem is that in the test this validation doesn't happen, because "companiesService" returns undefined (only in the test), what's missing?
Possible Solution: something related to useContainer(class-validator).
Thanks.
users.e2e-spec.ts
describe('UsersController (e2e)', () => {
let app: INestApplication;
let repository: Repository<User>;
beforeAll(async () => {
const module = await Test.createTestingModule({
imports: [UsersModule, AuthModule, TypeOrmModule.forRoot(ormConfig)],
providers: [
{
provide: APP_GUARD,
useClass: AuthGuard,
},
],
}).compile();
app = module.createNestApplication();
app.useGlobalPipes(new ValidationPipe());
useContainer(app.select(UsersModule), { fallbackOnErrors: true });
repository = module.get('UserRepository');
await app.init();
});
afterAll(async () => {
await app.close();
});
describe('/users (GET)', () => {
it('should return users if requesting user sent "companyCode" in the request body', async (done) => {
return request(app.getHttpServer())
.get('/users')
.auth('admin', 'admin')
.query({ companyCode: '2322661870558778503' }) // should return 200 because companyCode exists but is returning 400
.expect(200)
.then((res) => {
expect(res.body.users).toHaveLength(1);
done();
})
.catch((err) => done(err));
});
});
});
users.module.ts
#Module({
controllers: [UsersController],
providers: [UsersService, UserExistsRule],
imports: [
TypeOrmModule.forFeature([
User,
Person,
Type,
Profile,
UserProfile,
Company,
]),
CompaniesModule,
],
exports: [UsersService],
})
export class UsersModule {}
read-users.dto.ts
export class ReadUsersDto {
#IsOptional()
#IsNotEmpty()
#IsString()
#MinLength(1)
#MaxLength(255)
public name?: string;
#IsOptional()
#IsNotEmpty()
#IsNumberString()
#Type(() => String)
#Validate(CompanyExistsRule)
public companyCode?: string;
}
companies.module.ts
#Module({
providers: [CompaniesService, CompanyExistsRule],
imports: [TypeOrmModule.forFeature([Company, Person])],
exports: [CompaniesService],
})
export class CompaniesModule {}
companies.decorator.ts
#ValidatorConstraint({ name: 'CompanyExists', async: true })
#Injectable()
export class CompanyExistsRule implements ValidatorConstraintInterface {
constructor(private companiesService: CompaniesService) {}
async validate(code: string) {
try {
console.log('companiesService', this.companiesService); // returns undefined on test
await this.companiesService.findOneByCode(code);
} catch (e) {
return false;
}
return true;
}
defaultMessage() {
return `companyCode doesn't exist`;
}
}
I found that I imported useContainer from typeorm instead of the class-validator hahahahha.
// incorrectly imported
import { useContainer } from 'typeorm';
// correctly imported
import { useContainer } from 'class-validator';

Video as a background image not working in Gatsby PWA on iOS

I created a opt-in app for potential interims for our company, i worked with Gatsby and for now am quite satisfied with the result. I made it an Progressive Web App as that is fairly easy with the gatsby plugin.
The PWA works great on Android and shows the background video as expected, but on iOS the video doesn't show.
I updated all the packages and dependencies to the last versions but that doesn't change a thing. I tried googling the issue but got a lot of search results off people trying to let a PWA play video in the background when the app is closed (not my case).
{
resolve: `gatsby-plugin-manifest`,
options: {
name: `Afstuderen bij Arcady`,
short_name: `Afstuderen`,
start_url: `/`,
background_color: `#FFF`,
theme_color: `#00a667`,
display: `standalone`,
icon: `src/images/bear_green.png`,
},
},
'gatsby-plugin-offline',
And the content of the service worker
importScripts("workbox-v3.6.3/workbox-sw.js");
workbox.setConfig({modulePathPrefix: "workbox-v3.6.3"});
workbox.core.setCacheNameDetails({prefix: "gatsby-plugin-offline"});
workbox.skipWaiting();
workbox.clientsClaim();
/**
* The workboxSW.precacheAndRoute() method efficiently caches and responds to
* requests for URLs in the manifest.
*/
self.__precacheManifest = [
{
"url": "webpack-runtime-aec2408fe3a97f1352af.js"
},
{
"url": "app-5b624d17337895ddf874.js"
},
{
"url": "component---node-modules-gatsby-plugin-offline-app-shell-js-b97c345e19bb442c644f.js"
},
{
"url": "offline-plugin-app-shell-fallback/index.html",
"revision": "ac0d57f6ce61fac4bfa64e7e08d076c2"
},
{
"url": "0-d2c3040ae352cda7b69f.js"
},
{
"url": "component---src-pages-404-js-cf647f7c3110eab2f912.js"
},
{
"url": "static/d/285/path---404-html-516-62a-0SUcWyAf8ecbYDsMhQkEfPzV8.json"
},
{
"url": "static/d/604/path---offline-plugin-app-shell-fallback-a-30-c5a-BawJvyh36KKFwbrWPg4a4aYuc8.json"
},
{
"url": "manifest.webmanifest",
"revision": "5a580d53785b72eace989a49ea1e24f7"
}
].concat(self.__precacheManifest || []);
workbox.precaching.suppressWarnings();
workbox.precaching.precacheAndRoute(self.__precacheManifest, {});
workbox.routing.registerRoute(/(\.js$|\.css$|static\/)/, workbox.strategies.cacheFirst(), 'GET');
workbox.routing.registerRoute(/^https?:.*\.(png|jpg|jpeg|webp|svg|gif|tiff|js|woff|woff2|json|css)$/, workbox.strategies.staleWhileRevalidate(), 'GET');
workbox.routing.registerRoute(/^https?:\/\/fonts\.googleapis\.com\/css/, workbox.strategies.staleWhileRevalidate(), 'GET');
/* global importScripts, workbox, idbKeyval */
importScripts(`idb-keyval-iife.min.js`)
const WHITELIST_KEY = `custom-navigation-whitelist`
const navigationRoute = new workbox.routing.NavigationRoute(({ event }) => {
const { pathname } = new URL(event.request.url)
return idbKeyval.get(WHITELIST_KEY).then((customWhitelist = []) => {
// Respond with the offline shell if we match the custom whitelist
if (customWhitelist.includes(pathname)) {
const offlineShell = `/offline-plugin-app-shell-fallback/index.html`
const cacheName = workbox.core.cacheNames.precache
return caches.match(offlineShell, { cacheName }).then(cachedResponse => {
if (cachedResponse) return cachedResponse
console.error(
`The offline shell (${offlineShell}) was not found ` +
`while attempting to serve a response for ${pathname}`
)
return fetch(offlineShell).then(response => {
if (response.ok) {
return caches.open(cacheName).then(cache =>
// Clone is needed because put() consumes the response body.
cache.put(offlineShell, response.clone()).then(() => response)
)
} else {
return fetch(event.request)
}
})
})
}
return fetch(event.request)
})
})
workbox.routing.registerRoute(navigationRoute)
let updatingWhitelist = null
function rawWhitelistPathnames(pathnames) {
if (updatingWhitelist !== null) {
// Prevent the whitelist from being updated twice at the same time
return updatingWhitelist.then(() => rawWhitelistPathnames(pathnames))
}
updatingWhitelist = idbKeyval
.get(WHITELIST_KEY)
.then((customWhitelist = []) => {
pathnames.forEach(pathname => {
if (!customWhitelist.includes(pathname)) customWhitelist.push(pathname)
})
return idbKeyval.set(WHITELIST_KEY, customWhitelist)
})
.then(() => {
updatingWhitelist = null
})
return updatingWhitelist
}
function rawResetWhitelist() {
if (updatingWhitelist !== null) {
return updatingWhitelist.then(() => rawResetWhitelist())
}
updatingWhitelist = idbKeyval.set(WHITELIST_KEY, []).then(() => {
updatingWhitelist = null
})
return updatingWhitelist
}
const messageApi = {
whitelistPathnames(event) {
let { pathnames } = event.data
pathnames = pathnames.map(({ pathname, includesPrefix }) => {
if (!includesPrefix) {
return `${pathname}`
} else {
return pathname
}
})
event.waitUntil(rawWhitelistPathnames(pathnames))
},
resetWhitelist(event) {
event.waitUntil(rawResetWhitelist())
},
}
self.addEventListener(`message`, event => {
const { gatsbyApi } = event.data
if (gatsbyApi) messageApi[gatsbyApi](event)
})
I expect the iOS PWA (safari) to show the video as it does on Android but instead it gives a grey screen.
I hope some one can help me out or point me in the right direction.
How big is your video ?
Last time I checked, iOS has a limit of 50MB for the cache of a PWA, so if your video is bigger than 50MB, that may be the reason it works only on Android (which doesn't have such restrictions).
I found this blog post that helped me fix this problem
To add Range request handling to gatsby-plugin-offline, I added a script called range-request-handler.js with the following:
// range-request-handler.js
// Define workbox globally
importScripts('https://storage.googleapis.com/workbox-cdn/releases/5.0.0/workbox-sw.js');
// Bring in workbox libs
const { registerRoute } = require('workbox-routing');
const { CacheFirst } = require('workbox-strategies');
const { RangeRequestsPlugin } = require('workbox-range-requests'); // The fix
// Add Range Request support to fetching videos from cache
registerRoute(
/(\.webm$|\.mp4$)/,
new CacheFirst({
plugins: [
new RangeRequestsPlugin(),
],
})
);
Then in my gatsby-config.js I configured the plugin to append the above script:
// gatsby-config.js
module.exports = {
// ...
plugins: [
// ...plugins
{
resolve: 'gatsby-plugin-offline',
options: {
appendScript: require.resolve('./range-request-handler.js'),
},
},
// ...plugins
],
// ...
};
Videos now work in the Safari browser for me. If there is a better way to implement this, I am all ears. For now it works as intended

getting error `Property 'initializeData' does not exist on type of AppConfig` on `useFactory`

I am fetching data from service on APP_INITIALIZER, but getting error as
Property 'initializeData' does not exist on type of AppConfig
don't know what is the exact issue here. any one help me?
here is my module file:
import { AppConfig } from "./shared-components/auth/AdalService";
import { AppComponent } from './app.component';
import { RoutesModule } from './routes/routes.module';
import { SignInComponent } from './shared-components/user/sign-in/sign-in.component';
export function initializeApp() {
return () => AppConfig.initializeData(); //getting error here
}
#NgModule({
declarations: [
AppComponent,
SignInComponent
],
imports: [
BrowserModule,
AngularFontAwesomeModule,
MsAdalAngular6Module,
TranslateModule.forRoot({
loader: {
provide: TranslateLoader,
useFactory: (createTranslateLoader),
deps: [HttpClient]
},
isolate: true
}),
SharedModule,
HttpClientModule,
iboCalendarModule,
RoutesModule,
// HttpClientInMemoryWebApiModule.forRoot(EventData),
StoreModule.forRoot({}),
EffectsModule.forRoot([]),
StoreDevtoolsModule.instrument({
name:'IBO App',
maxAge:25
})
],
providers: [
{
provide: APP_INITIALIZER,
useFactory: initializeApp,
multi: true,
deps: [AppConfig, SignInComponent ]
},
MsAdalAngular6Service,
{
provide: 'adalConfig',
useFactory: getAdalConfig,
deps: []
},
{
provide: HTTP_INTERCEPTORS,
useClass: InsertAuthTokenInterceptor,
multi: true
}
],
bootstrap: [AppComponent]
})
export class AppModule { }
Here is my service.ts:
import { Injectable, OnInit } from '#angular/core';
import { ShareOption } from "./../user/sign-in/sign-in.component";
import { Store, select } from '#ngrx/store';
import { StateShared } from "./../models/models";
#Injectable({
providedIn: 'root'
})
export class AppConfig {
constructor(){}
initializeData() {
return new Promise((resolve, reject) => resolve(true));
}
}
I bought the service in parameter, got issue fixed. my updated chunk is:
export function initializeApp(service:AppConfig) { //getting service object in param
return () => service.initializeData();
}

Vue-Resource: How to get and show multiple arrays from JSON

I have a JSON from my API:
{"users":
[
{"id":1,"name":"Adam"},
{"id":2,"name":"Barry"}
],
"announcements":
[
{"id":1,"content":"blah blah"},
{"id":2,"content":"ya ya"}
]
}
How do I make vue-resource get those arrays and put them into users and announcements respectively, so that they can be looped in the view?
My script:
export default {
name: 'home',
data: {
users: [],
announcements: []
},
methods: {
fetchData () {
this.$http.get('http://localhost:3000')
.then(function (response) {
// what do I put here to assign the response to users and announcements?
})
},
},
created: function () {
this.fetchData()
}
From Jayem's advice, I used Axios:
Install then reference axios
import axios from 'axios'
Vue.prototype.$http = axios
Use Axios
<script>
export default {
data () {
return { info: null, announcements: null, users: null }
},
mounted () {
this.$http
.get('http://localhost:3000/')
.then(response => {
(this.info = response.data)
console.log(response.data.announcements)
console.log(response.data.users)
})
}
}
</script>
Add in View:
<div v-for="announcement in info.announcements">
{{ announcement.content }}
</div>
<div v-for="user in info.users">
{{ user.name }}
</div>

Resources