Angularfire conditionally provide appcheck - angularfire2

I have two angular projects:
Main app
Webcomponent (angular elements)
Webcomponent is used in the main app. Both are using angularfire for executing Firebase functions, working with Firestore and more.
Also I am enforcing verified request to the Functions and Firestore by AppCheck.
The web component needs to work separately. To be able to request Firebase servers I need to provide the AppCheck in both projects like this:
#NgModule({
...
imports: [
...
provideAppCheck(() => initializeAppCheck(getApp(), {
provider: new ReCaptchaV3Provider(environment.firebase.appCheck.recaptcha3SiteKey),
isTokenAutoRefreshEnabled: environment.firebase.appCheck.isTokenAutoRefreshEnabled,
}))
...
],
...
})
This works just fine when webcomponent is not included in the main app. However when so, the AppCheck is initialized two times and it throws an error:
Unhandled Promise rejection: reCAPTCHA has already been rendered in this element ; Zone: <root> ; Task: Promise.then ; Value: Error: reCAPTCHA has already been rendered in this element
So the webcomponent needs to check if appcheck already exists in document and add it only if it does not. I tried to work with appCheckInstance$ but that is an observable and provideAppCheck requires only AppCheck type.
When I try to move provideAppCheck to component which would handle the logic, I get an error saying that calling it can not be done outside module:
Either AngularFireModule has not been provided in your AppModule (this can be done manually or implictly using
provideFirebaseApp) or you're calling an AngularFire method outside of an NgModule (which is not supported).
I have no other ideas how this could be done other than building two webcomponents (one with appcheck, other without), but thats just not an option.

It turned out that the problem was elsewhere. I thought that conditional appcheck loading would help, but it didn't, because then angularfire(in webcomponent) didn't use the appcheck that the main app initialized. And hence connections to firebase were blocked (as if there was no appcheck initialized).
Solution I've figured out that works:
In webcomponent initialize all firebase services under different name.
So instead of:
#NgModule({
...
imports: [
...
provideFirebaseApp(() => initializeApp(environment.firebase)),
provideFirestore(() => getFirestore(getApp())),
provideAppCheck(() => initializeAppCheck(getApp(), {
provider: new ReCaptchaV3Provider(environment.firebase.appCheck.recaptcha3SiteKey),
isTokenAutoRefreshEnabled: environment.firebase.appCheck.isTokenAutoRefreshEnabled,
})),
...
],
...
})
do:
#NgModule({
...
imports: [
...
provideFirebaseApp(() => initializeApp(environment.firebase, 'webcomponent-app')),
provideFirestore(() => getFirestore(getApp('webcomponent-app'))),
provideAppCheck(() => initializeAppCheck(getApp('webcomponent-app'), {
provider: new ReCaptchaV3Provider(environment.firebase.appCheck.recaptcha3SiteKey),
isTokenAutoRefreshEnabled: environment.firebase.appCheck.isTokenAutoRefreshEnabled,
})),
...
],
...
})
This will initialize two instances (one for main app, other for webcomponent) with different names. And now initializing two appchecks is not problematic.

Related

How can I pass REDIS_URI for NestJS cache manager?

In the official documentation this is the correct way to use the cache manager with Redis:
import * as redisStore from 'cache-manager-redis-store';
import { CacheModule, Module } from '#nestjs/common';
import { AppController } from './app.controller';
#Module({
imports: [
CacheModule.register({
store: redisStore,
host: 'localhost',
port: 6379,
}),
],
controllers: [AppController],
})
export class AppModule {}
Source: https://docs.nestjs.com/techniques/caching#different-stores
However, I did not find any documentation on how to pass Redis instance data using REDIS_URI. I need to use it with Heroku and I believe this is a common use case.
EDIT:
now they are type-safe: https://github.com/nestjs/nest/pull/8592
I've exploring a bit about how the redis client is instantiated. Due to this line I think that the options that you've passed to CacheModule.register will be forwarded to Redis#createClient (from redis package). Therefore, you can pass the URI like:
CacheModule.register({
store: redisStore,
url: 'redis://localhost:6379'
})
try this and let me know if it works.
edit:
Explaining how I got that:
Taking { store: redisStore, url: '...' } as options.
Here in CacheModule.register I found that your options will live under CACHE_MODULE_OPTIONS token (as a Nest provider)
Then I search for places in where this token will be used. Then I found here that those options were passed to cacheManager.caching. Where cacheManager is the module cache-manager
Looking into to the cacheManager.caching's code here, you'll see that your options is now their args parameter
Since options.store (redisStore) is the module exported by cache-manager-redis-store package, args.store.create method is the same function as in redisStore.create
Thus args.store.create(args) is the same as doing redisStore.create(options) which, in the end, will call Redis.createClient passing this options

