mapcomplete/Logic/Web/MangroveReviews.ts

197 lines
6.1 KiB
TypeScript
Raw Normal View History

2020-12-07 03:02:50 +01:00
import * as mangrove from 'mangrove-reviews'
import {UIEventSource} from "../UIEventSource";
2020-12-08 23:44:34 +01:00
import {Review} from "./Review";
export class MangroveIdentity {
public keypair: any = undefined;
2020-12-11 16:29:51 +01:00
public readonly kid: UIEventSource<string> = new UIEventSource<string>(undefined);
2021-04-23 17:22:01 +02:00
private readonly _mangroveIdentity: UIEventSource<string>;
2020-12-08 23:44:34 +01:00
constructor(mangroveIdentity: UIEventSource<string>) {
const self = this;
this._mangroveIdentity = mangroveIdentity;
2021-06-30 15:42:12 +02:00
mangroveIdentity.addCallbackAndRunD(str => {
if (str === "") {
2020-12-08 23:44:34 +01:00
return;
}
mangrove.jwkToKeypair(JSON.parse(str)).then(keypair => {
self.keypair = keypair;
2020-12-11 16:29:51 +01:00
mangrove.publicToPem(keypair.publicKey).then(pem => {
console.log("Identity loaded")
self.kid.setData(pem);
})
2020-12-08 23:44:34 +01:00
})
})
2021-01-07 04:50:12 +01:00
try {
if ((mangroveIdentity.data ?? "") === "") {
this.CreateIdentity();
}
2021-04-23 17:22:01 +02:00
} catch (e) {
2021-01-07 04:50:12 +01:00
console.error("Could not create identity: ", e)
2020-12-08 23:44:34 +01:00
}
}
/**
* Creates an identity if none exists already.
* Is written into the UIEventsource, which was passed into the constructor
* @constructor
*/
private CreateIdentity() {
if ("" !== (this._mangroveIdentity.data ?? "")) {
throw "Identity already defined - not creating a new one"
}
const self = this;
mangrove.generateKeypair().then(
keypair => {
self.keypair = keypair;
mangrove.keypairToJwk(keypair).then(jwk => {
self._mangroveIdentity.setData(JSON.stringify(jwk));
})
});
}
}
2020-12-07 03:02:50 +01:00
export default class MangroveReviews {
2021-04-23 17:22:01 +02:00
private static _reviewsCache = {};
private static didWarn = false;
2020-12-08 23:44:34 +01:00
private readonly _lon: number;
private readonly _lat: number;
private readonly _name: string;
private readonly _reviews: UIEventSource<Review[]> = new UIEventSource<Review[]>([]);
private _dryRun: boolean;
private _mangroveIdentity: MangroveIdentity;
2021-04-23 17:22:01 +02:00
private _lastUpdate: Date = undefined;
private constructor(lon: number, lat: number, name: string,
identity: MangroveIdentity,
dryRun?: boolean) {
this._lon = lon;
this._lat = lat;
this._name = name;
this._mangroveIdentity = identity;
this._dryRun = dryRun;
if (dryRun && !MangroveReviews.didWarn) {
MangroveReviews.didWarn = true;
console.warn("Mangrove reviews will _not_ be saved as dryrun is specified")
}
}
2020-12-08 23:44:34 +01:00
public static Get(lon: number, lat: number, name: string,
identity: MangroveIdentity,
2021-04-23 17:22:01 +02:00
dryRun?: boolean) {
2020-12-08 23:44:34 +01:00
const newReviews = new MangroveReviews(lon, lat, name, identity, dryRun);
2021-04-23 17:22:01 +02:00
2020-12-08 23:44:34 +01:00
const uri = newReviews.GetSubjectUri();
const cached = MangroveReviews._reviewsCache[uri];
2021-04-23 17:22:01 +02:00
if (cached !== undefined) {
2020-12-08 23:44:34 +01:00
return cached;
}
MangroveReviews._reviewsCache[uri] = newReviews;
2020-12-07 03:02:50 +01:00
2021-04-23 17:22:01 +02:00
return newReviews;
2020-12-07 03:02:50 +01:00
}
2020-12-08 23:44:34 +01:00
/**
* Gets an URI which represents the item in a mangrove-compatible way
* @constructor
*/
public GetSubjectUri() {
let uri = `geo:${this._lat},${this._lon}?u=50`;
if (this._name !== undefined && this._name !== null) {
uri += "&q=" + this._name;
}
return uri;
}
2020-12-07 03:02:50 +01:00
/**
* Gives a UIEVentsource with all reviews.
* Note: rating is between 1 and 100
*/
2020-12-08 23:44:34 +01:00
public GetReviews(): UIEventSource<Review[]> {
2021-04-23 17:22:01 +02:00
if (this._lastUpdate !== undefined && this._reviews.data !== undefined &&
2020-12-08 23:44:34 +01:00
(new Date().getTime() - this._lastUpdate.getTime()) < 15000
2021-04-23 17:22:01 +02:00
) {
2020-12-08 23:44:34 +01:00
// Last update was pretty recent
return this._reviews;
}
this._lastUpdate = new Date();
const self = this;
mangrove.getReviews({sub: this.GetSubjectUri()}).then(
2020-12-07 03:02:50 +01:00
(data) => {
2020-12-08 23:44:34 +01:00
const reviews = [];
2020-12-11 15:27:52 +01:00
const reviewsByUser = [];
2020-12-07 03:02:50 +01:00
for (const review of data.reviews) {
const r = review.payload;
2020-12-11 16:29:51 +01:00
console.log("PublicKey is ", self._mangroveIdentity.kid.data, "reviews.kid is", review.kid);
const byUser = self._mangroveIdentity.kid.map(data => data === review.signature);
2020-12-11 15:27:52 +01:00
const rev: Review = {
made_by_user: byUser,
2020-12-07 03:02:50 +01:00
date: new Date(r.iat * 1000),
comment: r.opinion,
author: r.metadata.nickname,
2020-12-08 23:44:34 +01:00
affiliated: r.metadata.is_affiliated,
2020-12-07 03:02:50 +01:00
rating: r.rating // percentage points
2020-12-11 15:27:52 +01:00
};
(rev.made_by_user ? reviewsByUser : reviews).push(rev);
2020-12-07 03:02:50 +01:00
}
2020-12-11 16:29:51 +01:00
self._reviews.setData(reviewsByUser.concat(reviews))
2020-12-07 03:02:50 +01:00
}
);
2020-12-08 23:44:34 +01:00
return this._reviews;
}
AddReview(r: Review, callback?: (() => void)) {
callback = callback ?? (() => {
return undefined;
});
const payload = {
sub: this.GetSubjectUri(),
rating: r.rating,
opinion: r.comment,
metadata: {
nickname: r.author,
}
};
2020-12-11 15:27:52 +01:00
if (r.affiliated) {
// @ts-ignore
payload.metadata.is_affiliated = true;
}
2020-12-08 23:44:34 +01:00
if (this._dryRun) {
2020-12-11 15:27:52 +01:00
console.warn("DRYRUNNING mangrove reviews: ", payload);
if (callback) {
if (callback) {
callback();
}
this._reviews.data.push(r);
this._reviews.ping();
}
2020-12-08 23:44:34 +01:00
} else {
2020-12-11 15:27:52 +01:00
mangrove.signAndSubmitReview(this._mangroveIdentity.keypair, payload).then(() => {
if (callback) {
callback();
}
this._reviews.data.push(r);
this._reviews.ping();
})
2020-12-08 23:44:34 +01:00
}
2020-12-11 15:27:52 +01:00
2020-12-07 03:02:50 +01:00
}
}