im busy with Webpush to WebRTC application and need some help understanding the way some of it fits together.
After looking at the RFC: https://www.rfc-editor.org/rfc/rfc8599#section-12
I see that in Section 12 :
The value of the 'pn-provider' URI parameter is "webpush".
The value of the 'pn-param' URI parameter MUST NOT be used.
The value of the 'pn-prid' URI parameter is the push subscription
URI.
So, in my app.js I have implemented this something like this:
var pubKey = urlBase64ToUint8Array(<publicKey>)
var o = { userVisibleOnly: true, applicationServerKey: pubKey }
swg.pushManager.subscribe(o).then(function(sub){
var contact-params = {
"pn-provider" : "webpush"
"pn-prid" : sub.endpoint
}
// apply contact-params, and register
}
So as we can see above, the RFC specifies, 'pn-provider' and 'pn-prid' are to be used with your register. The pushManager.subscribe() provides me with this information, great... so far so good.
I have a Pubic key that I use above (with is corresponding private key that I have not used yet).
Now on the server my PHP code from here: https://github.com/web-push-libs/web-push-php
specifies a sample like the following:
$prid = $_GET['prid'];
$payload = '{ "text" : "Incoming Call !!" }';
$auth = array(
'VAPID' => array(
'subject' => $vapidSubject,
'publicKey' => $publicKey, // Same as in app.js
'privateKey' => $privateKey,
)
);
$subscription = Subscription::create(
array(
'endpoint' => $prid,
'contentEncoding' => 'aesgcm'
)
);
$webPush = new WebPush($auth);
$report = $webPush -> sendOneNotification($subscription, $payload);
The above php code works!... but, the payload (event.data) is null.
If I test with: https://web-push-codelab.glitch.me/ and give it the full subscription, with (the exact string of the sub from the app.js) it also works (as expected).
The same thing for my PHP code - If I add the keys:
$subscription = Subscription::create(
array(
'endpoint' => $prid,
'keys' => [
'p256dh' => '<sub.keys.p256dh>',
'auth' => '<sub.keys.auth>'
],
'contentEncoding' => 'aesgcm'
)
);
Then it work fine, and my payload is delivered.
So you can see, the problem is that the Push RFC says I only need to store 1 piece of information (endpoint), then have 1 common piece of information (public key) and 1 private piece of information (private key).
But in order for the the push code to work it needs 3 pieces of information, the endpoint, p256dh, auth, and then the public key and private key. (The p256dh and auth is of corse different with each endpoint.)
I think i'm missing something here, can someone help me understand this?
Related
I know the title might sound confusing, but I'm not sure how to express it accurately.
Let me explain the use case:
We've got a calendar with recurring events (potentially infinite list of events repeating eg every Monday forever)
I've got a bunch of UI elements interested in some part of those events (eg. some needs events from next week, some from next month, some from the previous year - they all might be displayed at once). Note that those requirements might overlap - the eg. current week and current month actually include an overlapping set of days, but those UI components are not aware of each other.
I need to refresh those events on some actions in the app (eg. the user changed calendar permission, list of calendars in his system, user minimized and re-opened the app) - I've got streams of those events like $appStatus $permissionStatus, $selectedCalendars
Fetching events is expensive and I want to re-use fetching and existing data as much as possible. Eg. if 3 components need events from the same week, I don't want to make 3 requests.
Also if one component requests events from March and another from 2nd week of March, I'd like to solve it with one request if possible. Note they might start their request at different moment.
I also don't want to fetch events that are not requested anymore (eg. part of UI showing events from the previous month is not rendered anymore)
I've got a few ideas about how to do it in plain JS, but I wonder if it's possible to create RxJs-ish solution for that
My raw-js-rx-mix ideas:
When a new range is requested, I 'round' it to full weeks. Then I create new observable with shared value for each week and remember it in some map
eg
const observablesMap = new Map();
function getObservableForDate(date: Date) {
const weekStart = getStartOfWeek(date);
// if we have 'cached' version of observable for this week - return cached one
if (observablesMap.has(weekStart.getTime()) {
return observablesMap.get(weekStart.getTime())
}
const weekObservable = // create observable that shares results
// set it to the map
// return it
}
But after a few hours of research, I have no idea if and how would I implement it in RxJS way.
Let's assume the fetching function signature is fetchEvents(startDate, endDate)
ps. I don't expect a working solution code, but just some guide. I've checked most of RxJS documentation and could not find anything promising for such use case
I created a running rxjs solution on stackblitz. I will also add the code here in case stackblitz shuts down one time in the future.
General idea
Save all requests and decide if you need a new request or not
Depending on previous decicion fake or process http request
Depending on the previous kind of request find the existing request or return the newly requested one.
If there need to be further infos please let me know and I try to explain in detail or add comments. I did not implement 100% of your requirements but with the following solution as base it should be possible to expand interfaces and functions within the pipes to implement them. Also if you see any code redundancy or optimization to interfaces, let me know and I will adapt
Interfaces
interface Requests {
action: Action,
currentRequest: number,
accumulatedRequests: number[],
}
interface FulfilledRequest extends Requests{
httpRequest: string
}
interface Response {
accumulatedHttpResponses: {
request: number,
response: string
}[],
response: string
}
Default Values
const defaultRequests: Requests = {
action: Action.IgnoreRequest,
currentRequest: -1,
accumulatedRequests: []
}
const defaultResponseStorage: Response = {
accumulatedHttpResponses: [],
response: ''
}
Enum
enum Action {
IgnoreRequest,
ProcessRequest
}
Functions
const isUpdateAction = (action: Action) => action === Action.ProcessRequest
const fakeHttp = (date: number): Observable<string> => of('http response for: ' + date).pipe(
tap(v => console.warn('fakeHttp called with: ', v))
);
const getResponseForExistingRequest = (storage: Response, request: FulfilledRequest): Response => {
const index = storage.accumulatedHttpResponses.findIndex(response => response.request === request.currentRequest);
return {
accumulatedHttpResponses: storage.accumulatedHttpResponses,
response: storage.accumulatedHttpResponses[index].response
}
}
const getResponseForNewRequest = (storage: Response, request: FulfilledRequest): Response => {
const newEntry = {request: request.currentRequest, response: request.httpRequest};
return {
accumulatedHttpResponses: [...storage.accumulatedHttpResponses, newEntry],
response: request.httpRequest
}
}
const getIgnoredRequest = (date: number, requests: Requests): Requests => ({
currentRequest: date,
action: Action.IgnoreRequest,
accumulatedRequests: requests.accumulatedRequests
})
const getProcessedRequests = (date: number, requests: Requests): Requests => ({
currentRequest: date,
action: Action.ProcessRequest,
accumulatedRequests: [...requests.accumulatedRequests, date]
})
const processRequest = (requests: Requests, date: number): Requests => {
const requestExists = requests.accumulatedRequests.some(request => request === date);
return requestExists
? getIgnoredRequest(date, requests)
: getProcessedRequests(date, requests)
}
const processFulfilledRequest = (storage: Response, request: FulfilledRequest): Response => isUpdateAction(request.action)
? getResponseForNewRequest(storage, request)
: getResponseForExistingRequest(storage, request)
const fulfillFakeRequest = (requests: Requests): Observable<FulfilledRequest> => of('').pipe(
map(response => ({...requests, httpRequest: response})),
)
const fulfillHttpRequest = (requests: Requests): Observable<FulfilledRequest> => fakeHttp(requests.currentRequest).pipe(
map(response => ({...requests, httpRequest: response}))
)
Final Connection via Observables
const date$: Subject<number> = new Subject();
const response$ = date$.pipe(
scan(processRequest, defaultRequests),
switchMap((requests): Observable<FulfilledRequest> => isUpdateAction(requests.action)
? fulfillHttpRequest(requests)
: fulfillFakeRequest(requests)
),
scan(processFulfilledRequest, defaultResponseStorage),
map(response => response.response)
)
Within Node.js (MS Bot Framework is being used), I'm trying to iterate over an array containing multiple objects that was returned by fetch(). It does one iteration and then throws an error.
I did already take node-fetch out of the equation and used a static array of my objects. I also tried converting to an array. Still the same result: TypeError: (images || []).forEach is not a function after the first iteration went perfectly well.
This is what it looks like at the moment (shortened object values for readability):
// exact copy of what fetch returns
let test = [
{
title: 'Power BI Desktop—Interactive Reports | Microsoft Power BI',
link: 'https://support.office.com/',
description: 'Create interactive reports with data',
thumbnail: 'https://powerbi.microsoft.com/'
}, {
title: 'What is Power BI administration? - Power BI',
link: 'https://support.office.com/',
description: 'Learn about the configuration of Power BI ',
thumbnail: 'https://learn.microsoft.com/'
}, {
title: 'Add a PowerBI tab to Teams',
link: 'https://support.office.com/',
description: 'You can add a new or existing PowerBI',
thumbnail: 'https://support.office.com/'
}
];
let cardContent = [];
test.forEach(element => {
logger.debug('working on: %j', element);
let card = CardFactory.heroCard(
element.title,
element.description,
element.thumbnail,
[element.link],
['Open Item']
);
cardContent.push(card);
});
Just for reference, here is part of my fetch function:
return fetch(url + question)
.then(res => res.json())
.then(jsonResponse => {
return jsonResponse;
})
I really don't know what else to try now. The exact same setup worked on a legacy version of my application - only with axios but I tried that as well.
I run your code and it works - this mean that test array doesn't contains what do you think it contains (probably it contains Promise returned by fetch() ). Use fetch with await keyword to get response and again use await to get json (something like code below - I write it form head)
async function load(url, question) {
...
return await (await fetch(url + question)).json()
}
...
test = await load(url, question) // this call shoud be also inside async function
It's not possible to tell with certainty why (images || []).forEach is not a function without knowing where/how images is defined, but I'm going to hazard a guess and say images exists, and is an object instead of an array.
Try executing
const images = {};
images.forEach(image => console.log(image))
And you'll see the same result, Uncaught TypeError: images.forEach is not a function.
Maybe it is the images.data field within the object that you want to run forEach on.
please apologize the unprecise title for this question, I am not an experienced programmer and even less so in node.js
My intent is a simple one: I want to use the bitfinex-api-node package (a node.js wrapper for bitfinex cryptocurrency exchange) that I installed via npm to read price data of various currency-pairs from the exchange to calculate better trading strategies.
The example code provided in the readme.md works fine, this is a stripped down version that creates a BFX-object which subscribes to a ticker of a given currency-pair and constantly outputs ticker-data:
const BFX = require('bitfinex-api-node')
const API_KEY = 'secret'
const API_SECRET = 'secret'
const opts = {
version: 2,
transform: true
}
const bws = new BFX(API_KEY, API_SECRET, opts).ws
bws.on('open', () => {
bws.subscribeTicker('BTCUSD')
})
bws.on('ticker', (pair, ticker) => {
console.log('Ticker:', ticker)
})
bws.on('error', console.error)
so far so good. Now for the sake of a simple example let's say I want to get the current price of two currency pairs (BTC/USD, ETH/USD) and add them an display the result. My obviously naive approach is like this:
const BFX = require('bitfinex-api-node')
const API_KEY = 'secret'
const API_SECRET = 'secret'
const opts = {
version: 2,
transform: true
}
const bws1 = new BFX(API_KEY, API_SECRET, opts).ws
const bws2 = new BFX(API_KEY, API_SECRET, opts).ws
var priceBTCUSD;
var priceETHBTC;
bws1.on('open', () => {
bws1.subscribeTicker('BTCUSD')
})
bws2.on('open', () => {
bws2.subscribeTicker('ETHUSD')
})
bws1.on('ticker', (pair, ticker) => {
//console.log('Ticker1:', ticker.LAST_PRICE)
priceBTCUSD = ticker.LAST_PRICE
})
bws2.on('ticker', (pair, ticker) => {
//console.log('Ticker2:', ticker.LAST_PRICE)
priceETHBTC = ticker.LAST_PRICE
})
bws1.on('error', console.error)
bws2.on('error', console.error)
//HERE IT COMES:
console.log(priceBTCUSD+priceETHBTC)
where the resulting output of the last line is "NaN". It seems the last line that logs the desired result to the console is executed before the BFX-objects establish a connection and receive any data.
How do I set this up properly? How can I retrieve data from the received data-stream? Do I really need a BFX-websocket object per currency pair? How would I read the price-data once, close down the websocket connection (which is not needed after reading the price once) and reconnect to read the price for a different currency pair?
Thank you! Feel free to request more data if my question isn't clear enough.
Kind regards,
s
Oh, your console.log is too soon there. Try this (I skipped a few lines):
bws1.on('ticker', (pair, ticker) => {
//console.log('Ticker1:', ticker.LAST_PRICE)
priceBTCUSD = ticker.LAST_PRICE;
printResults();
})
bws2.on('ticker', (pair, ticker) => {
//console.log('Ticker2:', ticker.LAST_PRICE)
priceETHBTC = ticker.LAST_PRICE
printResults();
})
bws1.on('error', console.error)
bws2.on('error', console.error)
//HERE IT COMES:
function printResults() {
if (priceBTCUSD && priceETHBTC)
console.log(priceBTCUSD+priceETHBTC)
}
Now, this is not the best approach, but it gets you of the ground. A better way is to have both prices asked on the same websocket, so when you get both prices back, call this function to calculate your results.
Having problem with using forEach function with http requests.
I have a _watchlistElements variable, which holds following data:
[{"xid":"DP_049908","name":"t10"},{"xid":"DP_928829","name":"t13"},{"xid":"DP_588690","name":"t14"},{"xid":"DP_891890","name":"t16"},{"xid":"DP_693259","name":"t17"}]
Now, Im making a function which will download data from server for each of these xid elements:
private download() {
this._watchlistElements.forEach(v =>
this.http.get('http://localhost:8080/getValue/' + v.xid)
.subscribe(res => this._values = res.json()));
}
It has to download data as object for every v.xid value and store it inside the _values variable.
private _values: Array<WatchlistComponent> = [];
But somehow, angular returns an error with v.xid element. It doesn't see that variable. But it's kinda strange, because when I do it just in console, I mean: store that json inside a variable and use forEach function on this v.xid elements, everything works well.
ERROR in [default] C:\Users\src\app\appBody\watchlist\watchl
ist.component.ts:51:115
Property 'xid' does not exist on type 'WatchlistComponent'.
The xid exists... but inside the _watchlistElements which downloads the data asynchonously...
I'm not 100% sure this method is right, but if you have any ideas how to fix it, please tell me.
What happens when you print out the _values array?
The error above is a type error. What does the WatchlistComponent interface look like? Does it include an xid property?
You can get around the type error by overriding the type like...
private download() {
this._watchlistElements.forEach((v as any) =>
this.http.get('http://localhost:8080/getValue/' + v.xid)
.subscribe(res => this._values = res.json()));
}
As far as helping you structure your code better. If you want to combine the result of many Observables, I would use something like forkJoin.
private download():void {
//create an array of Observables
let el$ = _watchlistElements.map(el => {
return this.http.get('http://localhost:8080/getValue/' + el.xid)
.map(res: Response => <any>res.json());
});
//subscribe to all, and store the data, el$ is an array of Observables
Observable.forkJoin(el$).subscribe( data => {
this._values = data; //data will be structured as [res[0], res[1], ...]
});
}
HERE is a Plunker with the above method working. https://plnkr.co/edit/woXUIDa0gc55WJPOZMKh?p=preview
Related: angular2 rxjs observable forkjoin
He there!
If have a question that is related to two different issues I currently have in an application I'm working on.
Issue 1:
- There is a message system. Users are able to send each other messages. I would like to have a real time pop up when the user gets a new message and is not on the inbox page.
Issue 2:
- I would like to create a basic achievement system, one of the achievements could (for example) be: "Receive a message."
Now I think both functionalities can be achieved through the same way. Anyone of you has any experience with this type of real time communication? I really have no idea where to start. I would really love it if it is not to heavy.
Thanks a lot.
Here's a boilerplate you might use for long polling (using jQuery and Yii):
Server-side:
class MessagesController extends CController {
public function actionPoll( $sincePk, $userPk ) {
while (true) {
$messages = Message::model()->findAll([
'condition' => '`t`.`userId` = :userPk AND `t`.`id` > :sincePk',
'order' => '`t`.`id` ASC',
'params' => [ ':userPk' => (int)$userPk, ':sincePk' => (int)$sincePk ],
]);
if ($messages) {
header('Content-Type: application/json; charset=utf-8');
echo json_encode(array_map(function($message){
return array(
'pk' => $message->primaryKey,
'from' => $message->from,
'text' => $message->text,
/* and whatever more information you want to send */
);
}, $messages));
}
sleep(1);
}
}
}
Client-side:
<?php
$userPk = 1;
$lastMessage = Messages::model()->findByAttributes([ 'userId' => $userId ], [ 'order' => 'id ASC' ]);
$lastPk = $lastMessage ? $lastMessage->primaryKey : 0;
?>
var poll = function( sincePk ) {
$.get('/messages/poll?sincePk='+sincePk+'&userPk=<?=$userPk?>').then(function(data) {
// the request ended, parse messages and poll again
for (var i = 0;i < data.length;i++)
alert(data[i].from+': '+data[i].text);
poll(data ? data[i].pk : sincePk);
}, function(){
// a HTTP error occurred (probable a timeout), just repoll
poll(sincePk);
});
}
poll(<?=$lastPk?>);
Remember to implement some kind of authentication to avoid users reading each others messages.