Angular in-memory-web-api method always returns 404 NotFound in the brower's console even if the tests passed

I'm new to unit testing in Angular (using Jasmine and Karma)
I'm trying to create some tests for my httpService, apparently the tests are OK.
But sometimes when I either run ng test, or refresh the browser, I found that one of the test in one of the 3 test suites has failed with this message : Uncaught [object Object] thrown.
Another annoying thing is that no matter whether all of the tests pass or any of them fail, if you check the browser's console, you'll ALWAYS find this message :
I'm attaching the code in a zip file (uploaded to Drive). You only need to run npm install and npm start.
I really hope you can help me understand why this testing behaves like a Russian roulette.
The issue is calculator.component.spec.ts. You are not mocking loanService where it is going out and making HTTP calls. You should always mock external services.
Change calculator.component.spec.ts to:
import { NO_ERRORS_SCHEMA } from '#angular/core';
import { FormBuilder } from '#angular/forms';
import { async, ComponentFixture, TestBed } from '#angular/core/testing';
import { CalculatorComponent } from './calculator.component';
import { LoanService } from '../loan.service';
import { Campaign } from '../campaign';
import { of } from 'rxjs/internal/observable/of';
describe('CalculatorComponent', () => {
let component: CalculatorComponent;
let fixture: ComponentFixture<CalculatorComponent>;
let mockLoanService: any;
beforeEach(async(() => {
// mockLoanService object, first parameter ('loanService') is optional, second paramter => array of methods needing
// mock for component
mockLoanService = jasmine.createSpyObj('loanService', ['getCurrentCampaign', 'getMonthlyAmount']);
TestBed.configureTestingModule({
declarations: [ CalculatorComponent ],
imports: [],
// NO_ERRORS_SCHEMA to ignore child components, if you need the
// painting of the DOM of the child components/directives, put them in declarations
schemas: [NO_ERRORS_SCHEMA],
providers: [
FormBuilder,
// provide the mock for LoanService
{ provide: LoanService, useValue: mockLoanService },
]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(CalculatorComponent);
component = fixture.componentInstance;
// getCurrentCampaig is related to ngOnInit so we have to mock it
mockLoanService.getCurrentCampaign.and.returnValue(of({
id: 1,
campaign_name: 'Donald Trump 2020',
min_quota: -200000000,
max_quota: 0,
max_amount: 0,
min_amount: 0,
tea: 1,
payment_date: new Date(),
currency: 'Fake News',
} as Campaign))
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});
I have written some comments in the file itself. By the way, Donald Trump 2020 and Fake News are just jokes, I have no political affiliation but I like writing jokes in my unit tests for other developers :).
Some notes:
1.) Whenever you are injecting a service, always mock it. You are testing the component and component alone, you have to assume that the service will do its job because it is already being tested.
2.) Check out NO_ERRORS_SCHEMA. It basically ignores all components/directives in your HTML that is not in the declarations array. If you are writing a test where you click the button of a child component and it affects this component, then declare it in declarations (basically if you need the actual implementation of the child component, declare it). Otherwise, use NO_ERRORS_SCHEMA.
3.) Importing SharedModule in all unit tests is not good in my opinion. It will make your unit tests slow. Instead, take advantage of declarations and providers and give the component what it needs and JUST what it needs (not extra stuff).
4.) A really good class in PluralSight called Unit Testing in Angular.
Taking that class, you will have a better understanding of Unit/Integration testing. Maybe buy a subscription to PluralSight or start a free trial.

Is there a way to define providers, declarations, imports for multiple tests

