Skip to content

Commit ff92deb

Browse files
committed
feat: angular support
1 parent 3aefbe3 commit ff92deb

File tree

7 files changed

+265
-20
lines changed

7 files changed

+265
-20
lines changed

package.json

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -59,9 +59,9 @@
5959
"url": "https://github.com/kefahB"
6060
},
6161
{
62-
"name": "Kefah BADER ALDIN",
63-
"email": "kefah.bader@gmail.com",
64-
"url": "https://github.com/kefahB"
62+
"name": "Ruslan Lekhman",
63+
"email": "lekhman112@gmail.com",
64+
"url": "https://github.com/lekhmanrus"
6565
}
6666
],
6767
"author": {
@@ -75,8 +75,11 @@
7575
"homepage": "https://github.com/nativescript-community/https",
7676
"readmeFilename": "README.md",
7777
"devDependencies": {
78+
"@angular/common": "^15.2.8",
79+
"@angular/core": "^15.2.8",
7880
"@nativescript-community/plugin-seed-tools": "file:tools",
79-
"@nativescript-community/template-snippet": "file:demo-snippets"
81+
"@nativescript-community/template-snippet": "file:demo-snippets",
82+
"@nativescript/angular": "^15.2.0"
8083
},
8184
"bootstrapper": "nativescript-plugin-seed",
8285
"commitlint": {

packages/https/package.json

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -33,9 +33,9 @@
3333
"url": "https://github.com/kefahB"
3434
},
3535
{
36-
"name": "Kefah BADER ALDIN",
37-
"email": "kefah.bader@gmail.com",
38-
"url": "https://github.com/kefahB"
36+
"name": "Ruslan Lekhman",
37+
"email": "lekhman112@gmail.com",
38+
"url": "https://github.com/lekhmanrus"
3939
}
4040
],
4141
"author": {
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
import { Injectable } from '@angular/core';
2+
import { HttpRequest } from '@angular/common/http';
3+
4+
@Injectable()
5+
export class ExcludedService {
6+
private readonly _urlList: Array<string> = [ ];
7+
8+
public static isMultipartFormRequest(request: HttpRequest<any>): boolean {
9+
const headers = request.headers.get('Content-Type');
10+
return headers ? headers.includes('application/x-www-form-urlencoded') : false;
11+
}
12+
13+
public addUrl(domain: string): void {
14+
this._urlList.push(domain);
15+
}
16+
17+
public contains(needle: string): boolean {
18+
return Boolean(this._urlList.filter((url) => url === needle).length);
19+
}
20+
21+
public skipSslPinning(request: HttpRequest<any>): boolean {
22+
return this.contains(request.url);
23+
}
24+
}

src/https/angular/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
export * from './ns-https.module';
2+
export * from './ns-http-xhr-backend';
Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
import { Inject, Injectable, InjectionToken, Optional } from '@angular/core';
2+
import {
3+
HttpErrorResponse,
4+
HttpEvent,
5+
HttpHeaders,
6+
HttpRequest,
7+
HttpResponse,
8+
XhrFactory
9+
} from '@angular/common/http';
10+
import { Observable, from, throwError } from 'rxjs';
11+
import { catchError, map } from 'rxjs/operators';
12+
import { NSFileSystem, NsHttpBackEnd } from '@nativescript/angular';
13+
import {
14+
Headers,
15+
HttpsRequestObject,
16+
HttpsRequestOptions,
17+
HttpsResponse,
18+
request as httpsRequest
19+
} from '../request';
20+
import { ExcludedService } from './excluded.service';
21+
22+
/** Https request default options. */
23+
export type HttpsRequestDefaultOptions
24+
= Pick<HttpsRequestOptions, 'timeout' | 'allowLargeResponse' | 'cachePolicy' | 'cookiesEnabled'>
25+
& { useLegacy?: boolean; };
26+
27+
/** Page size injection token. */
28+
export const HTTPS_REQUEST_DEFAULT_OPTIONS
29+
= new InjectionToken<HttpsRequestDefaultOptions>('HTTPS_REQUEST_DEFAULT_OPTIONS');
30+
31+
@Injectable()
32+
export class NativeScriptHttpXhrBackend extends NsHttpBackEnd {
33+
constructor(
34+
xhrFactory: XhrFactory,
35+
nsFileSystem: NSFileSystem,
36+
private readonly _excludedService: ExcludedService,
37+
@Optional()
38+
@Inject(HTTPS_REQUEST_DEFAULT_OPTIONS)
39+
private readonly _defaults?: HttpsRequestDefaultOptions
40+
) {
41+
super(xhrFactory, nsFileSystem);
42+
}
43+
44+
public handle(req: HttpRequest<any>): Observable<HttpEvent<any>> {
45+
let result: Observable<HttpEvent<any>>;
46+
if (this._isLocalRequest(req.url) || this._excludedService.skipSslPinning(req)) {
47+
result = super.handle(req);
48+
} else {
49+
result = this._request(req)
50+
.pipe(
51+
map((response: HttpsResponse) => {
52+
return new HttpResponse({
53+
body: response.content,
54+
headers: new HttpHeaders(response.headers),
55+
status: response.statusCode,
56+
statusText: response.reason,
57+
url: req.url
58+
});
59+
}),
60+
catchError((error) => {
61+
return throwError(() => new HttpErrorResponse({
62+
status: error.status,
63+
headers: error.headers,
64+
statusText: error.statusText,
65+
url: req.url
66+
}));
67+
})
68+
);
69+
}
70+
71+
return result;
72+
}
73+
74+
private _isLocalRequest(url: string): boolean {
75+
return url.indexOf('~') === 0 || url.indexOf('/') === 0;
76+
}
77+
78+
private _request(request: HttpRequest<any>) {
79+
const method = request.method as ('GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH' | 'HEAD');
80+
return from(httpsRequest({
81+
url: request.url,
82+
method,
83+
headers: this._mapHeaders(request),
84+
params: this._mapParams(request),
85+
body: request.body,
86+
timeout: this._defaults?.timeout ?? 3 * 60,
87+
allowLargeResponse: this._defaults?.allowLargeResponse ?? true,
88+
cachePolicy: this._defaults?.cachePolicy ?? 'noCache',
89+
cookiesEnabled: this._defaults?.cookiesEnabled ?? false
90+
}, this._defaults?.useLegacy ?? true));
91+
}
92+
93+
private _mapHeaders(request: HttpRequest<any>): Headers {
94+
const headerKeys = request.headers.keys();
95+
const headers = headerKeys.reduce<Headers>((accumulator, key) => {
96+
const values = request.headers.getAll(key);
97+
if (values !== null && values !== undefined) {
98+
accumulator[key] = values.length > 1 ? values.join(' ') : values[0];
99+
}
100+
return accumulator;
101+
}, { });
102+
103+
if (Object.keys(headers).length) {
104+
return headers;
105+
}
106+
107+
return {
108+
'Content-Type': 'application/json',
109+
'Accept': 'application/json'
110+
};
111+
}
112+
113+
private _mapParams(request: HttpRequest<any>): HttpsRequestObject {
114+
const paramKeys = request.params.keys();
115+
const params = paramKeys.reduce<HttpsRequestObject>((accumulator, key) => {
116+
const values = request.params.getAll(key);
117+
if (values !== null && values !== undefined) {
118+
accumulator[key] = values.length > 1 ? values : values[0];
119+
}
120+
return accumulator;
121+
}, { });
122+
return params;
123+
}
124+
}
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
import { NgModule, ModuleWithProviders, Optional, Inject, InjectionToken } from '@angular/core';
2+
import { HttpBackend } from '@angular/common/http';
3+
import { NativeScriptHttpClientModule } from '@nativescript/angular';
4+
import { HttpsSSLPinningOptions, enableSSLPinning } from '../request';
5+
import {
6+
HTTPS_REQUEST_DEFAULT_OPTIONS,
7+
NativeScriptHttpXhrBackend,
8+
HttpsRequestDefaultOptions
9+
} from './ns-http-xhr-backend';
10+
import { ExcludedService } from './excluded.service';
11+
12+
/** Page size injection token. */
13+
export const HTTPS_SSL_PINNING_OPTIONS
14+
= new InjectionToken<HttpsSSLPinningOptions>('HTTPS_SSL_PINNING_OPTIONS');
15+
16+
@NgModule({
17+
providers: [
18+
ExcludedService,
19+
NativeScriptHttpXhrBackend,
20+
{ provide: HttpBackend, useExisting: NativeScriptHttpXhrBackend },
21+
{ provide: HTTPS_REQUEST_DEFAULT_OPTIONS, useValue: { } },
22+
{ provide: HTTPS_SSL_PINNING_OPTIONS, useValue: { host: '', certificate: '' } }
23+
],
24+
imports: [
25+
NativeScriptHttpClientModule
26+
],
27+
exports: [
28+
NativeScriptHttpClientModule
29+
]
30+
})
31+
export class NativeScriptHttpsModule {
32+
constructor(
33+
@Optional()
34+
@Inject(HTTPS_SSL_PINNING_OPTIONS)
35+
defaults?: HttpsSSLPinningOptions
36+
) {
37+
enableSSLPinning(defaults ?? { host: '', certificate: '' });
38+
}
39+
/**
40+
* Creates and configures a module.
41+
* @param defaults Https request default options.
42+
* @returns A wrapper around an NgModule that associates it with the providers.
43+
*/
44+
static forRoot(
45+
defaults: HttpsRequestDefaultOptions & HttpsSSLPinningOptions = { host: '', certificate: '' }
46+
): ModuleWithProviders<NativeScriptHttpsModule> {
47+
return {
48+
ngModule: NativeScriptHttpsModule,
49+
providers: [
50+
{ provide: HTTPS_REQUEST_DEFAULT_OPTIONS, useValue: defaults },
51+
{ provide: HTTPS_SSL_PINNING_OPTIONS, useValue: defaults }
52+
]
53+
};
54+
}
55+
}

0 commit comments

Comments
 (0)