Refactored print.js, bulk-print.ejs, details.ejs, email.ejs, print.ejs, voucher.ejs and size.js for object compatibility with the UniFi Integration API. Updated the unifi.js module to implement the UniFi Integration API. Added the UNIFI_TOKEN to variables.js. Added fetch.js util. Check for undefined state in notes.js. Added undici to the dependencies. server.js refactored for compatibility with the UniFi integration API. Fixed incorrect quote filter within server.js. Temporary fixed guest mapping to voucher_code instead of ids

This commit is contained in:
Glenn de Haan
2025-08-08 19:38:10 +02:00
parent 21f5be6a0a
commit 68dd918d31
15 changed files with 358 additions and 221 deletions

View File

@@ -171,7 +171,7 @@ module.exports = {
});
doc.font('Roboto-Regular')
.fontSize(10)
.text(vouchers[item].quota === 1 ? t('singleUse') : vouchers[item].quota === 0 ? t('multiUse') : t('multiUse'));
.text(!vouchers[item].authorizedGuestLimit ? t('multiUse') : vouchers[item].authorizedGuestLimit === 1 ? t('singleUse') : t('multiUse'));
doc.font('Roboto-Bold')
.fontSize(10)
@@ -180,9 +180,9 @@ module.exports = {
});
doc.font('Roboto-Regular')
.fontSize(10)
.text(time(vouchers[item].duration, language));
.text(time(vouchers[item].timeLimitMinutes, language));
if (vouchers[item].qos_usage_quota) {
if (vouchers[item].dataUsageLimitMBytes) {
doc.font('Roboto-Bold')
.fontSize(10)
.text(`${t('dataLimit')}: `, {
@@ -190,10 +190,10 @@ module.exports = {
});
doc.font('Roboto-Regular')
.fontSize(10)
.text(`${bytes(vouchers[item].qos_usage_quota, 2)}`);
.text(`${bytes(vouchers[item].dataUsageLimitMBytes, 2)}`);
}
if (vouchers[item].qos_rate_max_down) {
if (vouchers[item].rxRateLimitKbps) {
doc.font('Roboto-Bold')
.fontSize(10)
.text(`${t('downloadLimit')}: `, {
@@ -201,10 +201,10 @@ module.exports = {
});
doc.font('Roboto-Regular')
.fontSize(10)
.text(`${bytes(vouchers[item].qos_rate_max_down, 1, true)}`);
.text(`${bytes(vouchers[item].rxRateLimitKbps, 1, true)}`);
}
if (vouchers[item].qos_rate_max_up) {
if (vouchers[item].txRateLimitKbps) {
doc.font('Roboto-Bold')
.fontSize(10)
.text(`${t('uploadLimit')}: `, {
@@ -212,7 +212,7 @@ module.exports = {
});
doc.font('Roboto-Regular')
.fontSize(10)
.text(`${bytes(vouchers[item].qos_rate_max_up, 1, true)}`);
.text(`${bytes(vouchers[item].txRateLimitKbps, 1, true)}`);
}
}
@@ -303,40 +303,40 @@ module.exports = {
printer.invert(true);
printer.print(`${t('type')}:`);
printer.invert(false);
printer.print(voucher.quota === 1 ? ` ${t('singleUse')}` : voucher.quota === 0 ? ` ${t('multiUse')}` : ` ${t('multiUse')}`);
printer.print(!voucher.authorizedGuestLimit ? ` ${t('multiUse')}` : voucher.authorizedGuestLimit === 1 ? ` ${t('singleUse')}` : ` ${t('multiUse')}`);
printer.newLine();
printer.setTextDoubleHeight();
printer.invert(true);
printer.print(`${t('duration')}:`);
printer.invert(false);
printer.print(` ${time(voucher.duration, language)}`);
printer.print(` ${time(voucher.timeLimitMinutes, language)}`);
printer.newLine();
if(voucher.qos_usage_quota) {
if(voucher.dataUsageLimitMBytes) {
printer.setTextDoubleHeight();
printer.invert(true);
printer.print(`${t('dataLimit')}:`);
printer.invert(false);
printer.print(` ${bytes(voucher.qos_usage_quota, 2)}`);
printer.print(` ${bytes(voucher.dataUsageLimitMBytes, 2)}`);
printer.newLine();
}
if(voucher.qos_rate_max_down) {
if(voucher.rxRateLimitKbps) {
printer.setTextDoubleHeight();
printer.invert(true);
printer.print(`${t('downloadLimit')}:`);
printer.invert(false);
printer.print(` ${bytes(voucher.qos_rate_max_down, 1, true)}`);
printer.print(` ${bytes(voucher.rxRateLimitKbps, 1, true)}`);
printer.newLine();
}
if(voucher.qos_rate_max_up) {
if(voucher.txRateLimitKbps) {
printer.setTextDoubleHeight();
printer.invert(true);
printer.print(`${t('uploadLimit')}:`);
printer.invert(false);
printer.print(` ${bytes(voucher.qos_rate_max_up, 1, true)}`);
printer.print(` ${bytes(voucher.txRateLimitKbps, 1, true)}`);
printer.newLine();
}

View File

@@ -8,6 +8,7 @@ const unifi = require('node-unifi');
*/
const variables = require('./variables');
const log = require('./log');
const fetch = require('../utils/fetch');
/**
* UniFi Settings
@@ -68,7 +69,7 @@ const startSession = () => {
/**
* UniFi module functions
*
* @type {{create: (function(*, number=, null=, boolean=): Promise<*>), remove: (function(*, boolean=): Promise<*>), list: (function(boolean=): Promise<*>), guests: (function(boolean=): Promise<*>)}}
* @type {{create: (function(*, number=, string=): Promise<*>), remove: (function(*): Promise<*>), list: (function(): Promise<*>), guests: (function(boolean=): Promise<*>)}}
*/
const unifiModule = {
/**
@@ -77,57 +78,50 @@ const unifiModule = {
* @param type
* @param amount
* @param note
* @param retry
* @return {Promise<unknown>}
*/
create: (type, amount = 1, note = null, retry = true) => {
create: (type, amount = 1, note = '') => {
return new Promise((resolve, reject) => {
startSession().then(() => {
controller.createVouchers(type.expiration, amount, parseInt(type.usage), note, typeof type.upload !== "undefined" ? type.upload : null, typeof type.download !== "undefined" ? type.download : null, typeof type.megabytes !== "undefined" ? type.megabytes : null).then((voucher_data) => {
if(amount > 1) {
log.info(`[UniFi] Created ${amount} vouchers`);
resolve(true);
} else {
controller.getVouchers(voucher_data[0].create_time).then((voucher_data_complete) => {
const voucher = `${[voucher_data_complete[0].code.slice(0, 5), '-', voucher_data_complete[0].code.slice(5)].join('')}`;
log.info(`[UniFi] Created voucher with code: ${voucher}`);
resolve(voucher);
}).catch((e) => {
log.error('[UniFi] Error while getting voucher!');
log.debug(e);
reject('[UniFi] Error while getting voucher!');
});
}
}).catch((e) => {
log.error('[UniFi] Error while creating voucher!');
log.debug(e);
// Set base voucher data
const data = {
count: amount,
name: note,
timeLimitMinutes: type.expiration
};
// Check if token expired, if true attempt login then try again
if (e.response) {
if(e.response.status === 401 && retry) {
log.info('[UniFi] Attempting re-authentication & retry...');
// Set voucher limit usage if limited
if(parseInt(type.usage) !== 0) {
data.authorizedGuestLimit = parseInt(type.usage);
}
controller = null;
unifiModule.create(type, amount, note, false).then((e) => {
resolve(e);
}).catch((e) => {
reject(e);
});
} else {
// Something else went wrong lets clear the current controller so a user can retry
log.error(`[UniFi] Unexpected ${JSON.stringify({status: e.response.status, retry})} cleanup controller...`);
controller = null;
reject('[UniFi] Error while creating voucher!');
}
} else {
// Something else went wrong lets clear the current controller so a user can retry
log.error('[UniFi] Unexpected cleanup controller...');
controller = null;
reject('[UniFi] Error while creating voucher!');
}
});
// Set data usage limit if limited
if(typeof type.megabytes !== "undefined") {
data.dataUsageLimitMBytes = type.megabytes;
}
// Set download speed limit if limited
if(typeof type.download !== "undefined") {
data.rxRateLimitKbps = type.download;
}
// Set upload speed limit if limited
if(typeof type.upload !== "undefined") {
data.txRateLimitKbps = type.upload;
}
fetch(`/hotspot/vouchers`, 'POST', {}, data).then((response) => {
if(amount > 1) {
log.info(`[UniFi] Created ${amount} vouchers`);
resolve(true);
} else {
const voucherCode = `${[response.vouchers[0].code.slice(0, 5), '-', response.vouchers[0].code.slice(5)].join('')}`;
log.info(`[UniFi] Created voucher with code: ${voucherCode}`);
resolve(voucherCode);
}
}).catch((e) => {
reject(e);
log.error('[UniFi] Error while creating voucher!');
log.debug(e);
reject('[UniFi] Error while creating voucher!');
});
});
},
@@ -136,44 +130,17 @@ const unifiModule = {
* Removes a UniFi Voucher
*
* @param id
* @param retry
* @return {Promise<unknown>}
*/
remove: (id, retry = true) => {
remove: (id) => {
return new Promise((resolve, reject) => {
startSession().then(() => {
controller.revokeVoucher(id).then(() => {
resolve(true);
}).catch((e) => {
log.error('[UniFi] Error while removing voucher!');
log.debug(e);
// Check if token expired, if true attempt login then try again
if (e.response) {
if(e.response.status === 401 && retry) {
log.info('[UniFi] Attempting re-authentication & retry...');
controller = null;
unifiModule.remove(id, false).then((e) => {
resolve(e);
}).catch((e) => {
reject(e);
});
} else {
// Something else went wrong lets clear the current controller so a user can retry
log.error(`[UniFi] Unexpected ${JSON.stringify({status: e.response.status, retry})} cleanup controller...`);
controller = null;
reject('[UniFi] Error while removing voucher!');
}
} else {
// Something else went wrong lets clear the current controller so a user can retry
log.error('[UniFi] Unexpected cleanup controller...');
controller = null;
reject('[UniFi] Error while removing voucher!');
}
});
fetch(`/hotspot/vouchers/${id}`, 'DELETE').then(() => {
log.info(`[UniFi] Deleted voucher: ${id}`);
resolve(true);
}).catch((e) => {
reject(e);
log.error('[UniFi] Error while removing voucher!');
log.debug(e);
reject('[UniFi] Error while removing voucher!');
});
});
},
@@ -181,45 +148,22 @@ const unifiModule = {
/**
* Returns a list with all UniFi Vouchers
*
* @param retry
* @return {Promise<unknown>}
*/
list: (retry = true) => {
list: () => {
return new Promise((resolve, reject) => {
startSession().then(() => {
controller.getVouchers().then((vouchers) => {
log.info(`[UniFi] Found ${vouchers.length} voucher(s)`);
resolve(vouchers);
}).catch((e) => {
log.error('[UniFi] Error while getting vouchers!');
log.debug(e);
// Check if token expired, if true attempt login then try again
if (e.response) {
if(e.response.status === 401 && retry) {
log.info('[UniFi] Attempting re-authentication & retry...');
controller = null;
unifiModule.list(false).then((e) => {
resolve(e);
}).catch((e) => {
reject(e);
});
} else {
// Something else went wrong lets clear the current controller so a user can retry
log.error(`[UniFi] Unexpected ${JSON.stringify({status: e.response.status, retry})} cleanup controller...`);
controller = null;
reject('[UniFi] Error while getting vouchers!');
}
} else {
// Something else went wrong lets clear the current controller so a user can retry
log.error('[UniFi] Unexpected cleanup controller...');
controller = null;
reject('[UniFi] Error while getting vouchers!');
}
});
fetch('/hotspot/vouchers', 'GET', {
limit: 10000
}).then((vouchers) => {
log.info(`[UniFi] Found ${vouchers.length} voucher(s)`);
resolve(vouchers.sort((a, b) => {
if (a.createdAt > b.createdAt) return -1;
if (a.createdAt < b.createdAt) return 1;
}));
}).catch((e) => {
reject(e);
log.error('[UniFi] Error while getting vouchers!');
log.debug(e);
reject('[UniFi] Error while getting vouchers!');
});
});
},
@@ -232,6 +176,17 @@ const unifiModule = {
*/
guests: (retry = true) => {
return new Promise((resolve, reject) => {
// fetch('/clients', 'GET', {
// filter: 'access.type.eq(\'GUEST\')',
// limit: 10000
// }).then((clients) => {
// console.log(clients)
// }).catch((e) => {
// log.error('[UniFi] Error while getting vouchers!');
// log.debug(e);
// reject('[UniFi] Error while getting vouchers!');
// });
startSession().then(() => {
controller.getGuests().then((guests) => {
log.info(`[UniFi] Found ${guests.length} guest(s)`);

View File

@@ -16,6 +16,7 @@ module.exports = {
unifiPort: config('unifi_port') || process.env.UNIFI_PORT || 443,
unifiUsername: config('unifi_username') || process.env.UNIFI_USERNAME || 'admin',
unifiPassword: config('unifi_password') || process.env.UNIFI_PASSWORD || 'password',
unifiToken: config('unifi_token') || process.env.UNIFI_TOKEN || '',
unifiSiteId: config('unifi_site_id') || process.env.UNIFI_SITE_ID || 'default',
unifiSsid: config('unifi_ssid') || process.env.UNIFI_SSID || '',
unifiSsidPassword: config('unifi_ssid_password') || process.env.UNIFI_SSID_PASSWORD || '',