Added web and email notifications. Tests. Fire page
This commit is contained in:
parent
df05b33e82
commit
3bf21c8caf
32 changed files with 696 additions and 88 deletions
|
|
@ -37,7 +37,7 @@ alexwine:bootstrap-4
|
|||
selaias:cookie-consent # Cookie consent
|
||||
gadicc:blaze-react-component # Add braze to react
|
||||
255kb:meteor-status # Connect status
|
||||
mixmax:smart-disconnect # Disconnect on lost focus
|
||||
# mixmax:smart-disconnect # Disconnect on lost focus (disabled to get notif)
|
||||
flowkey:raven # js errors
|
||||
vjrj:piwik # Stats
|
||||
mdg:geolocation
|
||||
|
|
@ -45,7 +45,9 @@ natestrauser:publish-performant-counts
|
|||
maximum:server-transform
|
||||
mys:fonts # fonst in npm
|
||||
ostrio:mailer # mailer
|
||||
mizzao:user-status # user status (online, etc)
|
||||
# babrahams:constellation # dev utility
|
||||
percolate:migrations # db migrations
|
||||
ostrio:meteor-root
|
||||
mizzao:user-status
|
||||
babrahams:constellation
|
||||
percolate:migrations
|
||||
meteortesting:mocha
|
||||
practicalmeteor:chai
|
||||
|
|
|
|||
|
|
@ -7,16 +7,12 @@ accounts-oauth@1.1.15
|
|||
accounts-password@1.5.0
|
||||
alanning:roles@1.2.16
|
||||
aldeed:collection2-core@2.0.1
|
||||
aldeed:template-extension@4.0.0
|
||||
alexwine:bootstrap-4@4.0.0-beta.2
|
||||
allow-deny@1.1.0
|
||||
audit-argument-checks@1.0.7
|
||||
autoupdate@1.3.12
|
||||
babel-compiler@6.24.7
|
||||
babel-runtime@1.1.1
|
||||
babrahams:constellation@0.4.10
|
||||
babrahams:editable-json@0.6.4
|
||||
babrahams:temple@0.4.5
|
||||
base64@1.0.10
|
||||
binary-heap@1.0.10
|
||||
blaze@2.3.2
|
||||
|
|
@ -27,13 +23,6 @@ caching-html-compiler@1.1.2
|
|||
callback-hook@1.0.10
|
||||
check@1.2.5
|
||||
coffeescript@1.0.17
|
||||
constellation:autopublish@0.4.4
|
||||
constellation:console@1.4.5
|
||||
constellation:plugins@0.4.7
|
||||
constellation:position@0.4.4
|
||||
constellation:session@0.4.5
|
||||
constellation:subscriptions@0.4.5
|
||||
constellation:tiny@0.4.4
|
||||
dburles:mongo-collection-instances@0.3.5
|
||||
ddp@1.4.0
|
||||
ddp-client@2.2.0
|
||||
|
|
@ -58,8 +47,6 @@ gadicc:blaze-react-component@1.4.0
|
|||
geojson-utils@1.0.10
|
||||
github-oauth@1.2.0
|
||||
google-oauth@1.2.4
|
||||
gwendall:body-events@0.1.6
|
||||
gwendall:session-json@0.1.7
|
||||
hot-code-push@1.0.4
|
||||
html-tools@1.0.11
|
||||
htmljs@1.0.11
|
||||
|
|
@ -76,10 +63,11 @@ maximum:server-transform@0.5.0
|
|||
mdg:geolocation@1.3.0
|
||||
meteor@1.8.0
|
||||
meteor-base@1.2.0
|
||||
meteortesting:browser-tests@0.1.2
|
||||
meteortesting:mocha@0.5.0
|
||||
minifier-css@1.2.16
|
||||
minifier-js@2.2.1
|
||||
minimongo@1.4.0
|
||||
mixmax:smart-disconnect@0.0.4
|
||||
mizzao:timesync@0.3.4
|
||||
mizzao:user-status@0.6.7
|
||||
mobile-experience@1.0.5
|
||||
|
|
@ -105,6 +93,8 @@ peerlibrary:fiber-utils@0.6.0
|
|||
peerlibrary:reactive-mongo@0.1.1
|
||||
peerlibrary:server-autorun@0.5.2
|
||||
percolate:migrations@1.0.2
|
||||
practicalmeteor:chai@2.1.0_1
|
||||
practicalmeteor:mocha-core@1.0.1
|
||||
promise@0.10.0
|
||||
raix:eventemitter@0.1.3
|
||||
random@1.0.10
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
import { Mongo } from 'meteor/mongo';
|
||||
import SimpleSchema from 'simpl-schema';
|
||||
import firesCommonSchema from '../Common/FiresSchema';
|
||||
|
||||
const ActiveFires = new Mongo.Collection('activefires', { idGeneration: 'MONGO' });
|
||||
|
||||
|
|
@ -17,6 +18,13 @@ ActiveFires.deny({
|
|||
remove: () => true
|
||||
});
|
||||
|
||||
|
||||
ActiveFires.schema = new SimpleSchema(firesCommonSchema);
|
||||
|
||||
ActiveFires.attachSchema(ActiveFires.schema);
|
||||
|
||||
export default ActiveFires;
|
||||
|
||||
/* Sample:
|
||||
* {
|
||||
* "_id" : ObjectId("5a208d7579095a1adba48191"),
|
||||
|
|
@ -46,19 +54,3 @@ ActiveFires.deny({
|
|||
* "createdAt" : ISODate("2017-11-30T08:07:33.720Z")
|
||||
* }
|
||||
* */
|
||||
|
||||
ActiveFires.schema = new SimpleSchema({
|
||||
lat: Number,
|
||||
lon: Number,
|
||||
scan: Number,
|
||||
type: String,
|
||||
acq_date: String,
|
||||
acq_time: String,
|
||||
when: Date,
|
||||
createdAt: Date,
|
||||
updatedAt: Date
|
||||
});
|
||||
|
||||
ActiveFires.attachSchema(ActiveFires.schema);
|
||||
|
||||
export default ActiveFires;
|
||||
|
|
|
|||
29
imports/api/Common/FiresSchema.js
Normal file
29
imports/api/Common/FiresSchema.js
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
/* eslint-disable import/no-absolute-path */
|
||||
|
||||
import { defaultCreatedAt, defaultUpdateAt } from '/imports/api/Utility/Utils.js';
|
||||
import LocationSchema from '/imports/api/Utility/LocationSchema.js';
|
||||
|
||||
const firesCommonSchema = {
|
||||
ourid: LocationSchema,
|
||||
lat: Number,
|
||||
lon: Number,
|
||||
scan: Number,
|
||||
type: String,
|
||||
when: Date,
|
||||
track: { type: Number, optional: true },
|
||||
acq_date: { type: String, optional: true },
|
||||
acq_time: { type: String, optional: true },
|
||||
satellite: { type: String, optional: true },
|
||||
confidence: { type: Number, optional: true },
|
||||
version: { type: String, optional: true },
|
||||
frp: { type: Number, optional: true },
|
||||
daynight: { type: String, optional: true },
|
||||
brightness: { type: Number, optional: true },
|
||||
bright_t31: { type: Number, optional: true },
|
||||
bright_ti4: { type: Number, optional: true },
|
||||
bright_ti5: { type: Number, optional: true },
|
||||
createdAt: defaultCreatedAt,
|
||||
updatedAt: defaultUpdateAt
|
||||
};
|
||||
|
||||
export default firesCommonSchema;
|
||||
25
imports/api/Fires/Fires.js
Normal file
25
imports/api/Fires/Fires.js
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
/* eslint-disable consistent-return */
|
||||
|
||||
import { Mongo } from 'meteor/mongo';
|
||||
import SimpleSchema from 'simpl-schema';
|
||||
import firesCommonSchema from '../Common/FiresSchema';
|
||||
|
||||
const Fires = new Mongo.Collection('fires', { idGeneration: 'MONGO' });
|
||||
|
||||
Fires.allow({
|
||||
insert: () => false,
|
||||
update: () => false,
|
||||
remove: () => false
|
||||
});
|
||||
|
||||
Fires.deny({
|
||||
insert: () => true,
|
||||
update: () => true,
|
||||
remove: () => true
|
||||
});
|
||||
|
||||
Fires.schema = new SimpleSchema(firesCommonSchema);
|
||||
|
||||
Fires.attachSchema(Fires.schema);
|
||||
|
||||
export default Fires;
|
||||
22
imports/api/Fires/methods.js
Normal file
22
imports/api/Fires/methods.js
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
/* eslint-disable import/no-absolute-path */
|
||||
|
||||
import { Meteor } from 'meteor/meteor';
|
||||
import { check } from 'meteor/check';
|
||||
import rateLimit from '/imports/modules/rate-limit';
|
||||
import urlEnc from '/imports/modules/url-encode';
|
||||
|
||||
Meteor.methods({
|
||||
'fire.decrypt': async function fireDecode(fireEnc) {
|
||||
check(fireEnc, String);
|
||||
const unsealed = await urlEnc.decrypt(fireEnc);
|
||||
return unsealed;
|
||||
}
|
||||
});
|
||||
|
||||
rateLimit({
|
||||
methods: [
|
||||
'fire.decode'
|
||||
],
|
||||
limit: 5,
|
||||
timeRange: 1000
|
||||
});
|
||||
37
imports/api/Fires/server/publications.js
Normal file
37
imports/api/Fires/server/publications.js
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
/* eslint-disable import/no-absolute-path */
|
||||
/* eslint-disable prefer-arrow-callback */
|
||||
|
||||
import { Meteor } from 'meteor/meteor';
|
||||
import { check } from 'meteor/check';
|
||||
import urlEnc from '/imports/modules/url-encode';
|
||||
import { Promise } from 'meteor/promise';
|
||||
import FiresCollection from '../Fires';
|
||||
|
||||
function findFire(unsealed) {
|
||||
const fire = FiresCollection.find({ ourid: { type: 'Point', coordinates: [unsealed.lon, unsealed.lat] } });
|
||||
return fire;
|
||||
}
|
||||
|
||||
Meteor.publish('fireFromHash', function fireFromHash(fireEnc) {
|
||||
check(fireEnc, String);
|
||||
try {
|
||||
// console.log(fireEnc);
|
||||
const unsealed = Promise.await(urlEnc.decrypt(fireEnc));
|
||||
const w = unsealed.when;
|
||||
// console.log(w);
|
||||
unsealed.when = new Date(w);
|
||||
// console.log(unsealed);
|
||||
FiresCollection.schema.validate(unsealed);
|
||||
const fire = findFire(unsealed);
|
||||
if (fire.count() === 0) {
|
||||
const result = FiresCollection.upsert({ ourid: unsealed.ourid }, { $set: unsealed }, { multi: false, upsert: true });
|
||||
console.log(JSON.stringify(result));
|
||||
}
|
||||
return findFire(unsealed);
|
||||
/* console.log(`fires: ${fire.count()}`);
|
||||
* return fire; */
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
throw new Meteor.Error('500', e);
|
||||
}
|
||||
});
|
||||
39
imports/api/Notifications/Notifications.js
Normal file
39
imports/api/Notifications/Notifications.js
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
/* eslint-disable consistent-return */
|
||||
/* eslint-disable import/no-absolute-path */
|
||||
|
||||
import { Mongo } from 'meteor/mongo';
|
||||
import SimpleSchema from 'simpl-schema';
|
||||
import { defaultCreatedAt, defaultUpdateAt } from '/imports/api/Utility/Utils.js';
|
||||
import LocationSchema from '/imports/api/Utility/LocationSchema.js';
|
||||
|
||||
const Notifications = new Mongo.Collection('notifications', { idGeneration: 'MONGO' });
|
||||
|
||||
Notifications.allow({
|
||||
insert: () => false,
|
||||
update: () => false,
|
||||
remove: () => false
|
||||
});
|
||||
|
||||
Notifications.deny({
|
||||
insert: () => true,
|
||||
update: () => true,
|
||||
remove: () => true
|
||||
});
|
||||
|
||||
Notifications.schema = new SimpleSchema({
|
||||
userId: String,
|
||||
content: String,
|
||||
geo: LocationSchema,
|
||||
type: String,
|
||||
webNotified: { type: Boolean, optional: true },
|
||||
webNotifiedAt: { type: Date, optional: true },
|
||||
emailNotified: { type: Boolean, optional: true },
|
||||
emailNotifiedAt: { type: Date, optional: true },
|
||||
when: Date,
|
||||
createdAt: defaultCreatedAt,
|
||||
updatedAt: defaultUpdateAt
|
||||
});
|
||||
|
||||
Notifications.attachSchema(Notifications.schema);
|
||||
|
||||
export default Notifications;
|
||||
25
imports/api/Notifications/methods.js
Normal file
25
imports/api/Notifications/methods.js
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
import { Meteor } from 'meteor/meteor';
|
||||
import { check } from 'meteor/check';
|
||||
import Notifications from './Notifications';
|
||||
import rateLimit from '../../modules/rate-limit';
|
||||
|
||||
Meteor.methods({
|
||||
'notifications.sent': function notificationsUpdate(notifId) {
|
||||
check(notifId, Meteor.Collection.ObjectID);
|
||||
|
||||
try {
|
||||
Notifications.update(notifId, { $set: { webNotified: true, webNotifiedAt: new Date() } });
|
||||
return notifId;
|
||||
} catch (exception) {
|
||||
throw new Meteor.Error('500', exception);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
rateLimit({
|
||||
methods: [
|
||||
'notifications.sent'
|
||||
],
|
||||
limit: 5,
|
||||
timeRange: 1000
|
||||
});
|
||||
10
imports/api/Notifications/server/publications.js
Normal file
10
imports/api/Notifications/server/publications.js
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
/* eslint-disable prefer-arrow-callback */
|
||||
|
||||
import { Meteor } from 'meteor/meteor';
|
||||
import Notifications from '../Notifications';
|
||||
|
||||
Meteor.publish('mynotifications', function notifications() {
|
||||
const notif = Notifications.find({ userId: this.userId, type: 'web', webNotified: null });
|
||||
console.log(`Notifications for user ${this.userId}: ${notif.count()}`);
|
||||
return notif;
|
||||
});
|
||||
|
|
@ -4,6 +4,7 @@
|
|||
import { Mongo } from 'meteor/mongo';
|
||||
import SimpleSchema from 'simpl-schema';
|
||||
import { defaultCreatedAt, defaultUpdateAt } from '/imports/api/Utility/Utils.js';
|
||||
import LocationSchema from '/imports/api/Utility/LocationSchema.js';
|
||||
|
||||
const Subscriptions = new Mongo.Collection('subscriptions', { idGeneration: 'MONGO' });
|
||||
|
||||
|
|
@ -39,36 +40,6 @@ Subscriptions.deny({
|
|||
* }
|
||||
* */
|
||||
|
||||
// https://stackoverflow.com/questions/24492333/meteor-simple-schema-for-mongo-geo-location-data
|
||||
// https://github.com/aldeed/meteor-simple-schema/issues/606
|
||||
const LocationSchema = new SimpleSchema({
|
||||
type: {
|
||||
type: String,
|
||||
allowedValues: ['Point']
|
||||
},
|
||||
coordinates: {
|
||||
type: Array,
|
||||
minCount: 2,
|
||||
maxCount: 2,
|
||||
custom: function custom() {
|
||||
if (!(this.value[0] >= -90 && this.value[0] <= 90)) {
|
||||
return 'lngOutOfRange';
|
||||
}
|
||||
if (!(this.value[1] >= -180 && this.value[1] <= 180)) {
|
||||
return 'latOutOfRange';
|
||||
}
|
||||
return true;
|
||||
}
|
||||
},
|
||||
'coordinates.$': {
|
||||
type: Number
|
||||
}
|
||||
});
|
||||
|
||||
LocationSchema.messageBox.messages({
|
||||
lonOutOfRange: '[label] longitude should be between -90 and 90',
|
||||
latOutOfRange: '[label] latitude should be between -180 and 180'
|
||||
});
|
||||
|
||||
Subscriptions.schema = new SimpleSchema({
|
||||
location: Object,
|
||||
|
|
|
|||
|
|
@ -16,8 +16,8 @@ export default (options, user) => {
|
|||
templateVars: {
|
||||
applicationName,
|
||||
firstName,
|
||||
welcomeUrl: Meteor.absoluteUrl('documents'), // e.g., returns http://localhost:3000/documents
|
||||
},
|
||||
welcomeUrl: Meteor.absoluteUrl('subscriptions') // e.g., returns http://localhost:3000/documents
|
||||
}
|
||||
})
|
||||
.catch((error) => {
|
||||
throw new Meteor.Error('500', `${error}`);
|
||||
|
|
|
|||
34
imports/api/Utility/LocationSchema.js
Normal file
34
imports/api/Utility/LocationSchema.js
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
import SimpleSchema from 'simpl-schema';
|
||||
|
||||
// https://stackoverflow.com/questions/24492333/meteor-simple-schema-for-mongo-geo-location-data
|
||||
// https://github.com/aldeed/meteor-simple-schema/issues/606
|
||||
const LocationSchema = new SimpleSchema({
|
||||
type: {
|
||||
type: String,
|
||||
allowedValues: ['Point']
|
||||
},
|
||||
coordinates: {
|
||||
type: Array,
|
||||
minCount: 2,
|
||||
maxCount: 2,
|
||||
custom: function custom() {
|
||||
if (!(this.value[0] >= -90 && this.value[0] <= 90)) {
|
||||
return 'lngOutOfRange';
|
||||
}
|
||||
if (!(this.value[1] >= -180 && this.value[1] <= 180)) {
|
||||
return 'latOutOfRange';
|
||||
}
|
||||
return true;
|
||||
}
|
||||
},
|
||||
'coordinates.$': {
|
||||
type: Number
|
||||
}
|
||||
});
|
||||
|
||||
LocationSchema.messageBox.messages({
|
||||
lonOutOfRange: '[label] longitude should be between -90 and 90',
|
||||
latOutOfRange: '[label] latitude should be between -180 and 180'
|
||||
});
|
||||
|
||||
export default LocationSchema;
|
||||
|
|
@ -7,6 +7,8 @@ import templateToHTML from './handlebars-email-to-html';
|
|||
const sendEmail = (options, { resolve, reject }) => {
|
||||
try {
|
||||
Meteor.defer(() => {
|
||||
// TODO: replace with import sendMail from '/imports/startup/server/email';
|
||||
console.log(`Email options: ${options}`);
|
||||
Email.send(options);
|
||||
resolve();
|
||||
});
|
||||
|
|
@ -15,13 +17,15 @@ const sendEmail = (options, { resolve, reject }) => {
|
|||
}
|
||||
};
|
||||
|
||||
export default ({ text, html, template, templateVars, ...rest }) => {
|
||||
export default ({
|
||||
text, html, template, templateVars, ...rest
|
||||
}) => {
|
||||
if (text || html || template) {
|
||||
return new Promise((resolve, reject) => {
|
||||
sendEmail({
|
||||
...rest,
|
||||
text: template ? templateToText(getPrivateFile(`email-templates/${template}.txt`), (templateVars || {})) : text,
|
||||
html: template ? templateToHTML(getPrivateFile(`email-templates/${template}.html`), (templateVars || {})) : html,
|
||||
html: template ? templateToHTML(getPrivateFile(`email-templates/${template}.html`), (templateVars || {})) : html
|
||||
}, { resolve, reject });
|
||||
});
|
||||
}
|
||||
|
|
|
|||
6
imports/modules/url-encode.js
Normal file
6
imports/modules/url-encode.js
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
import { Meteor } from 'meteor/meteor';
|
||||
import Iron from 'iron';
|
||||
|
||||
exports.encrypt = obj => Iron.seal(obj, Meteor.settings.private.ironPassword, Iron.defaults);
|
||||
|
||||
exports.decrypt = obj => Iron.unseal(obj, Meteor.settings.private.ironPassword, Iron.defaults);
|
||||
|
|
@ -13,4 +13,11 @@ import '../../api/FireAlerts/server/publications';
|
|||
|
||||
import '../../api/Subscriptions/methods';
|
||||
import '../../api/Subscriptions/server/publications';
|
||||
|
||||
// TODO add rate-limit to these publications
|
||||
|
||||
import '../../api/Notifications/methods';
|
||||
import '../../api/Notifications/server/publications';
|
||||
|
||||
import '../../api/Fires/methods';
|
||||
import '../../api/Fires/server/publications';
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@ const db = Meteor.users.rawDatabase(); // new Mongo.Collection('__mailTimeQueue_
|
|||
// db.getCollection("__mailTimeQueue__").count()
|
||||
|
||||
// https://litmus.com/community/discussions/4633-is-there-a-reliable-1px-horizontal-rule-method
|
||||
const hr = `<table cellspacing="0" cellpadding="0" border="0" width="100%" style="width: 100% !important;">
|
||||
export const hr = `<table cellspacing="0" cellpadding="0" border="0" width="100%" style="width: 100% !important;">
|
||||
<tr>
|
||||
<td align="left" valign="top" width="600px" height="1" style="background-color: #f0f0f0; border-collapse:collapse; mso-table-lspace: 0pt; mso-table-rspace: 0pt; mso-line-height-rule: exactly; line-height: 1px;"><!--[if gte mso 15]> <![endif]--></td>
|
||||
</tr>
|
||||
|
|
@ -32,7 +32,11 @@ const MailQueue = new MailTime({
|
|||
from(transport) {
|
||||
// To pass spam-filters `from` field should be correctly set
|
||||
// for each transport, check `transport` object for more options
|
||||
return `"${i18n.t('AppName')}" <${transport.options.auth.user}>`;
|
||||
if (!Meteor.isProduction) {
|
||||
// only for test purposes
|
||||
return `${i18n.t('AppName')} <notify@example.org>`;
|
||||
}
|
||||
return `${i18n.t('AppName')} <${transport.options.auth.user}>`;
|
||||
},
|
||||
debug: true,
|
||||
concatEmails: true, // Concatenate emails to the same addressee
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import i18nOpts from '../common/i18n';
|
|||
// import moment from 'moment';
|
||||
// import { T9n } from 'meteor-accounts-t9n';
|
||||
|
||||
// vi ostrio:meteor-root
|
||||
i18nOpts.backend.loadPath = `${Meteor.absolutePath}/public${i18nOpts.backend.loadPath}`;
|
||||
i18nOpts.backend.addPath = `${Meteor.absolutePath}/public${i18nOpts.backend.addPath}`;
|
||||
|
||||
|
|
|
|||
|
|
@ -5,3 +5,4 @@ import './fixtures';
|
|||
import './email';
|
||||
import './IPGeocoder';
|
||||
import './migrations';
|
||||
import './notificationsObserver';
|
||||
|
|
|
|||
89
imports/startup/server/notificationsObserver.js
Normal file
89
imports/startup/server/notificationsObserver.js
Normal file
|
|
@ -0,0 +1,89 @@
|
|||
/* eslint-disable import/no-absolute-path */
|
||||
|
||||
import i18n from 'i18next';
|
||||
import { Meteor } from 'meteor/meteor';
|
||||
import moment from 'moment';
|
||||
import Notifications from '/imports/api/Notifications/Notifications';
|
||||
import sendMail from '/imports/startup/server/email';
|
||||
import { hr } from '/imports/startup/server/email';
|
||||
import getOAuthProfile from '/imports/modules/get-oauth-profile';
|
||||
import image from 'google-maps-image-api-url';
|
||||
import { trim } from '/imports/ui/components/NotificationsObserver/util.js';
|
||||
|
||||
Meteor.startup(() => {
|
||||
// https://stackoverflow.com/questions/1199352/smart-way-to-shorten-long-strings-with-javascript
|
||||
function truncate(n, useWordBoundary) {
|
||||
if (this.length <= n) {
|
||||
return this;
|
||||
}
|
||||
const subString = this.substr(0, n - 1);
|
||||
return `${useWordBoundary ?
|
||||
subString.substr(0, subString.lastIndexOf(' ')) :
|
||||
subString}...`;
|
||||
}
|
||||
|
||||
// https://www.npmjs.com/package/google-maps-image-api-url
|
||||
// https://stackoverflow.com/questions/24355007/is-there-no-way-to-embed-a-google-map-into-an-html-email
|
||||
// https://developers.google.com/maps/documentation/static-maps/intro
|
||||
|
||||
function imgUrl(lat, lng) {
|
||||
return image({
|
||||
key: Meteor.settings.gmaps.key,
|
||||
type: 'staticmap',
|
||||
center: `${lat},${lng}`,
|
||||
size: '640x480',
|
||||
zoom: 16,
|
||||
maptype: 'hybrid',
|
||||
language: 'es',
|
||||
markers: `icon: ${Meteor.settings.private.fireIconUrl}|${lat},${lng}`
|
||||
});
|
||||
}
|
||||
|
||||
function imgEl(lat, lng) {
|
||||
return `<img src="${imgUrl(lat, lng)}" width="640" height="480"/>`;
|
||||
}
|
||||
|
||||
function process(notif) {
|
||||
if (notif.type === 'web' && !notif.emailNotified) {
|
||||
const user = Meteor.users.findOne({ _id: notif.userId });
|
||||
const hasPassword = user.services && user.services.password && user.services.password.bcrypt;
|
||||
|
||||
// console.log(`Has password: ${hasPassword}`);
|
||||
const OAuthProfile = getOAuthProfile({
|
||||
password: hasPassword,
|
||||
profile: user.profile
|
||||
}, user);
|
||||
const firstName = OAuthProfile ? OAuthProfile.name.first : user.profile.name.first;
|
||||
const emailAddress = OAuthProfile ? OAuthProfile.email :
|
||||
user && user.emails[0] && user.emails[0].verified ? user.emails[0].address : null;
|
||||
if (emailAddress) {
|
||||
const img = imgEl(notif.geo.coordinates[1], notif.geo.coordinates[0]);
|
||||
const url = imgUrl(notif.geo.coordinates[1], notif.geo.coordinates[0]);
|
||||
// FIXME use our map as url and static map as img
|
||||
moment.locale(user.lang);
|
||||
const message = `${trim(notif.content)} (${i18n.t('fireDetectedAt', { when: moment(notif.when).format('LLL') })}).`;
|
||||
const emailOpts = {
|
||||
to: emailAddress,
|
||||
userName: firstName,
|
||||
sendAt: new Date(),
|
||||
subject: truncate.apply(message, [50, true]),
|
||||
text: `${message} ${url}`,
|
||||
template: '<body><h2>{{appName}}</h2>{{{html}}}</body>',
|
||||
appName: i18n.t('AppName'),
|
||||
html: `<p>${message}</p>${hr}<p>${img}</p>`
|
||||
};
|
||||
sendMail(emailOpts, true);
|
||||
Notifications.update(notif._id, { $set: { emailNotified: true, emailNotifiedAt: new Date() } });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Notifications.find().observe({
|
||||
added: function notifAdded(notif) {
|
||||
process(notif);
|
||||
},
|
||||
changed: function notifChanged(updatedNotif, oldNotif) {
|
||||
process(updatedNotif);
|
||||
}
|
||||
});
|
||||
});
|
||||
12
imports/ui/components/History/History.js
Normal file
12
imports/ui/components/History/History.js
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
import { Meteor } from 'meteor/meteor';
|
||||
import createHistory from 'history/createBrowserHistory';
|
||||
|
||||
const history = createHistory();
|
||||
|
||||
history.listen((location) => { // , action ) => {
|
||||
// console.log(location.pathname);
|
||||
// console.log(action); // PUSH, etc
|
||||
Meteor.Piwik.trackPage(location.pathname);
|
||||
});
|
||||
|
||||
export default history;
|
||||
|
|
@ -0,0 +1,70 @@
|
|||
/* eslint-disable import/no-absolute-path */
|
||||
import { Meteor } from 'meteor/meteor';
|
||||
import { Tracker } from 'meteor/tracker';
|
||||
import { Bert } from 'meteor/themeteorchef:bert';
|
||||
import moment from 'moment';
|
||||
import Notifications from '/imports/api/Notifications/Notifications';
|
||||
import Push from 'push.js/bin/push.min.js';
|
||||
import i18n from '/imports/startup/client/i18n';
|
||||
import history from '../../components/History/History';
|
||||
import { trim } from './util.js';
|
||||
|
||||
function process(notif) {
|
||||
// No already notified
|
||||
if (Push.Permission.has()) {
|
||||
if (!notif.webNotified) {
|
||||
Push.create(i18n.t('AppName'), {
|
||||
body: `${trim(notif.content)} (${i18n.t('fireDetected', { when: moment(notif.when).fromNow() })})`,
|
||||
icon: '/n-fire-marker.png',
|
||||
requireInteraction: true,
|
||||
onClick: function onClickFocus() {
|
||||
window.focus();
|
||||
this.close();
|
||||
history.push('/fires');
|
||||
}
|
||||
});
|
||||
|
||||
Meteor.call('notifications.sent', notif._id, (error) => {
|
||||
if (error) {
|
||||
Bert.alert(error.reason, 'danger');
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Observe for new notifications
|
||||
Notifications.find().observe({
|
||||
added: function notifAdded(notif) {
|
||||
process(notif);
|
||||
},
|
||||
changed: function notifChanged(updatedNotif, oldNotif) {
|
||||
process(updatedNotif);
|
||||
}
|
||||
});
|
||||
|
||||
Meteor.startup(() => {
|
||||
if (Meteor.userId()) {
|
||||
Meteor.subscribe('mynotifications');
|
||||
// Check for notifications not processed at startup
|
||||
Notifications.find({ webNotified: null }).forEach((notif) => {
|
||||
process(notif);
|
||||
});
|
||||
}
|
||||
|
||||
Tracker.autorun(() => {
|
||||
if (Meteor.userId()) {
|
||||
Meteor.subscribe('mynotifications');
|
||||
// console.log('Started notifications listener');
|
||||
if (!Push.Permission.has()) {
|
||||
Push.Permission.request(() => {
|
||||
// on granted
|
||||
Bert.alert(i18n.t('Perfecto, recibirás notificaciones de fuegos en este equipo y también por correo cuando no estés conectado'), 'success');
|
||||
}, () => {
|
||||
// on denied
|
||||
Bert.alert(i18n.t('No recibirás notificaciones de fuegos en este equipo, solo por correo'), 'warning');
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
4
imports/ui/components/NotificationsObserver/util.js
Normal file
4
imports/ui/components/NotificationsObserver/util.js
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
export const trim = function trimMess(message) {
|
||||
// Removing utf8 suffix and prefix characters used in telegram but not useful here in browser
|
||||
return message.replace(/^🔥 /, '').replace(/:$/, '');
|
||||
};
|
||||
|
|
@ -1,5 +1,7 @@
|
|||
import React from 'react';
|
||||
import { translate } from 'react-i18next';
|
||||
import { Meteor } from 'meteor/meteor';
|
||||
import { Tracker } from 'meteor/tracker';
|
||||
import Blaze from 'meteor/gadicc:blaze-react-component';
|
||||
|
||||
const Reconnect = props => (
|
||||
|
|
@ -7,3 +9,12 @@ const Reconnect = props => (
|
|||
);
|
||||
|
||||
export default translate([], { wait: true })(Reconnect);
|
||||
|
||||
if (!Meteor.isProduction) {
|
||||
// We clear the console on disconnect during development
|
||||
Tracker.autorun(() => {
|
||||
if (Meteor.status().status === 'waiting') {
|
||||
// console.clear();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,7 +4,6 @@
|
|||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { Router, Switch, Route } from 'react-router-dom';
|
||||
import createHistory from 'history/createBrowserHistory';
|
||||
import { Grid } from 'react-bootstrap';
|
||||
import { I18nextProvider } from 'react-i18next';
|
||||
import { Meteor } from 'meteor/meteor';
|
||||
|
|
@ -41,17 +40,13 @@ import Privacy from '../../pages/Privacy/Privacy';
|
|||
import License from '../../pages/License/License';
|
||||
import Credits from '../../pages/Credits/Credits';
|
||||
import ReSendEmail from '../../components/ReSendEmail/ReSendEmail';
|
||||
import history from '../../components/History/History';
|
||||
import '../../components/NotificationsObserver/NotificationsObserver';
|
||||
import FiresMap from '../../pages/FiresMap/FiresMap';
|
||||
import Fires from '../../pages/Fires/Fires';
|
||||
import Sandbox from '../../pages/Sandbox/Sandbox';
|
||||
import './App.scss';
|
||||
|
||||
const history = createHistory();
|
||||
history.listen((location) => { // , action ) => {
|
||||
// console.log(location.pathname);
|
||||
// console.log(action); // PUSH, etc
|
||||
Meteor.Piwik.trackPage(location.pathname);
|
||||
});
|
||||
|
||||
const App = props => (
|
||||
/* https://react.i18next.com/components/i18nextprovider.html */
|
||||
<I18nextProvider i18n={i18n}>
|
||||
|
|
@ -72,9 +67,9 @@ const App = props => (
|
|||
<Authenticated exact path="/subscriptions/new" component={NewSubscription} {...props} />
|
||||
<Authenticated exact path="/subscriptions/:_id" component={ViewSubscription} {...props} />
|
||||
<Authenticated exact path="/subscriptions/:_id/edit" component={EditSubscription} {...props} />
|
||||
|
||||
<Authenticated exact path="/profile" component={Profile} {...props} />
|
||||
<Route path="/fires" component={FiresMap} {...props} />
|
||||
<Route path="/fire/:id" component={Fires} {...props} />
|
||||
<Public path="/signup" component={Signup} {...props} />
|
||||
<Public path="/login" component={Login} {...props} />
|
||||
<Route path="/logout" component={Logout} {...props} />
|
||||
|
|
|
|||
116
imports/ui/pages/Fires/Fires.js
Normal file
116
imports/ui/pages/Fires/Fires.js
Normal file
|
|
@ -0,0 +1,116 @@
|
|||
/* eslint-disable import/no-absolute-path */
|
||||
/* eslint-disable react/jsx-indent-props */
|
||||
/* eslint-disable react/jsx-indent */
|
||||
|
||||
import React, { Fragment } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { withTracker } from 'meteor/react-meteor-data';
|
||||
import { translate } from 'react-i18next';
|
||||
import { Col, Row } from 'react-bootstrap';
|
||||
import { Meteor } from 'meteor/meteor';
|
||||
import { Map, CircleMarker, Circle } from 'react-leaflet';
|
||||
import DefMapLayers from '/imports/ui/components/Maps/DefMapLayers';
|
||||
import moment from 'moment';
|
||||
import FiresCollection from '/imports/api/Fires/Fires';
|
||||
|
||||
class Fire extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
};
|
||||
}
|
||||
|
||||
/* componentDidUpdate() {
|
||||
* const map = this.firemap.leafletElement;
|
||||
* map.invalidateSize();
|
||||
* } */
|
||||
|
||||
handleLeafletLoad(map) {
|
||||
console.log(map);
|
||||
if (map) {
|
||||
// map.leafletELement.invalidateSize();
|
||||
// map.invalidateSize();
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
const { loading, fire, t } = this.props;
|
||||
return (
|
||||
<div className="ViewFire">
|
||||
{!loading &&
|
||||
<Fragment>
|
||||
<h4 className="page-header">
|
||||
{t('Información sobre fuego detectado el día {{when}}', { when: moment(fire.when).format('LLLL') })}
|
||||
</h4>
|
||||
<Row>
|
||||
<Col xs={12} sm={6} md={6} lg={6} >
|
||||
{(fire.type === 'modis' || fire.type === 'virrs') &&
|
||||
<p>{t('Detectado por satélites de la NASA')}
|
||||
</p>
|
||||
}
|
||||
<h5>Comentarios</h5>
|
||||
<p>{t('Añade un comentario si tienes información adicional sobre este fuego (por ejemplo, si está aún activo cómo acceder a él, o si conoces el motivo por el que comenzó el fuego, o si quieres denunciar algún tipo de ilegalidad relacionada con el fuego)')}
|
||||
</p>
|
||||
</Col>
|
||||
<Col xs={12} sm={6} md={6} lg={6} >
|
||||
<Map
|
||||
ref={(map) => {
|
||||
this.fireMap = map;
|
||||
this.handleLeafletLoad(map);
|
||||
}}
|
||||
animate
|
||||
sleep={false}
|
||||
center={[fire.lat, fire.lon]}
|
||||
zoom={8}
|
||||
>
|
||||
<Fragment>
|
||||
<CircleMarker
|
||||
center={[fire.lat, fire.lon]}
|
||||
color="red"
|
||||
stroke={false}
|
||||
fillOpacity="1"
|
||||
fill
|
||||
radius={3}
|
||||
/>
|
||||
<Circle
|
||||
center={[fire.lat, fire.lon]}
|
||||
color="red"
|
||||
fillColor="red"
|
||||
fillOpacity={0.1}
|
||||
radius={fire.scan * 1000}
|
||||
/>
|
||||
</Fragment>
|
||||
<DefMapLayers />
|
||||
</Map>
|
||||
</Col>
|
||||
</Row>
|
||||
</Fragment>
|
||||
}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Fire.propTypes = {
|
||||
t: PropTypes.func.isRequired,
|
||||
loading: PropTypes.bool.isRequired,
|
||||
fire: PropTypes.object // .isRequired
|
||||
};
|
||||
|
||||
Fire.defaultProps = {
|
||||
};
|
||||
|
||||
// export default translate([], { wait: true })(withTracker((props) => {
|
||||
|
||||
const FireContainer = withTracker(({ match }) => {
|
||||
const fireEncrypt = match.params.id;
|
||||
const subscription = Meteor.subscribe('fireFromHash', fireEncrypt);
|
||||
// console.log(`Subs ready: ${subscription.ready()}, fire: ${JSON.stringify(FiresCollection.findOne())}`);
|
||||
return {
|
||||
loading: !subscription.ready(),
|
||||
fire: FiresCollection.findOne()
|
||||
};
|
||||
})(Fire);
|
||||
|
||||
// export default FireContainer;
|
||||
export default translate([], { wait: true })(FireContainer);
|
||||
|
|
@ -53,7 +53,7 @@ class ResetPassword extends React.Component {
|
|||
if (error) {
|
||||
Bert.alert(T9n.get(`error.accounts.${error.reason}`), 'danger');
|
||||
} else {
|
||||
history.push('/documents');
|
||||
history.push('/subscriptions');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -19,12 +19,12 @@ class VerifyEmail extends React.Component {
|
|||
Accounts.verifyEmail(match.params.token, (error) => {
|
||||
if (error) {
|
||||
Bert.alert(T9n.get(`error.accounts.${error.reason}`), 'danger');
|
||||
this.setState({ error: T9n.get(`error.accounts.${error.reason}`) + ". " + this.t("Por favor, inténtalo otra vez.")});
|
||||
this.setState({ error: `${T9n.get(`error.accounts.${error.reason}`)}. ${this.t('Por favor, inténtalo otra vez.')}` });
|
||||
// this.setState({ error: `${error.reason}. Please try again.` });
|
||||
} else {
|
||||
setTimeout(() => {
|
||||
Bert.alert(this.t("¡Listo, gracias!"), 'success');
|
||||
history.push('/documents');
|
||||
Bert.alert(this.t('¡Listo, gracias!'), 'success');
|
||||
history.push('/subscriptions');
|
||||
}, 1500);
|
||||
}
|
||||
});
|
||||
|
|
@ -41,7 +41,7 @@ class VerifyEmail extends React.Component {
|
|||
|
||||
VerifyEmail.propTypes = {
|
||||
match: PropTypes.object.isRequired,
|
||||
history: PropTypes.object.isRequired,
|
||||
history: PropTypes.object.isRequired
|
||||
};
|
||||
|
||||
export default translate([], { wait: true })(VerifyEmail);
|
||||
|
|
|
|||
43
package-lock.json
generated
43
package-lock.json
generated
|
|
@ -4741,6 +4741,11 @@
|
|||
"resolved": "https://registry.npmjs.org/google-maps/-/google-maps-3.2.1.tgz",
|
||||
"integrity": "sha1-wHWPKxdaSo0RDBq7H8TMOMEXbgY="
|
||||
},
|
||||
"google-maps-image-api-url": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/google-maps-image-api-url/-/google-maps-image-api-url-1.0.3.tgz",
|
||||
"integrity": "sha1-2ezc4OnvS+pIGZD05ZC0slg8jbo="
|
||||
},
|
||||
"graceful-fs": {
|
||||
"version": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.11.tgz",
|
||||
"integrity": "sha1-Dovf5NHduIVNZOBOp8AOKgJuVlg="
|
||||
|
|
@ -5175,6 +5180,39 @@
|
|||
"integrity": "sha1-EEqOSqym09jNFXqO+L+rLXo//bY=",
|
||||
"dev": true
|
||||
},
|
||||
"iron": {
|
||||
"version": "5.0.4",
|
||||
"resolved": "https://registry.npmjs.org/iron/-/iron-5.0.4.tgz",
|
||||
"integrity": "sha512-7iQ5/xFMIYaNt9g2oiNiWdhrOTdRUMFaWENUd0KghxwPUhrIH8DUY8FEyLNTTzf75jaII+jMexLdY/2HfV61RQ==",
|
||||
"requires": {
|
||||
"boom": "7.1.1",
|
||||
"cryptiles": "4.1.1",
|
||||
"hoek": "5.0.2"
|
||||
},
|
||||
"dependencies": {
|
||||
"boom": {
|
||||
"version": "7.1.1",
|
||||
"resolved": "https://registry.npmjs.org/boom/-/boom-7.1.1.tgz",
|
||||
"integrity": "sha512-qwEARHTliqgEQiVkzKkkbLt3q0vRPIW60VRZ8zRnbjsm7INkPe9NxfAYDDYLZOdhxyUHa1gIe639Cx7t6RH/4A==",
|
||||
"requires": {
|
||||
"hoek": "5.0.2"
|
||||
}
|
||||
},
|
||||
"cryptiles": {
|
||||
"version": "4.1.1",
|
||||
"resolved": "https://registry.npmjs.org/cryptiles/-/cryptiles-4.1.1.tgz",
|
||||
"integrity": "sha512-YuQUPbcOmaZsdvxJZ25DCA1W+lLIRoPJKBDKin+St1RCYEERSfoe1d25B1MvWNHN3e8SpFSVsqYvEUjp8J9H2w==",
|
||||
"requires": {
|
||||
"boom": "7.1.1"
|
||||
}
|
||||
},
|
||||
"hoek": {
|
||||
"version": "5.0.2",
|
||||
"resolved": "https://registry.npmjs.org/hoek/-/hoek-5.0.2.tgz",
|
||||
"integrity": "sha512-NA10UYP9ufCtY2qYGkZktcQXwVyYK4zK0gkaFSB96xhtlo6V8tKXdQgx8eHolQTRemaW0uLn8BhjhwqrOU+QLQ=="
|
||||
}
|
||||
}
|
||||
},
|
||||
"is-arrayish": {
|
||||
"version": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz",
|
||||
"integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=",
|
||||
|
|
@ -9607,6 +9645,11 @@
|
|||
"version": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz",
|
||||
"integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4="
|
||||
},
|
||||
"push.js": {
|
||||
"version": "1.0.5",
|
||||
"resolved": "https://registry.npmjs.org/push.js/-/push.js-1.0.5.tgz",
|
||||
"integrity": "sha512-RPnZ7tc6oTPWf2g4dCIxIRgaJUENjor9CbyDg/o0JlsYqLOM0AjwHOndT9woZkt+7KI1srB9J70d5i6xiPd/HQ=="
|
||||
},
|
||||
"qs": {
|
||||
"version": "6.5.1",
|
||||
"resolved": "https://registry.npmjs.org/qs/-/qs-6.5.1.tgz",
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@
|
|||
"crypto-random-hex": "^1.0.0",
|
||||
"fs": "0.0.1-security",
|
||||
"google-maps": "^3.2.1",
|
||||
"google-maps-image-api-url": "^1.0.3",
|
||||
"handlebars": "^4.0.11",
|
||||
"history": "^4.7.2",
|
||||
"html5-device-mockups": "^3.2.0",
|
||||
|
|
@ -26,6 +27,7 @@
|
|||
"i18next-xhr-backend": "^1.5.0",
|
||||
"immutability-helper": "^2.5.1",
|
||||
"indexof": "0.0.1",
|
||||
"iron": "^5.0.4",
|
||||
"jquery": "^2.2.4",
|
||||
"jquery-validation": "^1.17.0",
|
||||
"juice": "^4.2.2",
|
||||
|
|
@ -43,6 +45,7 @@
|
|||
"nodemailer": "^4.4.1",
|
||||
"popper.js": "^1.12.7",
|
||||
"prop-types": "^15.6.0",
|
||||
"push.js": "^1.0.5",
|
||||
"rc-slider": "^8.5.0",
|
||||
"rc-tooltip": "^3.7.0",
|
||||
"react": "^16.0.0",
|
||||
|
|
|
|||
|
|
@ -185,5 +185,9 @@
|
|||
"Iniciar sesión con Telegram":
|
||||
"Iniciar sesión con Telegram",
|
||||
"Idioma":
|
||||
"Idioma"
|
||||
"Idioma",
|
||||
"fireDetected":
|
||||
"fuego detectado {{when}}",
|
||||
"fireDetectedAt":
|
||||
"fuego detectado el {{when}}"
|
||||
}
|
||||
|
|
|
|||
62
test/encode.test.js
Normal file
62
test/encode.test.js
Normal file
|
|
@ -0,0 +1,62 @@
|
|||
/* eslint-env mocha */
|
||||
/* eslint-disable func-names, prefer-arrow-callback */
|
||||
|
||||
import { chai } from 'meteor/practicalmeteor:chai';
|
||||
import ActiveFiresCollection from '/imports/api/ActiveFires/ActiveFires';
|
||||
import urlEnc from '/imports/modules/url-encode';
|
||||
|
||||
describe('url encoding', () => {
|
||||
it('should encrypt and dcrypt basic objects', async () => {
|
||||
const obj = { lat: 2 };
|
||||
const sealed = await urlEnc.encrypt(obj);
|
||||
const unsealed = await urlEnc.decrypt(sealed);
|
||||
chai.expect(unsealed).to.deep.equal(obj);
|
||||
});
|
||||
|
||||
it('should encrypt and dcrypt objects with date', async () => {
|
||||
const obj = { lat: 40.234503, lon: -3.350386, when: (new Date()).toString() };
|
||||
const sealed = await urlEnc.encrypt(obj);
|
||||
const unsealed = await urlEnc.decrypt(sealed);
|
||||
chai.expect(unsealed).to.deep.equal(obj);
|
||||
});
|
||||
|
||||
it('should encrypt complete objects', async () => {
|
||||
const obj = {
|
||||
ourid: {
|
||||
type: 'Point',
|
||||
coordinates: [
|
||||
24.813,
|
||||
9.223
|
||||
]
|
||||
},
|
||||
type: 'modis',
|
||||
lat: 9.223,
|
||||
lon: 24.813,
|
||||
when: new Date('2017-12-13T00:00:00.000Z').toISOString(),
|
||||
scan: 1,
|
||||
track: 1,
|
||||
satellite: 'A',
|
||||
confidence: 60,
|
||||
version: '6.0NRT',
|
||||
frp: 6.5,
|
||||
daynight: null,
|
||||
brightness: 305.9,
|
||||
bright_t31: 292.6
|
||||
};
|
||||
|
||||
const sealed = await urlEnc.encrypt(obj);
|
||||
console.log(encodeURI(sealed));
|
||||
|
||||
const unsealed = await urlEnc.decrypt(sealed);
|
||||
chai.expect(unsealed).to.deep.equal(obj);
|
||||
chai.expect(sealed).to.equal(encodeURI(sealed));
|
||||
});
|
||||
|
||||
// This fails because Date is return as String (not as Date)
|
||||
/* it('should encrypt and dcrypt collection objects', async () => {
|
||||
const obj = ActiveFiresCollection.findOne();
|
||||
const sealed = await urlEnc.encrypt(obj);
|
||||
const unsealed = await urlEnc.decrypt(sealed);
|
||||
chai.expect(unsealed).to.deep.equal(obj);
|
||||
}); */
|
||||
});
|
||||
Loading…
Add table
Add a link
Reference in a new issue