Skip to content
This repository has been archived by the owner on Aug 1, 2024. It is now read-only.

Commit

Permalink
18 descope component poc (#27)
Browse files Browse the repository at this point in the history
* initial component

* onSuccess, onFailure events

* full api

* add base headers to sdk

* fix lint and test

* better layout for demo app

* fix for afterRequestHooks

* dont create webcomponent after each change

* fix tests

---------

Co-authored-by: Piotr Bochenek <[email protected]>
  • Loading branch information
MF57 and Piotr Bochenek authored Oct 26, 2023
1 parent 29ad4b5 commit 7565fc8
Show file tree
Hide file tree
Showing 15 changed files with 230 additions and 23 deletions.
1 change: 0 additions & 1 deletion .eslintrc.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@
"error",
{
"type": "element",
"prefix": "lib",
"style": "kebab-case"
}
]
Expand Down
5 changes: 3 additions & 2 deletions projects/angular-sdk/.eslintrc.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,11 @@
"error",
{
"type": "element",
"prefix": "lib",
"prefix": "",
"style": "kebab-case"
}
]
],
"@angular-eslint/no-output-native": "off"
}
},
{
Expand Down
6 changes: 6 additions & 0 deletions projects/angular-sdk/src/lib/constants.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
// declare const BUILD_VERSION: string;

export const baseHeaders = {
'x-descope-sdk-name': 'angular',
'x-descope-sdk-version': '1.1.1'
};
7 changes: 6 additions & 1 deletion projects/angular-sdk/src/lib/descope-auth.module.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,21 @@
import {
CUSTOM_ELEMENTS_SCHEMA,
ModuleWithProviders,
NgModule,
Optional,
SkipSelf
} from '@angular/core';
import { DescopeComponent } from './descope/descope.component';

export class DescopeAuthConfig {
projectId = '';
}

@NgModule({
imports: [],
exports: []
schemas: [CUSTOM_ELEMENTS_SCHEMA],
exports: [DescopeComponent],
declarations: [DescopeComponent]
})
export class DescopeAuthModule {
constructor(@Optional() @SkipSelf() parentModule?: DescopeAuthModule) {
Expand All @@ -21,6 +25,7 @@ export class DescopeAuthModule {
);
}
}

static forRoot(
config?: DescopeAuthConfig
): ModuleWithProviders<DescopeAuthModule> {
Expand Down
4 changes: 3 additions & 1 deletion projects/angular-sdk/src/lib/descope-auth.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import type { UserResponse } from '@descope/web-js-sdk';
import createSdk from '@descope/web-js-sdk';
import { BehaviorSubject, finalize, Observable, tap } from 'rxjs';
import { observabilify, Observablefied } from './helpers';
import { baseHeaders } from './constants';

type DescopeSDK = ReturnType<typeof createSdk>;
type AngularDescopeSDK = Observablefied<DescopeSDK>;
Expand Down Expand Up @@ -39,7 +40,8 @@ export class DescopeAuthService {
createSdk({
...config,
persistTokens: true,
autoRefresh: true
autoRefresh: true,
baseHeaders
})
);
this.sdk.onSessionTokenChange(this.setSession.bind(this));
Expand Down
48 changes: 48 additions & 0 deletions projects/angular-sdk/src/lib/descope/descope.component.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';

import { DescopeComponent } from './descope.component';
import { DescopeAuthConfig } from '../descope-auth.module';
import createSdk from '@descope/web-js-sdk';
import mocked = jest.mocked;

jest.mock('@descope/web-js-sdk');
//Mock DescopeWebComponent
jest.mock('@descope/web-component', () => {
return jest.fn();
});

describe('DescopeComponent', () => {
let component: DescopeComponent;
let fixture: ComponentFixture<DescopeComponent>;
let mockedCreateSdk: jest.Mock;
const onSessionTokenChangeSpy = jest.fn();
const onUserChangeSpy = jest.fn();
const mockConfig: DescopeAuthConfig = {
projectId: 'someProject'
};

beforeEach(() => {
mockedCreateSdk = mocked(createSdk);

mockedCreateSdk.mockReturnValue({
onSessionTokenChange: onSessionTokenChangeSpy,
onUserChange: onUserChangeSpy
});

TestBed.configureTestingModule({
declarations: [DescopeComponent],
providers: [
DescopeAuthConfig,
{ provide: DescopeAuthConfig, useValue: mockConfig }
]
});

fixture = TestBed.createComponent(DescopeComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});

it('should create', () => {
expect(component).toBeTruthy();
});
});
109 changes: 109 additions & 0 deletions projects/angular-sdk/src/lib/descope/descope.component.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
import {
Component,
ElementRef,
EventEmitter,
Input,
OnChanges,
Output
} from '@angular/core';
import DescopeWebComponent from '@descope/web-component';
import DescopeWc, { ILogger } from '@descope/web-component';
import { DescopeAuthService } from '../descope-auth.service';
import { from } from 'rxjs';
import { baseHeaders } from '../constants';

@Component({
selector: 'descope[projectId][flowId]',
template: ''
})
export class DescopeComponent implements OnChanges {
@Input() projectId: string;
@Input() flowId: string;

@Input() locale: string;
@Input() theme: 'light' | 'dark' | 'os';
@Input() tenant: string;
@Input() telemetryKey: string;
@Input() redirectUrl: string;
@Input() autoFocus: true | false | 'skipFirstScreen';

@Input() debug: boolean;
@Input() errorTransformer: (error: { text: string; type: string }) => string;
@Input() logger: ILogger;

@Output() success: EventEmitter<void> = new EventEmitter<void>();
@Output() error: EventEmitter<void> = new EventEmitter<void>();

private readonly webComponent: DescopeWebComponent;

constructor(
private elementRef: ElementRef,
private authService: DescopeAuthService
) {
DescopeWc.sdkConfigOverrides = { baseHeaders };
this.webComponent = new DescopeWebComponent();
}
ngOnChanges(): void {
this.setupWebComponent();
}

private setupWebComponent() {
this.webComponent.setAttribute('project-id', this.projectId);
this.webComponent.setAttribute('flow-id', this.flowId);
if (this.locale) {
this.webComponent.setAttribute('locale', this.locale);
}
if (this.theme) {
this.webComponent.setAttribute('theme', this.theme);
}
if (this.tenant) {
this.webComponent.setAttribute('tenant', this.tenant);
}
if (this.telemetryKey) {
this.webComponent.setAttribute('telemetryKey', this.telemetryKey);
}
if (this.redirectUrl) {
this.webComponent.setAttribute('redirect-url', this.redirectUrl);
}
if (this.autoFocus) {
this.webComponent.setAttribute('auto-focus', this.autoFocus.toString());
}
if (this.debug) {
this.webComponent.setAttribute('debug', this.debug.toString());
}

if (this.errorTransformer) {
this.webComponent.errorTransformer = this.errorTransformer;
}

if (this.logger) {
this.webComponent.logger = this.logger;
}

if (this.success) {
this.webComponent.addEventListener('success', () => {
from(
this.authService.sdk.httpClient.hooks?.afterRequest!(
// eslint-disable-next-line @typescript-eslint/no-explicit-any
{} as any,
new Response(JSON.stringify({}))
) as Promise<unknown>
).subscribe(() => {
this.success?.emit();
});
});
}

if (this.error) {
this.webComponent.addEventListener('error', () => {
this.error?.emit();
});
}

const nativeElement = this.elementRef.nativeElement;
while (nativeElement.lastElementChild) {
nativeElement.removeChild(nativeElement.lastElementChild);
}
nativeElement.appendChild(this.webComponent);
}
}
1 change: 1 addition & 0 deletions projects/angular-sdk/src/public-api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,4 @@
export * from './lib/descope-auth.service';
export * from './lib/descope-auth.guard';
export * from './lib/descope-auth.module';
export * from './lib/descope/descope.component';
20 changes: 10 additions & 10 deletions projects/demo-app/src/app/app.component.html
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
<header>
<div class="card">
{{ session$ | async | json }}
</div>
<div class="card">
{{ user$ | async | json }}
</div>
</header>
<main>
<button (click)="refreshSession()">Refresh Session</button>
<button (click)="refreshUser()">Refresh User</button>
<router-outlet></router-outlet>
<div class="card-wrapper">
<div class="card">
<button (click)="refreshSession()">Refresh Session</button>
<pre>{{ session$ | async | json }}</pre>
</div>
<div class="card">
<button (click)="refreshUser()">Refresh User</button>
<pre>{{ user$ | async | json }}</pre>
</div>
</div>
</main>
16 changes: 8 additions & 8 deletions projects/demo-app/src/app/app.component.scss
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
header {
main {
padding-top: 4rem;
}

.card-wrapper {
display: flex;
align-items: center;
justify-content: center;
Expand All @@ -16,13 +20,9 @@ header {
word-wrap: break-word;
font-family: 'Arial', serif;
margin: 2rem;
}

main {
display: flex;
flex-direction: column;
align-items: center;
button {
margin-bottom: 1rem;
width: 100%;
margin: auto;
}
overflow: scroll;
}
11 changes: 11 additions & 0 deletions projects/demo-app/src/app/home/home.component.html
Original file line number Diff line number Diff line change
@@ -1,2 +1,13 @@
<button (click)="signUp()">Sign Up</button>
<button (click)="login()">Login</button>
<div class="theme-wrapper">
<button (click)="changeTheme('light')">Light Mode</button>
<button (click)="changeTheme('dark')">Dark Mode</button>
</div>
<descope
(success)="onSuccess()"
(error)="onError()"
[projectId]="projectId"
[theme]="theme"
flowId="sign-in"
></descope>
5 changes: 5 additions & 0 deletions projects/demo-app/src/app/home/home.component.scss
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,8 @@
flex-direction: column;
gap: 20px;
}

.theme-wrapper {
display: flex;
gap: 20px;
}
2 changes: 2 additions & 0 deletions projects/demo-app/src/app/home/home.component.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { HomeComponent } from './home.component';
import { DescopeAuthConfig } from '../../../../angular-sdk/src/lib/descope-auth.module';
import createSdk from '@descope/web-js-sdk';
import mocked = jest.mocked;
import { NO_ERRORS_SCHEMA } from '@angular/core';

jest.mock('@descope/web-js-sdk');

Expand All @@ -23,6 +24,7 @@ describe('HomeComponent', () => {
});

TestBed.configureTestingModule({
schemas: [NO_ERRORS_SCHEMA],
declarations: [HomeComponent],
providers: [
DescopeAuthConfig,
Expand Down
17 changes: 17 additions & 0 deletions projects/demo-app/src/app/home/home.component.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,17 @@
import { Component } from '@angular/core';
import { DescopeAuthService } from '../../../../angular-sdk/src/lib/descope-auth.service';
import { Router } from '@angular/router';
import { environment } from '../../environments/environment';

@Component({
selector: 'app-home',
templateUrl: './home.component.html',
styleUrls: ['./home.component.scss']
})
export class HomeComponent {
projectId: string = environment.descopeProjectId;
theme: 'light' | 'dark' = 'light';

constructor(
private router: Router,
private authService: DescopeAuthService
Expand Down Expand Up @@ -55,4 +59,17 @@ export class HomeComponent {
}
});
}

onSuccess() {
console.log('SUCCESSFULLY LOGGED IN FROM WEB COMPONENT');
this.router.navigate(['/protected']).catch((err) => console.error(err));
}

onError() {
console.log('ERROR FROM LOG IN FLOW FROM WEB COMPONENT');
}

changeTheme(theme: 'light' | 'dark') {
this.theme = theme;
}
}
1 change: 1 addition & 0 deletions tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
"baseUrl": "./",
"outDir": "./dist/out-tsc",
"forceConsistentCasingInFileNames": true,
"strictPropertyInitialization": false,
"strict": true,
"noImplicitOverride": true,
"noPropertyAccessFromIndexSignature": true,
Expand Down

0 comments on commit 7565fc8

Please sign in to comment.