I have an angular2 service and I want it to do the following:
Ftp to remote server
Find a file read some lines from it
Build a 'results' json object and return to the calling component
So - actually I have steps 1 / 2 working - but of course its all 'async'. So what is happening is in my component I am doing this call to the service where this.ftp is the instance of my service:
this.servers = this.ftp.lookForServers();
Now this correctly calls the lookForServers method of my FTP service , which looks like this:
lookForServers(){
var servers = [];
var whereAreWe = 0;
var possibles = ["/path/to/servers/"];
for(var i=0;i<possibles.length;i++){
whereAreWe = i;
this.c.list(possibles[i],false,(err,list)=>{
for(var p=0;p<list.length;p++){
console.log(list[p]);
var server_version = this.grabLog(possibles[whereAreWe]+list[p].name);
servers.push({
name: list[p].name,
path: possibles[whereAreWe]+list[p].name,
version: server_version
});
}
});
}
return servers;
}
Now - the this.grabLog(possibles[whereAreWe]+list[p].name); function call ends up making further calls to the this.c - the FTP client, which of course is async, so this method returns almost immediately - whilst the callbacks continue to run. Those callbacks download a file, and then another callback function processes this file - again line by line, asynchronously picking out various details i want to store.
By the end of this chain - I have all my details in the final :
lineReader.on('close', () => { function - but of course my `this.ftp.lookForServers();` function call has long gone....and the component is none the wiser.
So how can I let this work happen asynchronously, and still pass back to the component my results JSON object once the work is complete? This is probably quite a simple question about how do I make a service call a component callback...?
You don't need it to run syncronously. You should make lookForServers (and the other function it's using) use observables, then subscribe to the result like this:
this.ftp.lookForServers().subscribe((data) => { this.servers = data });
Here are the implementations:
const Client = require('ftp');
const fs = require('fs');
const readline = require('readline');
import { NextObserver } from 'rxjs/Observer';
import { Observable } from 'rxjs/Rx';
interface server {
name: string;
path: string;
version: string;
java_version: string;
}
export class FTPClient {
username: string;
password: string;
host: string;
port: number;
c: any;
constructor() {
}
init(username, password, host, port) {
console.log("initiating FTP connection to:" + host + "on port:" + port);
this.username = username;
this.password = password;
this.host = host;
this.port = port;
this.c = new Client();
console.log("Client created");
}
connect() {
console.log("About to start connection");
this.c.on('ready', () => {
this.c.list((err: any, list: any) => {
if (err) throw err;
console.dir(list);
this.c.end();
});
});
// connect to localhost:21 as anonymous
var connectProps = {
host : this.host,
port : this.port,
user : this.username,
password : this.password
};
console.log("Connecting now...");
this.c.connect(connectProps);
}
public lookForServers(name: string): Observable<any[]> {
return Observable.create((observer: NextObserver <any[]>) => {
let servers = [];
let whereAreWe = 0;
let possibles = [ "/path/to/servers/" ];
for (var i = 0; i < possibles.length; i++) {
whereAreWe = i;
this.c.list(possibles[ i ], false, (err: any, list: any) => {
for (var p = 0; p < list.length; p++) {
this.grabMessagesLog(possibles[ whereAreWe ] + list[ p ].name)
.subscribe((data: any) => {
let server_version = data;
servers.push({
name : list[ p ].name,
path : possibles[ whereAreWe ] + list[ p ].name,
version : server_version
});
observer.next(servers);
observer.complete();
}
);
}
});
}
});
}
grabMessagesLog(path): Observable<any> {
return Observable.create((observer: NextObserver <any>) => {
let result = '';
let unix = Math.round(+new Date() / 1000);
this.c.binary(function(err) {
console.log(err);
});
this.c.get(path + "/logs/messages.log", (err, stream) => {
if (err) throw err;
stream.once('close', () => {
this.c.end();
this.getServerMetadataFromMessagesLog(unix + "_messages.log")
.subscribe((data) => {
stream.pipe(fs.createWriteStream(unix + "_messages.log"));
observer.next(data);
observer.complete();
});
});
});
});
}
getServerMetadataFromMessagesLog(path): Observable<any> {
return Observable.create((observer: NextObserver <any>) => {
let lineReader = readline.createInterface({
input : fs.createReadStream(path)
});
let server_version = "";
let java_version = "";
let line_no = 0;
lineReader.on('line', function(line) {
line_no++;
console.log("line number is:" + line_no);
if (line.includes("STUFF") && line.includes("FLAG2") && line_no == 2) {
var first = line.split("FLAG2")[ 1 ];
var last = first.split(" (")[ 0 ];
var version = "FLAG2" + last;
this.server_version = version;
console.log("version is:" + version);
}
if (line.includes("java.version =")) {
var javav = line.split("java.version =")[ 1 ];
this.java_version = javav;
lineReader.close();
}
console.log('Line from file:', line);
});
lineReader.on('close', () => {
var res = {
version : server_version,
java_version : java_version
};
alert("RES IS:" + JSON.stringify(res));
observer.next(res);
observer.complete();
});
});
}
}
Try using a recursive function with the $timeout function of Angular
function recursiveWait(server_version){
if(server_version != null){
return;
}
$timeout(function(){recursiveWait()}, 500);
}
And place it here:
console.log(list[p]);
var server_version = this.grabLog(possibles[whereAreWe]+list[p].name);
recursiveWait(server_version);
servers.push({
name: list[p].name,
This will ask the var if it's != null If it's equal it will call the function again in 500ms, if it's not it will return and exit the function, letting the code continue.
Related
I need your help, it turns out that I am trying to use the Hubstaff api. I am working on nodejs to make the connection, I followed the documentation (official hubstaff api documentation) and use the methods they give as implementation examples (example of implementation nodejs).
But I get the following error:
I don't know why this happens, and I can't find more examples of how I can implement this api. The openid-client lib is used to make the connection through the token and a state follow-up is carried out to refresh the token.
To be honest, I'm not understanding how to implement it. Can someone who has already used this API give me a little explanation? I attach the code
hubstaffConnect.util
const {
Issuer,
TokenSet
} = require('openid-client');
const fs = require('fs');
const jose = require('jose');
// constants
const ISSUER_EXPIRE_DURATION = 7 * 24 * 60 * 60; // 1 week
const ACCESS_TOKEN_EXPIRATION_FUZZ = 30; // 30 seconds
const ISSUER_DISCOVERY_URL = 'https://account.hubstaff.com';
// API URl with trailing slash
const API_BASE_URL = 'https://api.hubstaff.com/';
let state = {
api_base_url: API_BASE_URL,
issuer_url: ISSUER_DISCOVERY_URL,
issuer: {}, // The issuer discovered configuration
issuer_expires_at: 0,
token: {},
};
let client;
function loadState() {
return fs.readFileSync('./configState.json', 'utf8');
}
function saveState() {
fs.writeFileSync('./configState.json', JSON.stringify(state, null, 2), 'utf8');
console.log('State saved');
}
function unixTimeNow() {
return Date.now() / 1000;
}
async function checkToken() {
if (!state.token.access_token || state.token.expires_at < (unixTimeNow() + ACCESS_TOKEN_EXPIRATION_FUZZ)) {
console.log('Refresh token');
state.token = await client.refresh(state.token);
console.log('Token refreshed');
saveState();
}
}
async function initialize() {
console.log('API Hubstaff API');
let data = loadState();
data = JSON.parse(data);
if (data.issuer) {
state.issuer = new Issuer(data.issuer);
state.issuer_expires_at = data.issuer_expires_at;
}
if (data.token) {
state.token = new TokenSet(data.token);
}
if (data.issuer_url) {
state.issuer_url = data.issuer_url;
}
if (data.api_base_url) {
state.api_base_url = data.api_base_url;
}
if (!state.issuer_expires_at || state.issuer_expires_at < unixTimeNow()) {
console.log('Discovering');
state.issuer = await Issuer.discover(state.issuer_url);
state.issuer_expires_at = unixTimeNow() + ISSUER_EXPIRE_DURATION;
console.log(state.issuer);
}
client = new state.issuer.Client({
// For personal access token we can use PAT/PAT.
// This is only needed because the library requires a client_id where as the API endpoint does not require it
client_id: 'PAT',
client_secret: 'PAT',
});
saveState();
console.log('API Hubstaff initialized');
}
async function request(url, options) {
await checkToken();
let fullUrl = state.api_base_url + url;
return client.requestResource(fullUrl, state.token, options);
}
function tokenDetails() {
let ret = {};
if (state.token.access_token) {
ret.access_token = jose.JWT.decode(state.token.access_token);
}
if (state.token.refresh_token) {
ret.refresh_token = jose.JWT.decode(state.token.refresh_token);
}
return ret;
}
module.exports = {
initialize,
checkToken,
request,
tokenDetails
};
controller
const usersGet = async(req, res = response) => {
const response = await api.request('v2/organizations', {
method: 'GET',
json: true,
});
const body = JSON.parse(response.body);
res.render('organizations', {
title: 'Organization list',
organizations: body.organizations || []
});
};
I can't seem to send an identifier over a request body to Firebase from a Swift iOS app.
My javascript code works (I tested it in an API builder):
exports.numberOfVideos = functions.https.onRequest((request, response) => {
var attributionID = request.body.data.attributionID || "No attributionID found.";
functions.logger.info('[LOG]: request.body has a value of ' + JSON.stringify(request.body), {structuredData: true});
var db = admin.firestore();
db.collection("content").get().then(snapshot => {
var total = 0;
snapshot.forEach(doc => {
if (doc.data().attributionID == attributionID) {
total++;
}
// stuff = stuff.concat(newelement);
});
response.send('{"data": {"attributionID": "' + attributionID + '", "numberOfContents": "' + total + '"}}');
return "";
}).catch(reason => {
response.send(reason)
})
});
My Swift code can't see to convey the data:
static func getContentCount(for obj: Object, completion: #escaping (_ count: Int?) -> Void) {
let httpsCallable = functions.httpsCallable("numberOfVideos")
// httpsCallable.setValue("application/json", forKey: "Content-Type")
httpsCallable.call(["attributionID" : obj.id]) { result, error in
if let error = error as NSError? {
if error.domain == FunctionsErrorDomain {
let code = FunctionsErrorCode(rawValue: error.code)
let message = error.localizedDescription
let details = error.userInfo[FunctionsErrorDetailsKey]
print(error)
}
}
if let response = result?.data as? [String: [String: Any]] {
if let data = response["data"] {
if let countAsAString = data["numberOfContents"] as? String {
let count = Int(countAsAString)
if count != nil {
completion(count)
} else {
print("Data is valid but no count found: \(result?.data)")
completion(nil)
}
}
}
} else {
print("Data is invalid: \(result?.data)")
completion(nil)
}
}
}
When it worked from a REST API tester, here was the body:
POST /numberOfVideos HTTP/1.1
Host: *****************
Content-Type: application/json
Content-Length: 49
{"data":{"attributionID":"biNiaWVtmjUKoTQ1fTZu"}}
Any help would be appreciated!
Fixed it. I had to use an NSDictionary, not a Dictionary:
NSDictionary(dictionary: ["attributionID" : obj.id])
I am having some issues with my a particular call in my cloud function that doesn't seem to be resolving correctly.
This is the code that doesn't want to resolve:
console.log('Getting Search Patterns');
let searchPatterns: FirebaseFirestore.QuerySnapshot;
try {
searchPatterns = await admin
.firestore()
.collection('FYP_LOCATIONS')
.get();
} catch (error) {
console.error(error);
}
console.log(`Search Patterns Received: ${searchPatterns}`);
LOG:
As you can see in the log, my function runs up until the console log before the try block, then stops until the function times out. I'm not sure what it is that is causing this issue.
EDIT: I have reformatted my code by separating out each of the different parts in my cloud function into separate functions that I can call; the resulting getSearchTerms() function is as follows:
async function getSearchTerms(): Promise<FirebaseFirestore.DocumentData[]> {
try {
const snapshot = await admin
.firestore()
.collection('FYP_LOCATIONS')
.get();
console.log('Get Returned');
return snapshot.docs.map(doc => doc.data());
} catch (e) {
console.error(e);
return [];
}
}
This still stops at the same point in the function execution, the full function is here, this has been updated to the latest version:
import * as functions from 'firebase-functions';
import * as admin from 'firebase-admin';
import * as path from 'path';
import * as WordExtractor from 'word-extractor';
import * as textract from 'textract';
import suffixArray from './suffixArray';
// interface Location {
// lid: string;
// location_name: string;
// location_type: string;
// sentimental_value: number;
// }
// interface Context {
// lid: string;
// context_string: string;
// fid: string;
// }
export const processFile = functions.storage.object().onFinalize(async file => {
const serviceAccount = require(__dirname + '/../config/serviceAccount.json');
admin.initializeApp({
credential: admin.credential.cert(serviceAccount),
databaseURL: 'https://fyp-alex.firebaseio.com',
});
const firestore = admin.firestore();
const fileBucket: string = file.bucket;
const filePath: string = file.name;
const fileDet: string = path.basename(filePath);
const fileNameSplit: string[] = fileDet.split('.');
const fileExt: string = fileNameSplit.pop();
const fileName: string = fileNameSplit.join('.');
const bucket = admin.storage().bucket(fileBucket);
const fileRef = bucket.file(filePath);
const _path: string = `/tmp/${fileName}.${fileExt}`;
console.log(`File path ${filePath}`);
console.log('Getting Download URL');
try {
console.log(`Downloading to: ${_path}`);
await fileRef.download({ destination: _path });
console.log('File Saved');
console.log(`Getting Details: ${_path}`);
const text: string = await getText(_path, fileExt);
console.log(`Processing: ${fileName}`);
console.log('Creating Suffix Array');
const suffix_array = suffixArray(text);
console.log(`Suffix Array Created: ${suffix_array}`);
console.log('Getting Search Patterns');
const searchTerms: FirebaseFirestore.DocumentData[] = await getSearchTerms();
console.log('Search Patterns Received');
const promises = [];
const allContexts: Object[] = [];
for (const searchDoc of searchTerms) {
const searchTerm = searchDoc.location_name.toLowerCase();
console.log(searchTerm);
const matchedIndexes = search(text, searchTerm, suffix_array);
const contexts = createContexts(matchedIndexes, searchDoc, text, fileName);
allContexts.concat(contexts);
}
for (const context of allContexts) {
const p = admin
.firestore()
.collection('FYP_CONTEXTS')
.add(context);
promises.push(p);
}
await Promise.all(promises);
const data = {
processed: 1,
};
return firestore.doc(`FYP_FILES/${fileName}`).update(data);
} catch (e) {
console.error(e);
const data = {
processed: 2,
};
return firestore.doc(`FYP_FILES/${fileName}`).update(data);
}
});
async function getText(_path: string, fileExt: string) {
let text: string = '';
switch (fileExt) {
case 'docx':
case 'doc':
const extractor = new WordExtractor();
const extracted = await extractor.extract(_path);
text = extracted.getBody();
break;
case 'pdf':
break;
case 'txt':
textract.fromFileWithPath(_path, function(extractedError: any, string: string) {
if (extractedError) {
console.error(extractedError);
}
if (string !== null) {
text = string;
}
});
break;
default:
console.log('Unsupported File Type');
}
return text;
}
async function getSearchTerms(): Promise<FirebaseFirestore.DocumentData[]> {
try {
const snapshot = await admin
.firestore()
.collection('FYP_LOCATIONS')
.get();
console.log('Get Returned');
return snapshot.docs.map(doc => doc.data());
} catch (e) {
console.error(e);
return [];
}
}
function createContexts(
matchedIndexes: number[],
searchDoc: FirebaseFirestore.DocumentData,
text: string,
fileName: string
) {
console.log('Creating Contexts');
const contexts = [];
const searchTerm = searchDoc.location_name.toLowerCase();
for (const index of matchedIndexes) {
let left = index - 25;
let right = index + searchTerm.length + 25;
if (left < 0) {
left = 0;
}
if (right > text.length) {
right = text.length;
}
const context = text.substring(left, right);
contexts.push({
lid: searchDoc.lid,
context_string: context,
fid: fileName,
});
}
return contexts;
}
function search(text: string, searchTerm: string, suffix_array: number[]) {
console.log(`Beginning search for: ${searchTerm}`);
let start = 0;
let end = suffix_array.length;
const matchedIndexes: Array<number> = [];
while (start < end) {
const mid: number = (end - 1) / 2;
const index: number = suffix_array[mid];
const finalIndex: number = index + searchTerm.length;
if (finalIndex <= text.length) {
const substring: string = text.substring(index, finalIndex);
const match: number = searchTerm.localeCompare(substring);
if (match === 0) {
console.log(`Match Found at Index: ${index}`);
matchedIndexes.push(index);
} else if (match < 0) {
end = mid;
} else if (match > 0) {
start = mid;
}
console.log(matchedIndexes);
}
}
if (matchedIndexes.length === 0) {
console.log(`No matches found for search term: ${searchTerm}`);
}
return matchedIndexes;
}
Hopefully the full function provides a bit more context.
I have watched Doug's videos through a few times but I am still coming up against this, I did notice that removing the await that seems to be failing (as in removing the promise all together) seemed to cause an earlier await to fail. This is indicative of it being an issue with promises later on in the function but I cannot for the life of me find the issue, I will keep trying but hopefully that provides some useful context.
You aren't letting the function know when the async operations have finished.
I would guess that you want to collect all of the async operations in an array and wait for all of them to finish before letting the function exit.
(Those youtube videos by Doug, mentioned in the comments above are quite good and do a more thorough job of explaining why)
ie.
const requests = [];
const things = [1,2,3];
for (let index = 0; index < things.length; index++) {
const element = things[index];
const promise = firebase.firestore().push(element);
requests.push(promise);
}
return Promise.all(requests);
I am building a node application, and trying to neatly organize my code. I wrote a serial module that imports the serial libs and handles the connection. My intention was to write a basic module and then reuse it over and over again in different projects as needed. The only part that changes per use is how the incoming serial data is handled. For this reason I would like to pull out following handler and redefine it as per the project needs. How can I use module exports to redefine only this section of the file?
I have tried added myParser to exports, but that gives me a null and I would be out of scope.
Handler to redefine/change/overload for each new project
myParser.on('data', (data) => {
console.log(data)
//DO SOMETHING WITH DATA
});
Example usage: main.js
const serial = require('./serial');
const dataParser = require('./dataParser');
const serial = require('./serial');
//call connect with CL args
serial.connect(process.argv[2], Number(process.argv[3]))
serial.myParser.on('data',(data) => {
//Do something unique with data
if (dataParser.parse(data) == 0)
serial.send('Error');
});
Full JS Module below serial.js
const SerialPort = require('serialport');
const ReadLine = require('#serialport/parser-readline');
const _d = String.fromCharCode(13); //char EOL
let myPort = null;
let myParser = null;
function connect(port, baud) {
let portName = port || `COM1`;
let baudRate = baud || 115200;
myPort = new SerialPort(portName, {baudRate: baudRate})
myParser = myPort.pipe(new ReadLine({ delimiter: '\n'}))
//Handlers
myPort.on('open', () => {
console.log(`port ${portName} open`)
});
myParser.on('data', (data) => {
console.log(data)
});
myPort.on('close', () => {
console.log(`port ${portName} closed`)
});
myPort.on('error', (err) => {
console.error('port error: ' + err)
});
}
function getPorts() {
let portlist = [];
SerialPort.list((err, ports) => {
ports.forEach(port => {
portlist.push(port.comName)
});
})
return portlist;
}
function send(data) {
myPort.write(JSON.stringify(data) + _d, function (err) {
if (err) {
return console.log('Error on write: ', err.message);
}
console.log(`${data} sent`);
});
}
function close() {
myPort.close();
}
module.exports = {
connect, getPorts, send, close
}
The problem is that a module is used where a class or a factory would be appropriate. myParser cannot exist without connect being called, so it doesn't make sense to make it available as module property, it would be unavailable by default, and multiple connect calls would override it.
It can be a factory:
module.exports = function connect(port, baud) {
let portName = port || `COM1`;
let baudRate = baud || 115200;
let myPort = new SerialPort(portName, {baudRate: baudRate})
let myParser = myPort.pipe(new ReadLine({ delimiter: '\n'}))
//Handlers
myPort.on('open', () => {
console.log(`port ${portName} open`)
});
myParser.on('data', (data) => {
console.log(data)
});
myPort.on('close', () => {
console.log(`port ${portName} closed`)
});
myPort.on('error', (err) => {
console.error('port error: ' + err)
});
function getPorts() {
let portlist = [];
SerialPort.list((err, ports) => {
ports.forEach(port => {
portlist.push(port.comName)
});
})
return portlist;
}
function send(data) {
myPort.write(JSON.stringify(data) + _d, function (err) {
if (err) {
return console.log('Error on write: ', err.message);
}
console.log(`${data} sent`);
});
}
function close() {
myPort.close();
}
return {
myParser, getPorts, send, close
};
}
So it could be used like:
const serial = require('./serial');
const connection = serial(...);
connection.myParser.on('data',(data) => {
//Do something unique with data
if (dataParser.parse(data) == 0)
connection.send('Error');
});
I just want check my notes.j that return a notes.logNote(note) when I call: node app.js add --title=" "
, if the --title=" " was declared so it wouldn't written over in notes-data.json.
this is my app.js
const fs = require('fs');
const os = require('os');
const _ = require('lodash');
const yargs = require('yargs');
const notes = require('./notes.js')
const argv = yargs.argv;
var command = argv._[0];
console.log(' Your Command: ', command);
console.log(' Your processes is: ',process.argv);
console.log(' Your Yargs is: ', argv);
if (command === 'add') {
var note = notes.addNote(argv.title, argv.body);
if (note) {
console.log(" You write the new object in notes");
notes.logNote(note);
} else {
console.log(" You write an exist objects in node");
console.log(' -- which is');
notes.logNote(note);
// here I can't call note.title = undifiend same with note.body = undifined
// I want to log the title if was exists
// console.log(` Title: ${note.title}`);
// console.log(` body: ${note.body}`);
} else {
console.log(' Your command not in my list command that i made');
};
and I called some few function from notes.js
const fs = require('fs');
var fetchNotes = () => {
try {
var noteString = fs.readFileSync('notes-data.json');
return JSON.parse(noteString);
} catch (e) {
return [];
}
};
var saveNotes = (notes) => {
fs.writeFileSync('notes-data.json', JSON.stringify(notes));
};
var logNote = (note) => {
console.log(' -- which is');
console.log(` Title: ${note.title}`);
console.log(` body: ${note.body}`);
};
var addNote = (title, body) => {
var notes = fetchNotes();
var note = {
title,
body
};
var duplicatesNotes = notes.filter((note) => note.title === title);
console.log( "If you get message your input object was duplicated");
if(duplicatesNotes.length === 0) {
notes.push(note);
saveNotes(notes);
return note;
}
};
module.exports = {
addNote,
logNote
};
So I called twice node app.js --title="drunk" --body="weed" and result get error title from ${note.title} was undefined.
Need some advise though, thanks for reading
try {
var noteString = fs.readFileSync('notes-data.json');
return JSON.parse(noteString);
} catch (e) {
return [];
}
fs will return buffer and you cannot parse the buffer, you have to stringify the buffer first and then parse it.
var note=fs.readFileSync('notes-data.json')
var noteString=JSON.stringify(note)
return JSON.parse(noteString)