@ztimson/momentum 0.52.0 → 0.52.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.mjs CHANGED
@@ -285,7 +285,7 @@ class Cache {
285
285
  }
286
286
  }
287
287
  function blackOrWhite(background) {
288
- const exploded = background.match(background.length >= 6 ? /\w\w/g : /\w/g);
288
+ const exploded = background == null ? void 0 : background.match(background.length >= 6 ? /\w\w/g : /\w/g);
289
289
  if (!exploded) return "black";
290
290
  const [r2, g, b2] = exploded.map((hex) => parseInt(hex, 16));
291
291
  const luminance = (0.299 * r2 + 0.587 * g + 0.114 * b2) / 255;
@@ -663,31 +663,13 @@ __publicField2(_Http, "interceptors", {});
663
663
  __publicField2(_Http, "headers", {});
664
664
  let Http = _Http;
665
665
  const CliEffects = {
666
- CLEAR: "\x1B[0m",
667
- BRIGHT: "\x1B[1m",
668
- DIM: "\x1B[2m",
669
- UNDERSCORE: "\x1B[4m",
670
- BLINK: "\x1B[5m",
671
- REVERSE: "\x1B[7m",
672
- HIDDEN: "\x1B[8m"
666
+ CLEAR: "\x1B[0m"
673
667
  };
674
668
  const CliForeground = {
675
- BLACK: "\x1B[30m",
676
669
  RED: "\x1B[31m",
677
- GREEN: "\x1B[32m",
678
670
  YELLOW: "\x1B[33m",
679
671
  BLUE: "\x1B[34m",
680
- MAGENTA: "\x1B[35m",
681
- CYAN: "\x1B[36m",
682
- LIGHT_GREY: "\x1B[37m",
683
- GREY: "\x1B[90m",
684
- LIGHT_RED: "\x1B[91m",
685
- LIGHT_GREEN: "\x1B[92m",
686
- LIGHT_YELLOW: "\x1B[93m",
687
- LIGHT_BLUE: "\x1B[94m",
688
- LIGHT_MAGENTA: "\x1B[95m",
689
- LIGHT_CYAN: "\x1B[96m",
690
- WHITE: "\x1B[97m"
672
+ LIGHT_GREY: "\x1B[37m"
691
673
  };
692
674
  var LOG_LEVEL = /* @__PURE__ */ ((LOG_LEVEL2) => {
693
675
  LOG_LEVEL2[LOG_LEVEL2["ERROR"] = 0] = "ERROR";
@@ -986,6 +968,7 @@ class Api extends Http {
986
968
  if (token) this.token = token;
987
969
  }
988
970
  }
971
+ /** Current API token */
989
972
  get token() {
990
973
  return this._token;
991
974
  }
@@ -999,12 +982,21 @@ class Api extends Http {
999
982
  }
1000
983
  this.emit(PES`api/token:${token ? "u" : "d"}`, token);
1001
984
  }
985
+ /**
986
+ * Fetch current Momentum status
987
+ * @return {Promise<Health>}
988
+ */
1002
989
  healthcheck() {
1003
990
  return this.request({ url: "/api/healthcheck" }).then((resp) => {
1004
991
  this.emit(PES`api/healthcheck:r`, resp);
1005
992
  return resp;
1006
993
  });
1007
994
  }
995
+ /**
996
+ * Create API request
997
+ * @param {HttpRequestOptions} options Request options
998
+ * @return {Promise} Response
999
+ */
1008
1000
  request(options) {
1009
1001
  const key = JSONSanitize(options);
1010
1002
  const method = options.method == "GET" ? "r" : options.method == "POST" ? "c" : options.method == "DELETE" ? "d" : "u";
@@ -1038,6 +1030,11 @@ class Actions extends PathEventEmitter {
1038
1030
  __publicField(this, "cache", new Cache("_id"));
1039
1031
  this.api = typeof api == "string" ? new Api(api) : api;
1040
1032
  }
1033
+ /**
1034
+ * All saved actions
1035
+ * @param {boolean} reload Will use cached response if false
1036
+ * @return {Promise<Action[]>} List of saved actions
1037
+ */
1041
1038
  async all(reload) {
1042
1039
  if (!reload && this.cache.complete) return this.cache.all();
1043
1040
  return this.api.request({ url: `/api/` + PES`actions` }).then((resp) => {
@@ -1046,6 +1043,11 @@ class Actions extends PathEventEmitter {
1046
1043
  return resp;
1047
1044
  });
1048
1045
  }
1046
+ /**
1047
+ * Delete an existing action
1048
+ * @param {string} id Action ID
1049
+ * @return {Promise<void>} Delete complete
1050
+ */
1049
1051
  delete(id) {
1050
1052
  if (!id) throw new Error("Cannot delete action, missing ID");
1051
1053
  return this.api.request({ url: `/api/` + PES`actions/${id}`, method: "DELETE" }).then(() => {
@@ -1053,7 +1055,13 @@ class Actions extends PathEventEmitter {
1053
1055
  this.emit(PES`actions/${id}:d`, id);
1054
1056
  });
1055
1057
  }
1056
- read(id, reload = false) {
1058
+ /**
1059
+ * Fetch action info
1060
+ * @param {string} id Action ID
1061
+ * @param {boolean} reload Will use cached response if false
1062
+ * @return {Promise<Action | null>} Requested action
1063
+ */
1064
+ read(id, reload) {
1057
1065
  if (!id) throw new Error("Cannot read action, missing ID");
1058
1066
  const cached = this.cache.get(id);
1059
1067
  if (!reload && cached) return Promise.resolve(cached);
@@ -1063,15 +1071,20 @@ class Actions extends PathEventEmitter {
1063
1071
  return action;
1064
1072
  });
1065
1073
  }
1066
- run(path, opts = {}) {
1067
- if (!path) throw new Error("Cannot run action, missing path");
1068
- return this.api.request({ url: `/api/` + PES`actions/run/${path}`, ...opts });
1069
- }
1070
- runById(action, opts = {}) {
1071
- const id = typeof action == "string" ? action : action == null ? void 0 : action._id;
1072
- if (!id) throw new Error("Cannot run action, missing ID");
1074
+ /**
1075
+ * Manually trigger an actions execution
1076
+ * @param {string} id Action ID
1077
+ * @param {HttpRequestOptions} opts Additional arguments
1078
+ * @return {Promise<ActionResult>} Result of action
1079
+ */
1080
+ runById(id, opts = {}) {
1073
1081
  return this.api.request({ url: "/api/" + PES`actions/run-by-id/${id}`, method: "POST", ...opts });
1074
1082
  }
1083
+ /**
1084
+ * Update an action
1085
+ * @param {Action} action The new action
1086
+ * @return {Promise<Action>} Saved action
1087
+ */
1075
1088
  update(action) {
1076
1089
  return this.api.request({
1077
1090
  url: `/api/` + PES`actions/${action._id}`,
@@ -1090,6 +1103,12 @@ class Ai extends PathEventEmitter {
1090
1103
  __publicField(this, "api");
1091
1104
  this.api = typeof api == "string" ? new Api(api) : api;
1092
1105
  }
1106
+ /**
1107
+ * Ask the AI assistant a question
1108
+ * @param {string} question Users question
1109
+ * @param {any} context Additional data to aid response
1110
+ * @return {Promise<string>} AI's response
1111
+ */
1093
1112
  ask(question, context) {
1094
1113
  if (!question) throw new Error("Cannot ask AI, missing question");
1095
1114
  return this.api.request({ url: `/api/` + PES`ai`, method: "POST", body: {
@@ -1100,9 +1119,17 @@ class Ai extends PathEventEmitter {
1100
1119
  return response;
1101
1120
  });
1102
1121
  }
1122
+ /**
1123
+ * Clear AI assistant memory & context
1124
+ * @return {Promise<void>} Resolves once complete
1125
+ */
1103
1126
  clear() {
1104
1127
  return this.api.request({ url: "/api/" + PES`ai`, method: "DELETE" }).then(() => this.emit(PES`ai:d`, this.api.token));
1105
1128
  }
1129
+ /**
1130
+ * Current chat history
1131
+ * @return {Promise<{role: string, content: string}[]>}
1132
+ */
1106
1133
  history() {
1107
1134
  return this.api.request({ url: "/api/" + PES`ai` }).then((resp) => {
1108
1135
  this.emit(PES`ai:r`, resp);
@@ -1116,6 +1143,11 @@ class Analytics extends PathEventEmitter {
1116
1143
  __publicField(this, "api");
1117
1144
  this.api = typeof api == "string" ? new Api(api) : api;
1118
1145
  }
1146
+ /**
1147
+ * Perform IP trace
1148
+ * @param {string} ip IP address to trace
1149
+ * @return {Promise<IpTrace>} Trace results
1150
+ */
1119
1151
  ipTrace(ip) {
1120
1152
  if (!ip) throw new Error("Cannot trace, missing IP");
1121
1153
  return this.api.request({ url: `/api/` + PES`analytics/trace/${ip}` }).then((resp) => {
@@ -1129,33 +1161,70 @@ class Token extends PathEventEmitter {
1129
1161
  super();
1130
1162
  this.api = api;
1131
1163
  }
1164
+ /**
1165
+ * Fetch all tokens for user
1166
+ * @param {string} username User to search
1167
+ * @return {Promise<UserToken[]>} List of tokens
1168
+ */
1132
1169
  all(username) {
1133
1170
  return this.api.request({ url: "/" + PES`api/auth/tokens/${username}` }).then((resp) => {
1134
1171
  this.emit(PES`token/${username}:r`, resp);
1135
1172
  return resp;
1136
1173
  });
1137
1174
  }
1175
+ /**
1176
+ * Create a new user token
1177
+ * @param {{name: string, owner: string, expire: null | Date}} token Token settings
1178
+ * @return {Promise<UserToken>} Crated token
1179
+ */
1138
1180
  create(token) {
1139
1181
  return this.api.request({ url: "/" + PES`api/auth/tokens/${token.owner}`, body: token }).then((resp) => {
1140
1182
  this.emit(PES`token/${token.owner}:c`, token);
1141
1183
  return resp;
1142
1184
  });
1143
1185
  }
1186
+ /**
1187
+ * Delete an existing user token
1188
+ * @param {string} id Token ID
1189
+ * @return {Promise<void>} Resolves once complete
1190
+ */
1144
1191
  delete(id) {
1145
1192
  return this.api.request({ url: "/" + PES`api/auth/tokens/${id}`, method: "DELETE" }).then(() => this.emit(PES`token/${id}:d`, id));
1146
1193
  }
1147
1194
  }
1148
1195
  class Totp {
1149
1196
  constructor(api) {
1197
+ /**
1198
+ * Enable 2FA for user
1199
+ * @param {string} username User to reset
1200
+ * @return {Promise<void>} Resolves once complete
1201
+ */
1150
1202
  __publicField(this, "enable", this.reset);
1151
1203
  this.api = api;
1152
1204
  }
1205
+ /**
1206
+ * Disable 2FA for user
1207
+ * @param {string} username User to disable 2FA for
1208
+ * @return {Promise<void>} Resolves once complete
1209
+ */
1153
1210
  disable(username) {
1154
1211
  return this.api.request({ url: "/" + PES`api/auth/totp/${username}`, method: "DELETE" });
1155
1212
  }
1213
+ /**
1214
+ * Reset users 2FA
1215
+ * @param {string} username User to reset
1216
+ * @return {Promise<void>} Resolves once complete
1217
+ */
1156
1218
  reset(username) {
1157
1219
  return this.api.request({ url: "/" + PES`api/auth/totp/${username}`, method: "POST" });
1158
1220
  }
1221
+ /**
1222
+ * Setup 2FA authentication method
1223
+ * @param {string} username User to setup
1224
+ * @param {string} method Authenticator type
1225
+ * @param {string | null} totp null to being process, 2FA code to validate method
1226
+ * @return {Promise<void>} Resolves once complete
1227
+ */
1159
1228
  setup(username, method = "app", totp) {
1160
1229
  return this.api.request({ url: "/" + PES`api/auth/totp/${username}`, body: clean({
1161
1230
  method,
@@ -1167,7 +1236,9 @@ class Auth extends PathEventEmitter {
1167
1236
  constructor(api, opts = {}) {
1168
1237
  super();
1169
1238
  __publicField(this, "api");
1239
+ /** Manage user tokens */
1170
1240
  __publicField(this, "token");
1241
+ /** Manage user 2FA */
1171
1242
  __publicField(this, "totp");
1172
1243
  __publicField(this, "_permissions", []);
1173
1244
  __publicField(this, "_user");
@@ -1204,6 +1275,7 @@ class Auth extends PathEventEmitter {
1204
1275
  else this.user = null;
1205
1276
  });
1206
1277
  }
1278
+ /** Get current user permissions */
1207
1279
  get permissions() {
1208
1280
  return this._permissions;
1209
1281
  }
@@ -1211,19 +1283,33 @@ class Auth extends PathEventEmitter {
1211
1283
  this._permissions = perms;
1212
1284
  this.emit(PES`auth/permissions:u`, this._permissions);
1213
1285
  }
1286
+ /** Get current user, undefined if not yet initialized */
1214
1287
  get user() {
1215
1288
  return this._user;
1216
1289
  }
1290
+ /** Update user info without changing the session */
1217
1291
  set user(user) {
1218
1292
  if (!isEqual(this.user, user)) {
1219
1293
  this._user = user ? user : null;
1220
1294
  this.emit(PES`auth/user:u`, this._user);
1221
1295
  }
1222
1296
  }
1297
+ /**
1298
+ * Check if origin is recognized & whitelisted
1299
+ * @param {string} host Origin to check
1300
+ * @return {Promise<void>} Resolves in known, 401 code otherwise
1301
+ */
1223
1302
  knownHost(host = location.origin) {
1224
1303
  if (host.startsWith("/")) return Promise.resolve();
1225
1304
  return this.api.request({ url: `/api/auth/known-host?host=${encodeURI(new URL(host).origin)}` });
1226
1305
  }
1306
+ /**
1307
+ * Login a user & return the account
1308
+ * @param {string} username username
1309
+ * @param {string} password user's password
1310
+ * @param {{totp: string, expire: null | number | Date}} opts 2FA code and expiry options (null to never expire)
1311
+ * @return {Promise<User | null>} User account on success
1312
+ */
1227
1313
  login(username, password, opts) {
1228
1314
  if (!username || !password) throw new Error("Cannot login, missing username or password");
1229
1315
  return this.api.request({
@@ -1242,6 +1328,11 @@ class Auth extends PathEventEmitter {
1242
1328
  return user;
1243
1329
  });
1244
1330
  }
1331
+ /**
1332
+ * Login via Momentum single sign on
1333
+ * @param {string} host Host origin attempting to login
1334
+ * @return {Promise<string>} Token on success
1335
+ */
1245
1336
  loginRedirect(host = location.origin) {
1246
1337
  return new Promise((res, rej) => {
1247
1338
  var _a;
@@ -1251,10 +1342,10 @@ class Auth extends PathEventEmitter {
1251
1342
  if (event.origin != origin || data.sender != origin) return;
1252
1343
  if (!data.token) return rej("Unknown response from login");
1253
1344
  done = true;
1254
- this.api.token = event.data.token;
1345
+ this.api.token = data.token;
1255
1346
  window.removeEventListener("message", listener);
1256
- res(event.data.token);
1257
1347
  win.close();
1348
+ res(data.token);
1258
1349
  });
1259
1350
  win = window.open(encodeURI(`${(_a = this.opts) == null ? void 0 : _a.loginUrl}?redirect=postmessage&host=${host}`), "_blank");
1260
1351
  if (!win) {
@@ -1266,18 +1357,26 @@ class Auth extends PathEventEmitter {
1266
1357
  });
1267
1358
  });
1268
1359
  }
1360
+ /**
1361
+ * Logout current user
1362
+ */
1269
1363
  logout() {
1270
1364
  this.emit(PES`auth/logout:d`, this.user);
1271
1365
  this.api.token = null;
1272
1366
  this.user = null;
1273
1367
  }
1274
- async register(u) {
1368
+ /**
1369
+ * Create a new user with login
1370
+ * @param {Partial<User> & {password: string}} user User data with password
1371
+ * @return {Promise<User>} Registered user data
1372
+ */
1373
+ async register(user) {
1275
1374
  var _a;
1276
- if (!u.username || !u.password) throw new Error("Cannot register user, missing username or password");
1277
- const user = await this.api.request({ url: "/api/auth/register", body: { ...u } });
1278
- if ((_a = user == null ? void 0 : user.image) == null ? void 0 : _a.startsWith("/")) user.image = `${this.api.url}${user.image}?token=${this.api.token}`;
1279
- this.emit(PES`auth/register:c`, user);
1280
- return user;
1375
+ if (!user.username || !user.password) throw new Error("Cannot register user, missing username or password");
1376
+ const u = await this.api.request({ url: "/api/auth/register", body: { ...user } });
1377
+ if ((_a = u == null ? void 0 : u.image) == null ? void 0 : _a.startsWith("/")) u.image = `${this.api.url}${u.image}?token=${this.api.token}`;
1378
+ this.emit(PES`auth/register:c`, u);
1379
+ return u;
1281
1380
  }
1282
1381
  reset(emailOrPass, token) {
1283
1382
  if (!emailOrPass) throw new Error("Cannot reset password, missing email or token");
@@ -1292,6 +1391,12 @@ class Auth extends PathEventEmitter {
1292
1391
  this.emit(PES`auth/reset:${token ? "u" : "c"}`, token || emailOrPass);
1293
1392
  });
1294
1393
  }
1394
+ /**
1395
+ * Get session information
1396
+ * @param {string} token Token to fetch session info for
1397
+ * @param {boolean} set Set as current active session
1398
+ * @return {Promise<{token: string, user: User, permissions: string[], custom: any} | null>} Session information
1399
+ */
1295
1400
  async session(token, set = false) {
1296
1401
  if (!token) token = this.api.token;
1297
1402
  const session = await this.api.request({
@@ -1308,6 +1413,13 @@ class Auth extends PathEventEmitter {
1308
1413
  }
1309
1414
  return session;
1310
1415
  }
1416
+ /**
1417
+ * Update password for user
1418
+ * @param {string} username User to reset
1419
+ * @param {string} password New user password
1420
+ * @param {string} oldPassword Old password for validation
1421
+ * @return {Promise<void>} Resolves once complete
1422
+ */
1311
1423
  async updatePassword(username, password, oldPassword) {
1312
1424
  if (!username || !password) throw new Error("Cannot update password, missing username or password");
1313
1425
  return this.api.request({
@@ -1329,9 +1441,11 @@ class Client extends PathEventEmitter {
1329
1441
  this.settings = settings;
1330
1442
  this.pushSubscription.then((resp) => this.notifications = !!resp);
1331
1443
  }
1444
+ /** Check if mobile device */
1332
1445
  get mobile() {
1333
1446
  return ["android", "ios"].includes(this.platform);
1334
1447
  }
1448
+ /** Are notifications enabled */
1335
1449
  get notifications() {
1336
1450
  return this._notifications;
1337
1451
  }
@@ -1339,6 +1453,7 @@ class Client extends PathEventEmitter {
1339
1453
  this._notifications = enabled;
1340
1454
  this.emit(PES`client/notifications:${enabled ? "c" : "d"}`, enabled);
1341
1455
  }
1456
+ /** Get the current platform type */
1342
1457
  get platform() {
1343
1458
  if (!this._platform) {
1344
1459
  const userAgent = navigator.userAgent || navigator.vendor;
@@ -1351,6 +1466,7 @@ class Client extends PathEventEmitter {
1351
1466
  }
1352
1467
  return this._platform;
1353
1468
  }
1469
+ /** Get Push Subscription info */
1354
1470
  get pushSubscription() {
1355
1471
  var _a;
1356
1472
  if (!((_a = navigator.serviceWorker) == null ? void 0 : _a.controller)) return Promise.resolve(null);
@@ -1359,115 +1475,77 @@ class Client extends PathEventEmitter {
1359
1475
  return (_a2 = sw == null ? void 0 : sw.pushManager) == null ? void 0 : _a2.getSubscription();
1360
1476
  });
1361
1477
  }
1478
+ /** Check if running as a PWA */
1362
1479
  get pwa() {
1363
1480
  if (this._pwa == null)
1364
1481
  this._pwa = matchMedia("(display-mode: standalone)").matches || (navigator == null ? void 0 : navigator.standalone) || document.referrer.includes("android-app://");
1365
1482
  return this._pwa;
1366
1483
  }
1367
- async inject(reload = false, firstLoad = true) {
1368
- var _a, _b, _c, _d, _e, _f;
1484
+ /**
1485
+ * Inject the client theme settings, meta tags & PWA prompt
1486
+ * @param {boolean} reload Reload settings
1487
+ * @return {Promise<void>} Resolves on completion
1488
+ */
1489
+ async inject(reload) {
1490
+ var _a, _b, _c, _d, _e, _f, _g;
1491
+ const meta = (key, name, value) => {
1492
+ const exists = document.querySelector(`meta[${key}="${name}"]`);
1493
+ if (value === void 0 || exists) return exists;
1494
+ const meta2 = document.createElement("meta");
1495
+ meta2.setAttribute(key, name);
1496
+ meta2.content = value;
1497
+ document.head.append(meta2);
1498
+ };
1369
1499
  let settings = this.settings.cache;
1370
- if (firstLoad) this.settings.all(false, reload).then(() => this.inject(false, false)).catch(() => {
1371
- });
1372
- if (settings["title"] && !document.head.querySelector('meta[property="og:title"]')) {
1373
- const meta = document.createElement("meta");
1374
- meta.setAttribute("property", "og:title");
1375
- meta.content = settings["title"];
1376
- document.head.append(meta);
1377
- }
1378
- if (!document.querySelector('meta[property="og:type"]')) {
1379
- const meta = document.createElement("meta");
1380
- meta.setAttribute("property", "og:type");
1381
- meta.content = "article";
1382
- document.head.append(meta);
1383
- }
1384
- if (settings["public_url"] && !document.querySelector('meta[property="og:url"]')) {
1385
- const meta = document.createElement("meta");
1386
- meta.setAttribute("property", "og:url");
1387
- meta.content = settings["public_url"];
1388
- document.head.append(meta);
1389
- }
1390
- if (settings["description"]) {
1391
- if (!document.querySelector('meta[property="og:description"]')) {
1392
- const meta = document.createElement("meta");
1393
- meta.setAttribute("property", "og:description");
1394
- meta.content = settings["description"];
1395
- document.head.append(meta);
1396
- }
1397
- if (!document.querySelector('meta[property="description"]')) {
1398
- const meta = document.createElement("meta");
1399
- meta.setAttribute("property", "description");
1400
- meta.content = settings["description"];
1401
- document.head.append(meta);
1402
- }
1403
- if (!document.querySelector('meta[property="twitter:card"]')) {
1404
- const meta = document.createElement("meta");
1405
- meta.setAttribute("property", "twitter:card");
1406
- meta.content = settings["description"];
1407
- document.head.append(meta);
1408
- }
1409
- }
1410
- if (settings["public_url"] && !document.querySelector('meta[property="og:image"]')) {
1411
- const meta = document.createElement("meta");
1412
- meta.setAttribute("property", "og:image");
1413
- meta.content = settings["public_url"] + "/favicon.ico";
1414
- document.head.append(meta);
1415
- }
1416
- if (!document.querySelector('meta[name="mobile-web-app-capable"]')) {
1417
- const meta = document.createElement("meta");
1418
- meta.name = "mobile-web-app-capable";
1419
- meta.content = "yes";
1420
- document.head.append(meta);
1421
- }
1422
- if (!document.querySelector('meta[name="apple-mobile-web-app-status-bar-style"]')) {
1423
- const meta = document.createElement("meta");
1424
- meta.name = "apple-mobile-web-app-status-bar-style";
1425
- meta.content = "default";
1426
- document.head.append(meta);
1427
- }
1428
- if (settings["title"] && !document.querySelector('meta[name="apple-mobile-web-app-title"]')) {
1429
- const meta = document.createElement("meta");
1430
- meta.name = "apple-mobile-web-app-title";
1431
- meta.content = settings["title"];
1432
- document.head.append(meta);
1433
- }
1434
- if (settings["public_url"] && !document.querySelector('link[rel="apple-touch-icon"]')) {
1435
- const meta = document.createElement("link");
1436
- meta.rel = "apple-touch-icon";
1437
- meta.href = settings["public_url"] + "/favicon.ico";
1438
- document.head.append(meta);
1439
- }
1440
- if (settings["public_url"] && !document.querySelector('link[rel="apple-touch-startup-image"]')) {
1441
- const meta = document.createElement("link");
1442
- meta.rel = "apple-touch-startup-image";
1443
- meta.href = settings["public_url"] + "/favicon.ico";
1444
- document.head.append(meta);
1445
- }
1446
1500
  if (settings["title"]) document.querySelectorAll(".momentum-title").forEach((el) => el.innerText = settings["title"]);
1447
1501
  if (settings["description"]) document.querySelectorAll(".momentum-description").forEach((el) => el.innerText = settings["description"]);
1448
1502
  if (settings["version"]) document.querySelectorAll(".momentum-version").forEach((el) => el.innerText = settings["version"]);
1449
1503
  if (settings["logo"]) document.querySelectorAll(".momentum-logo").forEach((el) => el.src = settings["logo"]);
1450
- if (settings["theme"]) {
1451
- document.body.classList.add(((_a = settings["theme"]) == null ? void 0 : _a.darkMode) ? "theme-dark" : "theme-light");
1452
- document.body.classList.remove(((_b = settings["theme"]) == null ? void 0 : _b.darkMode) ? "theme-light" : "theme-dark");
1453
- document.body.style.setProperty("--theme-backdrop", (_c = settings["theme"]) == null ? void 0 : _c.background);
1454
- document.body.style.setProperty("--theme-primary", (_d = settings["theme"]) == null ? void 0 : _d.primary);
1455
- document.body.style.setProperty("--theme-accent", (_e = settings["theme"]) == null ? void 0 : _e.accent);
1456
- document.body.style.setProperty("--theme-contrast", blackOrWhite((_f = settings["theme"]) == null ? void 0 : _f.background));
1457
- }
1504
+ meta("name", "theme-color", (_a = settings["theme"]) == null ? void 0 : _a.background);
1505
+ document.body.classList.add(((_b = settings["theme"]) == null ? void 0 : _b.darkMode) ? "theme-dark" : "theme-light");
1506
+ document.body.classList.remove(((_c = settings["theme"]) == null ? void 0 : _c.darkMode) ? "theme-light" : "theme-dark");
1507
+ document.body.style.setProperty("--theme-backdrop", (_d = settings["theme"]) == null ? void 0 : _d.background);
1508
+ document.body.style.setProperty("--theme-primary", (_e = settings["theme"]) == null ? void 0 : _e.primary);
1509
+ document.body.style.setProperty("--theme-accent", (_f = settings["theme"]) == null ? void 0 : _f.accent);
1510
+ document.body.style.setProperty("--theme-contrast", blackOrWhite((_g = settings["theme"]) == null ? void 0 : _g.background));
1511
+ meta("name", "description", settings["description"]);
1512
+ meta("itemprop", "name", settings["title"]);
1513
+ meta("itemprop", "description", settings["description"]);
1514
+ meta("itemprop", "image", `${settings["public_url"]}/favicon.png`);
1515
+ meta("property", "og:type", "website");
1516
+ meta("property", "og:url", settings["public_url"]);
1517
+ meta("property", "og:image", `${settings["public_url"]}/favicon.png`);
1518
+ meta("property", "og:title", settings["title"]);
1519
+ meta("property", "og:description", settings["description"]);
1520
+ meta("name", "twitter:card", "summary_large_image");
1521
+ meta("name", "twitter:title", settings["title"]);
1522
+ meta("name", "twitter:description", settings["description"]);
1523
+ meta("name", "twitter:image", `${settings["public_url"]}/favicon.png`);
1524
+ meta("name", "mobile-web-app-capable", "yes");
1525
+ meta("name", "apple-mobile-web-app-status-bar-style", "default");
1526
+ meta("name", "apple-mobile-web-app-title", settings["title"]);
1527
+ meta("name", "apple-touch-icon", `${settings["public_url"]}/favicon.png`);
1528
+ meta("name", "apple-touch-startup-image", `${settings["public_url"]}/favicon.png`);
1458
1529
  if (settings["pwa"]) {
1459
- const link = document.createElement("link");
1460
- link.setAttribute("rel", "manifest");
1461
- link.setAttribute("href", this.settings.api.url + "/manifest.json");
1462
- document.head.append(link);
1530
+ if (!document.querySelector('link[rel="manifest"]')) {
1531
+ const link = document.createElement("link");
1532
+ link.setAttribute("rel", "manifest");
1533
+ link.setAttribute("href", this.settings.api.url + "/manifest.json");
1534
+ document.head.append(link);
1535
+ }
1463
1536
  setTimeout(() => {
1464
1537
  const dismissed = !!localStorage.getItem("momentum:install-prompt");
1465
1538
  if (!dismissed && !this.pwa && this.mobile) this.pwaPrompt();
1466
- }, 500);
1539
+ }, 6e4);
1467
1540
  }
1468
1541
  this.emit(PES`client/inject:c`, this.platform);
1469
1542
  }
1543
+ /**
1544
+ * Create UI prompt for user to install as PWA
1545
+ * @param platform Platform prompt, leave blank to auto-detect
1546
+ */
1470
1547
  pwaPrompt(platform) {
1548
+ if (document.querySelector(".momentum-pwa-prompt")) return;
1471
1549
  const url = this.settings.api.url;
1472
1550
  const settings = this.settings.cache;
1473
1551
  const android = (platform || this.platform) == "android";
@@ -1574,7 +1652,7 @@ class Client extends PathEventEmitter {
1574
1652
  </div>`;
1575
1653
  const close = document.createElement("button");
1576
1654
  close.classList.add("momentum-pwa-prompt-close");
1577
- close.innerText = "X";
1655
+ close.innerText = "x";
1578
1656
  close.onclick = () => {
1579
1657
  prompt.classList.add("exit");
1580
1658
  backdrop.classList.add("exit");
@@ -1590,6 +1668,10 @@ class Client extends PathEventEmitter {
1590
1668
  document.body.append(backdrop);
1591
1669
  this.emit(PES`client/pwa:c`, platform);
1592
1670
  }
1671
+ /**
1672
+ * Enable device notifications
1673
+ * @return {Promise<null>} Resolves on success
1674
+ */
1593
1675
  async enableNotifications() {
1594
1676
  const granted = await Notification.requestPermission();
1595
1677
  if (!granted) return null;
@@ -1600,6 +1682,10 @@ class Client extends PathEventEmitter {
1600
1682
  })).toJSON();
1601
1683
  return this.api.request({ url: "/api/notifications", body: subscription }).then(() => this.notifications = true);
1602
1684
  }
1685
+ /**
1686
+ * Disable device notifications
1687
+ * @return {Promise<void>} Resolves on success
1688
+ */
1603
1689
  async disableNotifications() {
1604
1690
  var _a;
1605
1691
  const subscription = await this.pushSubscription;
@@ -1609,11 +1695,36 @@ class Client extends PathEventEmitter {
1609
1695
  } }).then(() => this.notifications = false);
1610
1696
  }
1611
1697
  }
1698
+ class Schemas extends PathEventEmitter {
1699
+ constructor(api) {
1700
+ super();
1701
+ this.api = api;
1702
+ }
1703
+ delete(path) {
1704
+ if (!path) throw new Error("Cannot delete schema, missing collection path");
1705
+ return this.api.request({ url: `/api/` + PES`schema/${path}`, method: "DELETE" }).then(() => this.emit(PES`schema/${path}:d`, path));
1706
+ }
1707
+ read(pathOrTree) {
1708
+ return this.api.request({ url: "/api/" + PES`schema/${typeof pathOrTree == "string" ? pathOrTree : ""}` + (pathOrTree === true ? `?tree=${pathOrTree}` : "") }).then((resp) => {
1709
+ this.emit(PES`schema/${typeof pathOrTree == "string" ? pathOrTree : ""}:r`, resp);
1710
+ return resp;
1711
+ });
1712
+ }
1713
+ update(schema) {
1714
+ if (!schema.path) throw new Error("Cannot update schema, missing collection path");
1715
+ return this.api.request({ url: "/api/" + PES`schema/${schema.path}`, body: schema }).then((resp) => {
1716
+ this.emit(PES`schema/${schema.path}:${schema._id ? "u" : "c"}`, resp);
1717
+ return resp;
1718
+ });
1719
+ }
1720
+ }
1612
1721
  class Data extends PathEventEmitter {
1613
1722
  constructor(api) {
1614
1723
  super();
1615
1724
  __publicField(this, "api");
1725
+ __publicField(this, "schema");
1616
1726
  this.api = typeof api == "string" ? new Api(api) : api;
1727
+ this.schema = new Schemas(this.api);
1617
1728
  }
1618
1729
  create(collection, document2) {
1619
1730
  if (!collection || !document2) throw new Error("Cannot create document, missing collection or document");
@@ -1659,24 +1770,6 @@ class Data extends PathEventEmitter {
1659
1770
  return resp;
1660
1771
  });
1661
1772
  }
1662
- // Schema ==========================================================================================================
1663
- deleteSchema(path) {
1664
- if (!path) throw new Error("Cannot delete schema, missing collection path");
1665
- return this.api.request({ url: `/api/` + PES`schema/${path}`, method: "DELETE" }).then(() => this.emit(PES`schema/${path}:d`, path));
1666
- }
1667
- readSchema(pathOrTree) {
1668
- return this.api.request({ url: "/api/" + PES`schema/${typeof pathOrTree == "string" ? pathOrTree : ""}` + (pathOrTree === true ? `?tree=${pathOrTree}` : "") }).then((resp) => {
1669
- this.emit(PES`schema/${typeof pathOrTree == "string" ? pathOrTree : ""}:r`, resp);
1670
- return resp;
1671
- });
1672
- }
1673
- updateSchema(schema) {
1674
- if (!schema.path) throw new Error("Cannot update schema, missing collection path");
1675
- return this.api.request({ url: "/api/" + PES`schema/${schema.path}`, body: schema }).then((resp) => {
1676
- this.emit(PES`schema/${schema.path}:${schema._id ? "u" : "c"}`, resp);
1677
- return resp;
1678
- });
1679
- }
1680
1773
  }
1681
1774
  class Email extends PathEventEmitter {
1682
1775
  constructor(api) {
@@ -1684,6 +1777,11 @@ class Email extends PathEventEmitter {
1684
1777
  __publicField(this, "api");
1685
1778
  this.api = typeof api == "string" ? new Api(api) : api;
1686
1779
  }
1780
+ /**
1781
+ * Send Email
1782
+ * @param {Mail} email Email to send
1783
+ * @return {Promise<any>} Response
1784
+ */
1687
1785
  send(email) {
1688
1786
  if (!email.to && !email.bcc || !email.body) throw new Error("Cannot send email, missing address or body");
1689
1787
  return this.api.request({ url: "/api/email", body: email }).then((response) => {
@@ -1894,46 +1992,102 @@ ${log}`;
1894
1992
  }
1895
1993
  }
1896
1994
  class Payments extends PathEventEmitter {
1897
- constructor(api, secret) {
1995
+ constructor(api, opts = {}) {
1898
1996
  super();
1899
1997
  __publicField(this, "api");
1998
+ __publicField(this, "loaded", false);
1900
1999
  __publicField(this, "stripe");
2000
+ __publicField(this, "token");
2001
+ this.opts = opts;
1901
2002
  this.api = typeof api == "string" ? new Api(api) : api;
1902
- const setup = (retry = 1) => {
1903
- try {
1904
- if (!Stripe) throw new Error("Stripe not added");
1905
- this.stripe = Stripe(secret);
1906
- } catch (err) {
1907
- if (retry > 0) setTimeout(() => setup(retry--), 250);
1908
- else console.warn("Stripe failed, did you add the library & setup your API key?", err);
1909
- }
2003
+ this.opts = {
2004
+ paymentUrl: this.api.url + "/ui/#/payment",
2005
+ ...this.opts
1910
2006
  };
1911
- setup();
1912
2007
  }
1913
- async create(amount, custom = {}) {
1914
- if (!amount) throw new Error("Please specify a valid amount`");
2008
+ async checkout(cart, invoice, extra = {}) {
2009
+ if (!(cart == null ? void 0 : cart.length)) throw new Error("Please add items to cart`");
1915
2010
  const request = await this.api.request({ url: "/api/payments", body: {
1916
- amount,
1917
- custom
2011
+ cart,
2012
+ invoice,
2013
+ extra
1918
2014
  } });
1919
- this.emit(PES`payments:c`, request.data.clientSecret);
1920
- return request.data.clientSecret;
2015
+ this.emit(PES`payments:c`, request.token);
2016
+ return request.token;
1921
2017
  }
1922
- async createForm(element, amount, custom) {
1923
- const token = await this.create(amount, custom);
1924
- const form = this.stripe.elements({ clientSecret: token });
1925
- form.create("payment").mount(element);
1926
- return () => this.stripe.confirmPayment({
1927
- elements: form,
1928
- redirect: "if_required",
1929
- confirmParams: { return_url: location.origin }
2018
+ async init() {
2019
+ if (!this.stripe) this.stripe = await new Promise((res) => {
2020
+ if (this.loaded || this.stripe) return res(window["Stripe"]);
2021
+ const script = document.createElement("script");
2022
+ script.src = "https://js.stripe.com/v3/";
2023
+ script.setAttribute("crossorigin", "anonymous");
2024
+ script.onload = () => res(window["Stripe"]);
2025
+ document.head.appendChild(script);
1930
2026
  });
2027
+ return this.stripe(this.token);
1931
2028
  }
1932
2029
  async history(username) {
1933
- const history = await this.api.request({ url: `/api/` + PES`payments/${username}` });
2030
+ const history = await this.api.request({ url: `/api/payments/${username || ""}` });
1934
2031
  this.emit(PES`payments/${username}:r`, history);
1935
2032
  return history;
1936
2033
  }
2034
+ async products(id) {
2035
+ return this.api.request({ url: `/api/payments/products/${id || ""}` }).then((resp) => {
2036
+ this.emit(PES`products/${id}:r`, resp);
2037
+ return resp;
2038
+ });
2039
+ }
2040
+ async payment(card, token) {
2041
+ const client = await this.init();
2042
+ return client.confirmPayment({
2043
+ clientSecret: token,
2044
+ confirmParams: {
2045
+ payment_method: {
2046
+ card: { ...card, details: void 0 },
2047
+ billing_details: card.details || {}
2048
+ }
2049
+ }
2050
+ });
2051
+ }
2052
+ async paymentForm(element, token) {
2053
+ const client = await this.init();
2054
+ const form = client.elements({ clientSecret: token });
2055
+ form.create("payment").mount(element);
2056
+ return (redirect = location.href) => client.confirmPayment({
2057
+ elements: form,
2058
+ redirect: "if_required",
2059
+ confirmParams: { return_url: redirect + "?payment_status=success" }
2060
+ });
2061
+ }
2062
+ /**
2063
+ * Complete payment via Momentum's payment page
2064
+ * @param {string} token Stripe payment intent secret
2065
+ * @param {string} host Host origin attempting to login
2066
+ * @return {Promise<string>} Token on success
2067
+ */
2068
+ paymentRedirect(token, host = location.origin) {
2069
+ return new Promise(async (res, rej) => {
2070
+ var _a;
2071
+ let origin = new URL(this.opts.paymentUrl).origin, done = false, listener, win;
2072
+ window.addEventListener("message", listener = (event) => {
2073
+ const data = (event == null ? void 0 : event.data) || {};
2074
+ if (event.origin != origin || data.sender != origin) return;
2075
+ if (!data.response) return rej("Unknown response from payment page");
2076
+ done = true;
2077
+ window.removeEventListener("message", listener);
2078
+ win.close();
2079
+ res(data.response);
2080
+ });
2081
+ win = window.open(encodeURI(`${(_a = this.opts) == null ? void 0 : _a.paymentUrl}?token=${token}&host=${host}`), "_blank");
2082
+ if (!win) {
2083
+ window.removeEventListener("message", listener);
2084
+ return rej("Unable to open payment page");
2085
+ }
2086
+ win.addEventListener("close", () => {
2087
+ if (!done) rej("Window closed before payment was complete");
2088
+ });
2089
+ });
2090
+ }
1937
2091
  }
1938
2092
  class Pdf extends PathEventEmitter {
1939
2093
  constructor(api) {
@@ -2254,7 +2408,7 @@ class Momentum extends PathEventEmitter {
2254
2408
  this.forms = new Forms(this.api);
2255
2409
  this.groups = new Groups(this.api);
2256
2410
  this.logger = new Logger(this.api, (opts == null ? void 0 : opts.app) || "client", opts == null ? void 0 : opts.logLevel);
2257
- if (opts == null ? void 0 : opts.stripeSecret) this.payments = new Payments(this.api, opts.stripeSecret);
2411
+ this.payments = new Payments(this.api);
2258
2412
  this.pdf = new Pdf(this.api);
2259
2413
  this.phone = new Phone(this.api);
2260
2414
  this.settings = new Settings(this.api);
@@ -2273,7 +2427,7 @@ class Momentum extends PathEventEmitter {
2273
2427
  this.relayEvents(this.email);
2274
2428
  this.relayEvents(this.groups);
2275
2429
  this.relayEvents(this.logger);
2276
- if (this.payments) this.relayEvents(this.payments);
2430
+ this.relayEvents(this.payments);
2277
2431
  this.relayEvents(this.pdf);
2278
2432
  this.relayEvents(this.phone);
2279
2433
  this.relayEvents(this.settings);
@@ -2285,8 +2439,12 @@ class Momentum extends PathEventEmitter {
2285
2439
  const cached = this.users.cache.get(this.auth.user.username);
2286
2440
  if (cached) this.auth.user = cached;
2287
2441
  });
2288
- if (!(opts == null ? void 0 : opts.disableWorker) && "serviceWorker" in navigator) {
2289
- navigator.serviceWorker.register((opts == null ? void 0 : opts.workerUrl) || "/momentum.worker.js").catch(() => console.warn("Unable to load momentum worker, some features may be limited."));
2442
+ this.settings.on("settings:r", (event, value) => {
2443
+ var _a;
2444
+ this.payments.token = ((_a = value["stripe_token"]) == null ? void 0 : _a.value) || value["stripe_token"];
2445
+ });
2446
+ if ((opts == null ? void 0 : opts.worker) !== false && "serviceWorker" in navigator) {
2447
+ navigator.serviceWorker.register((opts == null ? void 0 : opts.worker) || "/momentum.worker.js").catch(() => console.warn("Unable to load momentum worker, some features may be limited."));
2290
2448
  }
2291
2449
  }
2292
2450
  }