I'm starting writing unit tests for our projects, for some reason we don't have any unit tests before. I'm currently trying to make the default test to pass, and I have to define some imports, declarations, and providers. We have 44 components/services now, and for 3 of the unit tests I work on, they use same imports, declarations, and providers.
I'm just wondering if there is a way to define imports, declarations, and providers for all of the tests.
it('should create', () => {
expect(component).toBeTruthy();
});
You can create testing module with set of common dependencies like this:
#NgModule({
// ...whatever you need
providers: [
{ provide: Dependency, useClass: DependencyMock },
]
})
export class CommonTestingModule {
}
and then in tests
beforeEach(() => {
TestBed.configureTestingModule({
imports: [
CommonTestingModule,
...
],
...
})
...
});
You can create more modules for parts of applications that can be used in a few places its similar to modules provided by angular like HttpClientTestingModule

app.component in angular 2 is mandatory to be staring component?

Currently I am using Visual studio for angular application with online Angular 2 template. The application is working fine when I choose default template and app.component as starting component. but when i need to change it with another component as starting component(home.component) my application not working.
Please guide me.
This is what you app module will be like now
#NgModule({
declarations: [
AppComponent // change this to HomeComponent
],
imports: [
BrowserModule,
HttpModule
],
providers: [],
bootstrap: [AppComponent] // change this to HomeComponent
})
If you want to change the AppComponent to a different component just change the bootstrap array and declarations array to HomeComponent.
And if you you want to route use the RouterModule in importsto route from app to home component.

React Router code split "randomly" fails at loading chunks

I am struggling with a issue with react-router + webpack code split + servicer worker (or cache).
Basically the issue is the following, the code split is working properly but from time to time I get error reports from customers at sentry.io such as:
"Dynamic page loading failed Error: Loading chunk 19 failed."
My react-router code is the following:
const errorLoading = (err) => {
console.error('Dynamic page loading failed', err);
};
export default (
<Route path="/" component={App}>
<IndexRoute
getComponent={(nextState, cb) => {
System.import('./containers/home/home')
.then((module) => { cb(null, module.default); })
.catch(errorLoading);
}}
/>
</Route>
);
For my ServiceWorker I use OfflinePlugin with the following configuration:
new OfflinePlugin({
cacheName: 'cache-name',
cacheMaps: [
{
match: function(requestUrl) {
return new URL('/', location);
},
requestTypes: ['navigate']
}
],
externals: [
'assets/images/logos/slider.png',
'assets/images/banners/banner-1-320.jpg',
'assets/images/banners/banner-1-480.jpg',
'assets/images/banners/banner-1-768.jpg',
'assets/images/banners/banner-1-1024.jpg',
'assets/images/banners/banner-1-1280.jpg',
'assets/images/banners/banner-1-1400.jpg'
],
responseStrategy: 'network-first', // One of my failed attempts to fix this issue
ServiceWorker: {
output: 'my-service-worker.js'
}
})
The issue is not browser related because I have reports from IE11, safari, chrome, etc.
Any clues on what I might be doing wrong or how can I fix this issue?
Edit 2: I ended using chunks with hashes, and doing a window.location.reload() inside errorLoading's catch(), so when the browser fails to load a chunk it will reload the window and fetch the new file.
<Route path="about"
getComponent={(location, callback) => {
System.import('./about')
.then(module => { callback(null, module.default) })
.catch(() => {
window.location.reload()
})
}}
/>
It happens to me too and I don't think I have a proper solution yet, but what I noticed is this usually happens when I deploy a new version of the app, the hashes of the chunks change, and when I try to navigate to another address (chunk) the old chunk doesn't exist (it seems as if it wasn't cached) and I get the error.
I managed to reproduce this by removing the service worker that caches stuff and deploying a new version (which I guess simulates a user without the service worker running?).
remove the service worker code
unregister the service worker in devtools
reload the page
deploy a new app version
navigate to another chunk (for instance from /home to /about)
In my case it appears as if the error occurs when the old files are not cached (hence not available any more) and the user doesn't reload the page to request new ones. Reloading 'fixes' the issue because the app has the new chunk names, which correctly load.
Something else I tried was to name the chunk files without their hashes, so instead of 3.something.js they were only 3.js. When I deployed a new version the chunks were obviously still there, but this is not a good solution because the files will be cached by the browser instead of being cached by the caching plugin.
Edit: same setup as you, using sw-precache-webpack-plugin.

Resources