My project is using Nuxt, and I would like to implement dynamic injection of my services. It means I just add a class to my specific folder, and the class will be automatically injected into the context.
For instance, these are my classes placed in nuxtApp/service:
// /service/foo.service.js
export class FooService {
constructor (context) {
this.context = context
}
functionFoo (param) {
console.log(`FooService.functionFoo: ${param}`)
}
}
// /service/bar.service.js
export class BarService {
constructor (context) {
this.context = context
}
functionBar (param) {
console.log(`BarService.functionBar: ${param}`)
}
}
And this is how my plugin currently looks like, but I would like to automate it:
// /plugins/service-loader.js
import { FooService } from '../service/foo.service'
import { BarService } from '../service/bar.service'
export default ({ app }, inject) => {
const fooService = new FooService(app)
inject('fooService', fooService)
const barService = new BarService(app)
inject('barService', barService)
}
Is it possible to create automatically loading of services placed in the /service folder, and then inject their instance into the context?
You could use a Nuxt module to provide plugins for ~/service/*.service.js. The module would scan the service/ directory, and call addPlugin() for each .service.js file:
// ~/modules/service-loader.js
import path from 'path'
import glob from 'glob'
export default function serviceLoader() {
glob(path.resolve(__dirname, '../service/**/*.service.js'), { follow: true }, (err, files) => {
if (err) throw err
for (const file of files) {
const exportedMembers = Object.keys(require(file))
if (!exportedMembers.length) return
const className = exportedMembers[0]
this.addPlugin({
src: path.resolve(__dirname, './service-template.js'),
fileName: path.basename(file),
options: {
className,
propName: className.slice(0,1).toLowerCase() + className.substring(1),
moduleName: file,
}
})
}
})
}
// ~/modules/service-template.js
import { <%= options.className %> } from '<%= options.moduleName %>'
export default ({ app }, inject) => {
const <%= options.propName %> = new <%= options.className %> (app)
inject('<%= options.propName %>', <%= options.propName %>)
}
Install this module under the modules array in your Nuxt config:
// nuxt.config.js
export default {
modules: [
'~/modules/service-loader'
],
}
Related
I'm using pino-logger in my NestJS project to log the activities in my application, and I'm logging the object along with ReqId so I can trace the whole activity inside one request. I'd like to use the same "ReqId" in another place as well, but I'm unsure of how to move it outside of the module, so for that, I'm thinking to save that generated ReqId into the CacheManager but not sure how to inject CacheManager class inside genReqId function. Please look over the code below.
app.module.ts
#Module({
imports: [
LoggerModule.forRoot({
pinoHttp: {
genReqId: (req: any) => {
// I'm thinking to use CacheManager here but I'm not sure how to inject CacheManager class here
return req.headers.req_id || uuid(); // from here it generate the request ID and I want to export this ID and use in side an another class
},
base: undefined,
quietReqLogger: true,
timestamp: false,
},
}),
],
})
export class AppModule {}
you need To create sharable service and import it Imports
#Injectable()
export class RequestIdService {
private reqId: string;
setRequestId(reqId: string) {
this.reqId = reqId;
}
getRequestId() {
return this.reqId;
}
}
than import it to logger module
imports: [
LoggerModule.forRoot({
pinoHttp: {
genReqId: (req: any) => {
this.requestIdService.setRequestId(req.headers.req_id || uuid());
return this.requestIdService.getRequestId();
},
base: undefined,
quietReqLogger: true,
timestamp: false,
},
}),
],
providers: [RequestIdService],
```
use that service by
import { RequestIdService } from './request-id.service';
this.requestIdService.getRequestId()
My company uses Keycloak for authentication connected with LDAP and returning a user object filled with corporative data.
Yet in this period we are all working from home and in my daily work having to authenticate in my corporative server every time I reload the app, has proven to be an expensive overhead. Especially with intermittent internet connections.
How can I fake the Keycloak call and make keycloak.protect() work as it has succeeded?
I can install a Keyclock server in my machine, but I'd rather not do that because it would be another server running in it besides, vagrant VM, Postgres server, be server, and all the other things I leave open.
It would be best to make a mock call and return a fixed hard-coded object.
My project's app-init.ts is this:
import { KeycloakService } from 'keycloak-angular';
import { KeycloakUser } from './shared/models/keycloakUser';
<...>
export function initializer(
keycloak: KeycloakService,
<...>
): () => Promise<any> {
return (): Promise<any> => {
return new Promise(async (res, rej) => {
<...>
await keycloak.init({
config: environment.keycloakConfig,
initOptions: {
onLoad: 'login-required',
// onLoad: 'check-sso',
checkLoginIframe: false
},
bearerExcludedUrls: [],
loadUserProfileAtStartUp: false
}).then((authenticated: boolean) => {
if (!authenticated) return;
keycloak.getKeycloakInstance()
.loadUserInfo()
.success(async (user: KeycloakUser) => {
// ...
// load authenticated user data
// ...
})
}).catch((err: any) => rej(err));
res();
});
};
I just need one fixed logged user. But it has to return some fixed customized data with it. Something like this:
{ username: '111111111-11', name: 'Whatever Something de Paula',
email: 'whatever#gmail.com', department: 'sales', employee_number: 7777777 }
EDIT
I tried to look at the idea of #BojanKogoj but AFAIU from Angular Interceptor page and other examples and tutorials, it has to be injected in a component. Keycloak initialization is called on app initialization, not in a component. Also Keycloak's return is not the direct return of init() method. It passes through other objects in the .getKeycloakInstance().loadUserInfo().success() sequence.
Or maybe it's just me that didn't fully understand it. If anyone can come with an example of an interceptor that can intercept the call and return the correct result, that could be a possibility.
Edit2
Just to complement that what I need is for the whole keycloak's system to work. Please notice that the (user: KeycloakUser) => { function is passed to success method of keycloak's internal system. As I said above, routes have a keycloak.protect() that must work. So it's not just a simple case of returning a promise with a user. The whole .getKeycloakInstance().loadUserInfo().success() chain has to be mocked. Or at least that's how I understand it.
I included an answer with the solution I made based on #yurzui's answer
Will wait a couple of days to award the bounty to see if someone can came up with an even better solution (which I doubt).
You can leverage Angular environment(or even process.env) variable to switch between real and mock implementations.
Here is a simple example of how to do that:
app-init.ts
...
import { environment } from '../environments/environment';
export function initializer(
keycloak: KeycloakService
): () => Promise<any> {
function authenticate() {
return keycloak
.init({
config: {} as any,
initOptions: {onLoad: 'login-required', checkLoginIframe: false},
bearerExcludedUrls: [],
loadUserProfileAtStartUp: false
})
.then(authenticated => {
return authenticated ? keycloak.getKeycloakInstance().loadUserInfo() : Promise.reject();
});
}
// we use 'any' here so you don't have to define keyCloakUser in each environment
const { keyCloakUser } = environment as any;
return () => {
return (keyCloakUser ? Promise.resolve(keyCloakUser) : authenticate()).then(user => {
// ...
// do whatever you want with user
// ...
});
};
}
environment.ts
export const environment = {
production: false,
keyCloakUser: {
username: '111111111-11',
name: 'Whatever Something de Paula',
email: 'whatever#gmail.com',
}
};
environment.prod.ts
export const environment = {
production: true,
};
Update
If you want to mock KeycloakService on client side then you can tell Angular dependency injection to handle that:
app.module.ts
import { environment } from '../environments/environment';
import { KeycloakService, KeycloakAngularModule } from 'keycloak-angular';
import { MockedKeycloakService } from './mocked-keycloak.service';
#NgModule({
...
imports: [
...
KeycloakAngularModule
],
providers: [
{
provide: KeycloakService,
useClass: environment.production ? KeycloakService : MockedKeycloakService
},
{
provide: APP_INITIALIZER,
useFactory: initializer,
multi: true,
deps: [KeycloakService]
}
],
bootstrap: [AppComponent]
})
export class AppModule { }
mocked-keycloak.service.ts
import { Injectable} from '#angular/core';
import { KeycloakService } from 'keycloak-angular';
#Injectable()
class MockedKeycloakService extends KeycloakService {
init() {
return Promise.resolve(true);
}
getKeycloakInstance() {
return {
loadUserInfo: () => {
let callback;
Promise.resolve().then(() => {
callback({
userName: 'name'
});
});
return {
success: (fn) => callback = fn
};
}
} as any;
}
}
Although you explicitly state that you think that mocking is the best option, I suggest to reconsider it in favor of setting up local Keycloak instance using docker. It becomes easy when you provide a realm to bootstrap your environment. I've been using this approach with success for over 2 years of developing applications that work with Keycloak. This approach will let you "substitute calls to your corporate server" hence I post it here.
Assuming that you have docker & docker-compose installed, you'll need:
1. docker-compose.yaml
version: '3.7'
services:
keycloak:
image: jboss/keycloak:10.0.1
environment:
KEYCLOAK_USER: admin
KEYCLOAK_PASSWORD: admin
KEYCLOAK_IMPORT: /tmp/dev-realm.json
ports:
- 8080:8080
volumes:
- ./dev-realm.json:/tmp/dev-realm.json
2. dev-realm.json (exact content depend on required settings, this is the minimum that you've mentioned in your question)
{
"id": "dev",
"realm": "dev",
"enabled": true,
"clients": [
{
"clientId": "app",
"enabled": true,
"redirectUris": [
"*"
],
"bearerOnly": false,
"consentRequired": false,
"standardFlowEnabled": true,
"implicitFlowEnabled": false,
"directAccessGrantsEnabled": false,
"secret": "mysecret",
"publicClient": false,
"protocol": "openid-connect",
"fullScopeAllowed": false,
"protocolMappers": [
{
"name": "department",
"protocol": "openid-connect",
"protocolMapper": "oidc-usermodel-attribute-mapper",
"consentRequired": false,
"config": {
"user.attribute": "department",
"id.token.claim": "true",
"access.token.claim": "true",
"claim.name": "department",
"userinfo.token.claim": "true"
}
},
{
"name": "employee_number",
"protocol": "openid-connect",
"protocolMapper": "oidc-usermodel-attribute-mapper",
"consentRequired": false,
"config": {
"user.attribute": "employee_number",
"id.token.claim": "true",
"access.token.claim": "true",
"claim.name": "employee_number",
"userinfo.token.claim": "true"
}
}
]
}
],
"users": [
{
"username": "111111111-11",
"enabled": true,
"firstName": "Whatever Something de Paula",
"email": "whatever#gmail.com",
"credentials": [{
"type": "password",
"value": "demo"
}],
"attributes": {
"department": "sales",
"employee_number": 7777777
}
}
]
}
3. Create dedicated Angular environment that will use the "http://localhost:8080/auth" and realm "dev" for your local development
The advantages of this approach over mocking:
all OIDC and keycloak features are working. I admit that it depends if you need them but you are free to use realm/client roles, groups, 'real' OIDC flow with token refreshal. This gives you guarantee that your local setup will work also with corporate service
this setup can be stored in repository (contrary to manual setup of Keycloak server) and used both for working on web applications and backend services
By default, Keycloak uses a H2 in-memory database and needs about 600MB of RAM so I'd argue that it is a relatively low footprint.
Solution
I was able to mock Keycloak service using the method #yurzui suggested. I'll document it here as it may be useful for somebody.
Initially I had posted a solution where I conditionally exported the mock or real classes from the mock module. All worked well on dev mode, but when I tried to build the application for publishing in production server I got an error, so I had to return to the 2 class solution. I explain the problem in details this question .
This is the working code (so far).
Frontend:
With a little help from #kev's answer in this question and #yurzui (again :D) in this one, I created a MockKeycloakService class:
import { Injectable } from '#angular/core';
import { KeycloakService } from 'keycloak-angular';
import { environment } from '../../../environments/environment';
#Injectable({ providedIn: 'root' })
export default class MockKeycloakService {
init() {
console.log('[KEYCLOAK] Mocked Keycloak call');
return Promise.resolve(true);
}
getKeycloakInstance() {
return {
loadUserInfo: () => {
let callback : any;
Promise.resolve().then(() => {
callback({
username: '77363698953',
NOME: 'Nelson Teixeira',
FOTO: 'assets/usuarios/nelson.jpg',
LOTACAOCOMPLETA: 'DIOPE/SUPOP/OPSRL/OPSMC (local)',
});
});
return { success: fn=>callback = fn };
}
} as any;
}
login() {}
logout() {}
}
const KeycloakServiceImpl =
environment.production ? KeycloakService : MockKeycloakService
export { KeycloakServiceImpl, KeycloakService, MockKeycloakService };
then I substituted it in app.module:
<...>
import { KeycloakAngularModule } from 'keycloak-angular';
import { KeycloakServiceImpl } from 'src/app/shared/services/keycloak-mock.service';
import { initializer } from './app-init';
<...>
imports: [
KeycloakAngularModule,
<...>
],
providers: [
<...>,
{
provide: APP_INITIALIZER,
useFactory: initializer,
multi: true,
deps: [KeycloakServiceImpl, <...>]
},
<...>
],
bootstrap: [AppComponent]
})
export class AppModule { }
Then changed the type of keycloak service variable in app-init, that was the only change, but then I could remove KeycloackService import as it's being provided in app.module:
import { KeycloakUser } from './shared/models/keycloakUser';
<...>
export function initializer(
keycloakService: any,
<...>
): () => Promise<any> {
return (): Promise<any> => {
return new Promise(async (res, rej) => {
<...>
await keycloak.init({
config: environment.keycloakConfig,
initOptions: {
onLoad: 'login-required',
// onLoad: 'check-sso',
checkLoginIframe: false
},
bearerExcludedUrls: [],
loadUserProfileAtStartUp: false
}).then((authenticated: boolean) => {
if (!authenticated) return;
keycloak.getKeycloakInstance()
.loadUserInfo()
.success(async (user: KeycloakUser) => {
<...>
})
}).catch((err: any) => rej(err));
res();
});
};
But in the component I still have to check which environment I'm on and instance the class correctly:
<...>
import { MockKeycloakService } from '../../shared/services/keycloak.mock.service';
import { environment } from '../../../environments/environment';
<...>
export class MainComponent implements OnInit, OnDestroy {
<...>
keycloak: any;
constructor(
<...>
) {
this.keycloak = (environment.production) ? KeycloakServiceImpl : new KeycloakServiceImpl();
}
async doLogout() {
await this.keycloak.logout();
}
async doLogin() {
await this.keycloak.login();
}
<...>
}
Backend:
That was easier, again I created a KeycloakMock class:
import KeyCloack from 'keycloak-connect';
class KeycloakMock {
constructor(store, config) {
//ignore them
}
middleware() {
return (req, res, next) =>{
next();
}}
protect(req, res, next) {
return (req, res, next) =>{
next();
}}
}
const exportKeycloak =
(process.env.NODE_ENV == 'local') ? KeycloakMock : KeyCloack;
export default exportKeycloak;
Then I just substitued 'keycloak-connect' import on app.js by this class, and everythig worked fine. It connects to the real service if I set production = true and it mocks it with production = false.
Very cool solution. If anyone has anything to say on my implementation of #yurzui idea I'll like to hear from you.
Some notes:
I still couln't get rid of having to check the environment in the main-component class, as if I do this in the mock class module:
const KeycloakServiceImpl =
environment.production ? KeycloakService : new MockKeycloakService()
app.module doesn't work anymore. and if I do this in main-component:
constructor(
<...>
keycloakService: KeyclockServiceImpl;
) { }
The build fails with a "KeyclockServiceImpl refers to a value but is being used as a type here";
I had to export all classes or the build fails
export { KeycloakServiceImpl, KeycloakService, MockKeycloakService };
I am trying to pass data that I get from my module options down to a plugin. So let's say this is my module:
module.exports = function (moduleOptions) {
const options = {
...this.options.moduleName,
...moduleOptions
}
this.addPlugin({
src: resolve(__dirname, 'plugin.js'),
options
})
}
and this is my plugin
import { createStore } from 'lib';
export default async ({ store, app }) => {
const settings = {
axios: app.$axios,
models: <% options.models %>
}
settings.axios = app.$axios;
createStore(settings).install()(store)
};
and this is my config
const { resolve } = require('path')
module.exports = {
rootDir: resolve(__dirname, '..'),
buildDir: resolve(__dirname, '.nuxt'),
srcDir: __dirname,
render: {
resourceHints: false
},
modules: [
'moduleName'
],
moduleName: {
{ models: require(resolve(__dirname, '../example/models')) }
}
}
it throws
axios: app.$axios,
7 | models:
> 8 | }
where models is just empty, nothing behind it. No null, no undefined.
But if I do <% console.log(options.models) %> it will show the models that I've loaded. Btw models is just an array of classes.
These models must be configurable, so how do I pass these data from my nuxt.config.js via a module to my plugin?
Hope somebody knows :)
I've worked around this issue using an require in my plugin instead of my config.
If I set language as rootURL this works I am able to go to any route/subroute and current language will be present in url, but on page refresh browser is trying get app from that language folder any thoughts? :/ I am using Ember 1.11.
// router.coffee
`import Ember from 'ember';`
`import config from './config/environment';`
Router = Ember.Router.extend
location: config.locationType
rootURL: '/' + localStorage.getItem('locale') + '/'
// config/environment.js
module.exports = function(environment) {
var ENV = {
locationType: 'history',
baseURL: '/'
...
You shouldn't use rootURL for this purpose. Instead, create a route that will be a parent to all other routes:
//../app/router.js
import Ember from 'ember';
import config from './config/environment';
var Router = Ember.Router.extend(
{
location: config.locationType
}
);
Router.map(
function ()
{
this.route(
'lang', { path: '/:lang' }, function ()
{
this.route('index', { path: '/' });
this.route('404', { path: '/*wildcard' });
this.route('your-route-name');
}
);
}
);
export default Router;
Than you can use afterModel method of lang route to determine desired locale:
//../app/routes/lang.js
import config from '../config/environment';
export default Ember.Route.extend(
{
afterModel: function (params)
{
var allowedLocales = config.i18n.allowedLocales;
var defaultLocale = config.i18n.defaultLocale;
this.set(
'i18n.locale',
params && params.lang && allowedLocales.indexOf(params.lang) > -1 ? params.lang : defaultLocale
);
}
}
);
And in index route you need to detect user's locale from browser's settings or use default one:
//../app/routes/index.js
import config from '../config/environment';
export default Ember.Route.extend(
{
beforeModel: function ()
{
var allowedLanguages = config.i18n.allowedLocales;
var language = config.i18n.defaultLocale;
if (navigator.languages) {
for (let lang of navigator.languages) {
if (allowedLanguages.indexOf(lang) > -1) {
language = lang;
break;
}
}
} else {
if (navigator.language) {
language = navigator.language;
} else {
if (navigator.userLanguage) {
language = navigator.userLanguage;
}
}
}
this.transitionTo('lang.index', { lang: language });
}
}
);
BTW, your Ember version is quite old. You may want to upgrade it to 1.13 (1.13.x shouldn't break your app, 2.x could).
I want to use ipcMain / ipcRenderer on my project to communicate from Angular to Electron and back.
The Electron side is pretty clear:
const
electron = require('electron'),
ipcMain = electron.ipcMain,
;
ipcMain.on('asynchronous-message', function(event, arg) {
console.debug('ipc.async', arg);
event.sender.send('asynchronous-reply', 'async-pong');
});
ipcMain.on('synchronous-message', function(event, arg) {
console.debug('ipc.sync', arg);
event.returnValue = 'sync-pong';
});
But I have no idea how to integrate that Electron module into my Angular 2 app. I use SystemJS as module loader, but I'm a rookie with it.
Any help appreciated. Thanks.
--- Mario
There is conflict, because Electron use commonjs module resolving, but your code already compiled with systemjs rules.
Two solutions:
Robust way. Register object require returned:
<script>
System.set('electron', System.newModule(require('electron')));
</script>
This is the best, because renderer/init.js script loads that module on start. SystemJS have to take it only, not loads.
Alternative way. Use dirty trick with declaration.
Get electron instance inside index.html:
<script>
var electron = require('electron');
</script>
Declare it inside your typescript file this way:
declare var electron: any;
Use it with freedom )
electron.ipcRenderer.send(...)
A recent package called ngx-electron makes this easy. Link to repo and link to article
src/app/app.module.ts
import { NgxElectronModule } from 'ngx-electron';
// other imports
#NgModule({
imports: [NgxElectronModule],
...
})
src/app/your.component.ts
import { Component, NgZone } from '#angular/core';
import { ElectronService } from 'ngx-electron';
#Component({
selector: 'app-your',
templateUrl: 'your.component.html'
})
export class YourComponent {
message: string;
constructor(private _electronService: ElectronService, private _ngZone: NgZone) {
this._electronService.ipcRenderer.on('asynchronous-reply', (event, arg) => {
this._ngZone.run(() => {
let reply = `Asynchronous message reply: ${arg}`;
this.message = reply;
});
}
}
playPingPong() {
this._electronService.ipcRenderer.send('asynchronous-message', 'ping');
}
}
Note: NgZone is used because this.message is updated asynchronously outside of Angular’s zone. article
But I have no idea how to integrate that Electron module into my Angular 2 app
You would have angular hosted within the UI rendering process in electron. The ipcMain is used to communicate to non rendering child processes.
This should just be a case of requiring the ipcRenderer module in your main html file (electron will provide this for you):
<script>
var ipc = require('electron').ipcRenderer;
var response = ipc.sendSync('getSomething');
console.log(response); // prints 'something'
</script>
and then setting up a handler in your main js file:
const ipcMain = require('electron').ipcMain;
ipcMain.on('getSomething', function(event, arg) {
event.returnValue = 'something';
});
That's all there should be to it.
My solution:
configure a baseUrl in tsconfig.json
at the root of the directory pointed by the baseUrl, create a directory "electron".
Inside this directory, a file index.ts:
const electron = (<any>window).require('electron');
export const {BrowserWindowProxy} = electron;
export const {desktopCapturer} = electron;
export const {ipcRenderer} = electron;
export const {remote} = electron;
export const {webFrame} = electron;
(ideally export default [...]require('electron'), but this is not statically analysable...)
now I can have in my renderer process:
import {remote} from 'electron';
console.log(remote);
Hope it makes sense...
with typings enabled:
///<reference path="../../typings/globals/electron/index.d.ts"/>
const electron = (<any>window).require('electron');
export const BrowserWindowProxy = <Electron.BrowserWindowProxy>electron.BrowserWindowProxy;
export const desktopCapturer = <Electron.DesktopCapturer>electron.desktopCapturer;
export const ipcRenderer = <Electron.IpcRenderer>electron.ipcRenderer;
export const remote = <Electron.Remote>electron.remote;
export const webFrame = <Electron.WebFrame>electron.webFrame;
NB: typings I got is from:
{
"globalDependencies": {
"electron": "registry:dt/electron#1.4.8+20161220141501"
}
}
Component.TS
const ipc = require('electron').ipcRenderer;
#Component({
selector: 'app-my component',.....
})
....
public testElectronIpc(): void{
ipc.send('test-alert');
}
MAIN.JS
// IPC message listeners
ipc.on('test-alert', function (event, arg) {
console.log('Test alert received from angular component');
})
config
plugins: [
new webpack.ExternalsPlugin('commonjs', [
'desktop-capturer',
'electron',
'ipc',
'ipc-renderer',
'native-image',
'remote',
'web-frame',
'clipboard',
'crash-reporter',
'screen',
'shell'
])
],