I'm new to flutter, I have a flutter project that I'm building for android, ios and web. And only in the web build do I need to use the js library to encrypt api requests. Here is this library: https://www.npmjs.com/package/#expressms/smartapp-bridge and she's not very good, as far as I'm concerned. This is a requirement of the client and he provides this library. I can't rewrite it in dart. I have to use js callbacks for encryption and decryption in every api call. And I can't do it. I figured out what to use https://pub.dev/packages/js but it's too complicated for me. Maybe there is a good example with code?
#JS()
library web.js;
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:medlike/app.dart';
import 'package:js/js.dart';
#JS()
#anonymous
abstract class WebBridge {
external void constructor();
external factory WebBridge();
external void addGlobalListener();
external void enableLogs();
}
void main() async {
WidgetsFlutterBinding.ensureInitialized();
SystemChrome.setPreferredOrientations(
[DeviceOrientation.portraitUp, DeviceOrientation.portraitDown]);
runApp(App());
var bridge = WebBridge();
print(bridge.toString());
}
I managed to cope with this problem, although it was not easy. It was necessary to wrap the response in promiseToFeature and allowInterop
index.html
<script>
var bridge = window.webBridgeInstance;
async function sendBotEvent(eventObject) {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve('done!');
}, 1500);
setTimeout(() => {
reject('err rejected!');
}, 2500);
});
}
</script>
api_client.dart
#JS()
library main;
import 'dart:js' as js;
typedef Callback<T> = dynamic Function(T arg);
#JS()
class Promise<T> {
external Promise<T> then(Callback<T> successCallback,
[Function errorCallback]);
external Promise<T> catchIt(Function errorCallback);
}
#JS('sendBotEvent')
external Promise<dynamic> sendBotEvent(Object objectParams);
class SmartAppClient {
Future<String> get(String endpoint, Object params) async {
return await promiseToFuture(sendBotEvent({
'method': 'get_$endpoint',
'params': params,
}).then(js.allowInterop((data) {
print('SUCCESS: $data');
return data;
}), js.allowInterop((err) {
print('ERROR: $err');
return err;
})));
}
}
run:
void testSmartappFuncs() async {
await SmartAppClient()
.get(
'urlString',
{},
)
.then((value) {
print('RESULT RESULT RESULT');
print(value);
})
.catchError((onError) {
print('ERROR ERROR ERROR');
print(onError);
});
}
Related
I am trying to experiment with typescript namespaces. It works perfectly when i dont import other modules but when I import other modules in my case sort the exported function get cant be used in other file even though they are in same namespace.
import { sort } from "../sort"
namespace Api {
export async function get(url:string){
const res = await fetch("api/"+url)
return await res.json()
}
export class Users {
private uid: string
constructor(uid: string){
this.uid = uid
}
public async getUserProfile(){
return await get(`/u/${this.uid}/profile`)
}
public async getUserSongs(){
return await get(`/u/${this.uid}/musics`)
}
}
}
/// <reference path="api.ts" />
namespace Api {
export class Track{
private track_id: string
constructor(track_id){
this.track_id = track_id
}
public async hasLiked(){
return await get("/track/"+this.track_id+"/hasLiked")
}
}
}
It says could not find name get
But when i dont import sort it works perfectly
namespace Api {
export async function get(url:string){
const res = await fetch("api/"+url)
return await res.json()
}
export class Users {
private uid: string
constructor(uid: string){
this.uid = uid
}
public async getUserProfile(){
return await get(`/u/${this.uid}/profile`)
}
public async getUserSongs(){
return await get(`/u/${this.uid}/musics`)
}
}
}
When you add import statement to the file it turns that .ts file into a module, and "moves" all declarations in the file into the scope of that module.
So namespace Api is not seen outside of its module definition and removes it from global namespace. There are a few ways to deal with it:
You can turn you main Api file into definition file by changing its extension to d.ts and wrap namespace Api into declare global {}. By that you don't even need triple slash import, it will still be available in global scope. Your new api.d.ts should look like this:
import { sort } from "./sort";
declare global {
namespace Api {
export async function get(url: string) {
const res = await fetch("api/" + url);
return await res.json();
}
export class Users {
private uid: string;
constructor(uid: string) {
this.uid = uid;
}
public async getUserProfile() {
return await get(`/u/${this.uid}/profile`);
}
public async getUserSongs() {
return await get(`/u/${this.uid}/musics`);
}
}
}
}
Ommiting top-level import and using dynamic import like this:
namespace Api {
export async function get(url: string) {
const sort = await import("./sort");
const res = await fetch("api/" + url);
return await res.json();
}
export class Users {
private uid: string;
constructor(uid: string) {
this.uid = uid;
}
public async getUserProfile() {
return await get(`/u/${this.uid}/profile`);
}
public async getUserSongs() {
return await get(`/u/${this.uid}/musics`);
}
}
}
Simply turning namespace Api with Track into d.ts resolves the issue
Working with declaration merging and modules in typescript is pretty complex and i think there are still many workarounds and pitfalls about it so my answer can definitely be extended.
I'm trying to fetch HTML from a webpage, using a class with a single async method. I use Typescript 3.4.3, request-promise 4.2.4.
import * as rp from 'request-promise';
class HtmlFetcher {
public uri: string;
public html: string;
public constructor(uri: string) {
this.uri = uri;
}
public async fetch() {
await rp(this.uri).then((html) => {
this.html = html;
}).catch((error) => {
throw new Error('Unable to fetch the HTML page');
});
}
}
export { HtmlFetcher };
I use following code to test my class with Jest 24.8.0. The address at line 6 is used for the sole purpose of testing, I also tried with different URIs.
import { HtmlFetcher } from './htmlFetcher.service';
describe('Fetch HTML', () => {
it('should fetch the HTMl at the given link', () => {
const uri = 'http://help.websiteos.com/websiteos/example_of_a_simple_html_page.htm';
const fetcher = new HtmlFetcher(uri);
fetcher.fetch();
expect(fetcher.html).toBeDefined();
});
});
I expect the html property to contain the HTML string fetched at the given address after the fetch() method has been called. However, the test code fails, and logs that fetcher.html is undefined. The Typescript, Jest and request-promise docs did not provide any help. What am I doing wrong?
Found the answer thanks to TKoL's comments, and another look at a doc I already read 50 times, namely: Jest async testing. I should've RTFM more carefully...
The test code must be async as well.
import { HtmlFetcher } from './htmlFetcher.service';
describe('Fetch HTML', () => {
it('should fetch the HTMl at the given link', async () => { // Added async keyword
const uri = 'http://help.websiteos.com/websiteos/example_of_a_simple_html_page.htm';
const fetcher = new HtmlFetcher(uri);
await fetcher.fetch(); // Added await keyword
expect(fetcher.html).toBeDefined();
});
});
Issue
I'm looking for a way to bring up a NestJS application with mocked providers. This is necessary for provider contract tests because a service needs to be brought up in isolation. Using the Pact library, testing the provider assumes that the provider service is already running. It needs to be able to make HTTP requests against the actual server (with some dependencies mocked if necessary). PactJS
Current Research
I've looked into the docs for NestJS and the closest solution I can find is pasted below. From what I can tell, this solution tells the module to replace any provider called CatsService with catsService. This theoretically would work for provider contract testing purposes, but I don't think this allows for the entire app to be brought up, just a module. There is no mention in the docs for being able to bring up the app on a specific port using the testing module. I've tried to call app.listen on the returned app object and it fails to hit a breakpoint placed right after the call.
import * as request from "supertest";
import { Test } from "#nestjs/testing";
import { CatsModule } from "../../src/cats/cats.module";
import { CatsService } from "../../src/cats/cats.service";
import { INestApplication } from "#nestjs/common";
describe("Cats", () => {
let app: INestApplication;
let catsService = { findAll: () => ["test"] };
beforeAll(async () => {
const module = await Test.createTestingModule({
imports: [CatsModule]
})
.overrideProvider(CatsService)
.useValue(catsService)
.compile();
app = module.createNestApplication();
await app.init();
});
it(`/GET cats`, () => {
return request(app.getHttpServer())
.get("/cats")
.expect(200)
.expect({
data: catsService.findAll()
});
});
afterAll(async () => {
await app.close();
});
});
Java Example
Using Spring a configuration class, mocks can be injected into the app when running with the "contract-test" profile.
#Profile({"contract-test"})
#Configuration
public class ContractTestConfig {
#Bean
#Primary
public SomeRepository getSomeRepository() {
return mock(SomeRepository.class);
}
#Bean
#Primary
public SomeService getSomeService() {
return mock(SomeService.class);
}
}
Update
Since version 4.4 you can also use listen since it now also returns a Promise.
You have to use the method listenAsync instead of listen so that you can use it with await:
beforeAll(async () => {
const moduleFixture = await Test.createTestingModule({
imports: [AppModule],
})
.overrideProvider(AppService).useValue({ root: () => 'Hello Test!' })
.compile();
app = moduleFixture.createNestApplication();
await app.init();
await app.listenAsync(3000);
^^^^^^^^^^^^^^^^^^^^^
});
Then you can make actual http requests instead of relying on supertest. (I am using the nodejs standard http library in this example.)
import * as http from 'http';
// ...
it('/GET /', done => {
http.get('http://localhost:3000/root', res => {
let data = '';
res.on('data', chunk => data = data + chunk);
res.on('end', () => {
expect(data).toEqual('Hello Test!');
expect(res.statusCode).toBe(200);
done();
});
});
});
Don't forget to close the application or otherwise your test will run until closed manually.
afterAll(() => app.close());
I have a service which based on the environment file, loads up a configuration object in memory. However, the reading of the settings is asynchronous and the application starts before even all the settings are loaded and crashes. Is there any way to 'await' for these functions before the dependency injection is complete.
My service :
import { Injectable } from '#angular/core';
import { IAppConfig } from '../models/app-config.model';
#Injectable()
export class AppConfig {
settings: IAppConfig;
version: any;
constructor() {
this.loadConfig();
}
// reads env.json file
// based on which environment it is loads config setting from
// environment specific config settings.
public loadConfig() {
return new Promise((resolve, reject) => {
const envFile = '/env.json';
this.readJsonFile(envFile).
then((envData) => {
const configFile = `assets/appconfigs/config.${envData.env}.json`;
this.version = envData.version;
this.readJsonFile(configFile).
then((configsettings) => {
this.settings = configsettings;
resolve(this.settings);
});
});
});
}
// reads json file and returns the json object promise
public readJsonFile(jsonUrl: string): any {
return new Promise((resolve, reject) => {
let retObject: any;
const xhr = new XMLHttpRequest();
xhr.overrideMimeType('application/json');
xhr.open('GET', jsonUrl, true);
xhr.onreadystatechange = () => {
if (xhr.readyState === 4) {
if (xhr.status === 200) {
retObject = JSON.parse(xhr.responseText);
resolve(retObject);
} else {
reject(`Could not load file '${jsonUrl}': ${xhr.status}`);
}
}
};
xhr.send(null);
});
}
}
I want the settings object to be loaded before the application fires up. The other way was to make this class static and call loadConfig but it is a nightmare for testbeds. Is there anything I could specify when providing the service in the module?
I'm not sure about this solution (untested code), but I guess you could async/await everything.
Is there any reason why you're not using Angular's http service, and instead, making a manual XMLHttpRequest?
It could be something like this :
constructor() {
this.run()
}
async run() {
await this.loadConfig()
}
// reads env.json file
// based on which environment it is loads config setting from
// environment specific config settings.
public loadConfig() {
return new Promise( async (resolve, reject) => {
const envFile = '/env.json';
let envData = await this.readJsonFile(envFile)
const configFile = `assets/appconfigs/config.${envData.env}.json`;
this.version = envData.version;
this.settings = await this.readJsonFile(configFile);
resolve(); // No need to pass this.settings to resolve()
});
}
// reads json file and returns the json object promise
public readJsonFile(jsonUrl: string): any {
return this.http
.get(jsonUrl)
.map(res => res.json())
.toPromise()
}
I want the settings object to be loaded before the application fires up.
You could/should make use of the injection token APP_INITIALIZER: angular.io/APP_INITIALIZER
In your case, something like below code would suffice.
In your AppModule:
{
provide: APP_INITIALIZER,
useFactory: initApp,
deps: [AppConfig]
}
export function initApp(appConfig: AppConfig) {
return () => appConfig.loadConfig();
}
More information on factory providers: angular.io/FactoryProvider
Here's the scenario: There is a file called api.js which has method api() to make api calls. There is another class called AutoLogout which has functionality to show autologout modal and logging out user after certain time in case of no activity. These works fine.
index.js in ../services
export { default as api } from './api';
// export { api, onResponse } from './api'; tried this as well
export { default as userService } from './userService';
api.js
import userService from './userService';
export function onResponse(response) {
// returns response to calling function
return response;
}
async function api(options) {
const settings = Object.assign(
{
headers: {
'content-type': 'application/json',
'x-correlation-id': Math.random()
.toString(36)
.substr(2),
},
mode: 'cors',
credentials: 'include',
body: options.json != null ? JSON.stringify(options.json) : undefined,
},
options,
);
const response = await window.fetch(`/api/v0${options.endpoint}`, settings);
// calling onResponse() to send the response
onResponse(response);
if (response.status === 403) return userService.logout();
if (response.status > 299) throw new Error();
if (response.status === 204) return true;
return response.json ? response.json() : false;
}
export default api;
Now, in response header I've "x-expires-at" and I want to use it in autologout. So, that if api call is made the user token resets.
auto-lougout.js
import { userService, api } from '../services';
// import { userService, api, onResponse } from '../services'; tried this as well
export default class AutoLogout {
constructor() {
super();
if (!userService.getUser()) userService.logout();
// here I am not able to call onResponse() from api.js
// getting response received from onResponse()
api.onResponse((resp) => { console.log(resp.headers.get('x-expires-at'))});
}
}
Trying to implement as an example given in this article:
https://zpao.com/posts/calling-an-array-of-functions-in-javascript/
Here I cannot use export { api, onResponse }; as api is already being used at multiple places in whole project.
How do I call onResponse function in one js file from another class in another js file ? Am I using callback correctly here ? If not, how to use callback correctly in such scenario ?
Here I cannot use export { api, onResponse }; as api is already being used at multiple places in whole project.
The correct import/export syntax for your project would be
// api.js
export function onResponse() { … }
export default function api() { … }
// index.js
export { default as userService } from './userService';
export { default as api, onResponse } from './api';
// elsewhere
import { userService, api, onResponse } from '../services';
// use these three
Am I using callback correctly here?
No, not at all. onResponse should not be a function declared in your api.js file, and it should not be exported from there - sparing you all the above hassle.
If not, how to use callback correctly in such scenario?
Make the callback a parameter of the function that uses it:
export default async function api(options, onResponse) {
// ^^^^^^^^^^
const settings = Object.assign(…);
const response = await window.fetch(`/api/v0${options.endpoint}`, settings);
onResponse(response);
…
}
Then at the call of the api function, pass your callback as an argument:
api(options, resp => {
console.log(resp.headers.get('x-expires-at'));
});