@ztimson/momentum 0.51.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,34 +1328,55 @@ 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;
1248
- const win = window.open(encodeURI(`${(_a = this.opts) == null ? void 0 : _a.loginUrl}?redirect=postmessage&host=${host}`), "_blank");
1249
- if (!win) return rej("Unable to open login");
1250
- const origin = new URL(this.opts.loginUrl).origin;
1251
- win.addEventListener("message", (event) => {
1339
+ let origin = new URL(this.opts.loginUrl).origin, done = false, listener, win;
1340
+ window.addEventListener("message", listener = (event) => {
1252
1341
  const data = (event == null ? void 0 : event.data) || {};
1253
- if (event.origin != data.sender || data.sender != origin) return;
1342
+ if (event.origin != origin || data.sender != origin) return;
1254
1343
  if (!data.token) return rej("Unknown response from login");
1255
- this.api.token = event.data.token;
1256
- res(event.data.token);
1344
+ done = true;
1345
+ this.api.token = data.token;
1346
+ window.removeEventListener("message", listener);
1257
1347
  win.close();
1348
+ res(data.token);
1349
+ });
1350
+ win = window.open(encodeURI(`${(_a = this.opts) == null ? void 0 : _a.loginUrl}?redirect=postmessage&host=${host}`), "_blank");
1351
+ if (!win) {
1352
+ window.removeEventListener("message", listener);
1353
+ return rej("Unable to open login");
1354
+ }
1355
+ win.addEventListener("close", () => {
1356
+ if (!done) rej("Window closed before logging in");
1258
1357
  });
1259
1358
  });
1260
1359
  }
1360
+ /**
1361
+ * Logout current user
1362
+ */
1261
1363
  logout() {
1262
1364
  this.emit(PES`auth/logout:d`, this.user);
1263
1365
  this.api.token = null;
1264
1366
  this.user = null;
1265
1367
  }
1266
- 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) {
1267
1374
  var _a;
1268
- if (!u.username || !u.password) throw new Error("Cannot register user, missing username or password");
1269
- const user = await this.api.request({ url: "/api/auth/register", body: { ...u } });
1270
- if ((_a = user == null ? void 0 : user.image) == null ? void 0 : _a.startsWith("/")) user.image = `${this.api.url}${user.image}?token=${this.api.token}`;
1271
- this.emit(PES`auth/register:c`, user);
1272
- 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;
1273
1380
  }
1274
1381
  reset(emailOrPass, token) {
1275
1382
  if (!emailOrPass) throw new Error("Cannot reset password, missing email or token");
@@ -1284,6 +1391,12 @@ class Auth extends PathEventEmitter {
1284
1391
  this.emit(PES`auth/reset:${token ? "u" : "c"}`, token || emailOrPass);
1285
1392
  });
1286
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
+ */
1287
1400
  async session(token, set = false) {
1288
1401
  if (!token) token = this.api.token;
1289
1402
  const session = await this.api.request({
@@ -1300,6 +1413,13 @@ class Auth extends PathEventEmitter {
1300
1413
  }
1301
1414
  return session;
1302
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
+ */
1303
1423
  async updatePassword(username, password, oldPassword) {
1304
1424
  if (!username || !password) throw new Error("Cannot update password, missing username or password");
1305
1425
  return this.api.request({
@@ -1321,9 +1441,11 @@ class Client extends PathEventEmitter {
1321
1441
  this.settings = settings;
1322
1442
  this.pushSubscription.then((resp) => this.notifications = !!resp);
1323
1443
  }
1444
+ /** Check if mobile device */
1324
1445
  get mobile() {
1325
1446
  return ["android", "ios"].includes(this.platform);
1326
1447
  }
1448
+ /** Are notifications enabled */
1327
1449
  get notifications() {
1328
1450
  return this._notifications;
1329
1451
  }
@@ -1331,6 +1453,7 @@ class Client extends PathEventEmitter {
1331
1453
  this._notifications = enabled;
1332
1454
  this.emit(PES`client/notifications:${enabled ? "c" : "d"}`, enabled);
1333
1455
  }
1456
+ /** Get the current platform type */
1334
1457
  get platform() {
1335
1458
  if (!this._platform) {
1336
1459
  const userAgent = navigator.userAgent || navigator.vendor;
@@ -1343,6 +1466,7 @@ class Client extends PathEventEmitter {
1343
1466
  }
1344
1467
  return this._platform;
1345
1468
  }
1469
+ /** Get Push Subscription info */
1346
1470
  get pushSubscription() {
1347
1471
  var _a;
1348
1472
  if (!((_a = navigator.serviceWorker) == null ? void 0 : _a.controller)) return Promise.resolve(null);
@@ -1351,115 +1475,77 @@ class Client extends PathEventEmitter {
1351
1475
  return (_a2 = sw == null ? void 0 : sw.pushManager) == null ? void 0 : _a2.getSubscription();
1352
1476
  });
1353
1477
  }
1478
+ /** Check if running as a PWA */
1354
1479
  get pwa() {
1355
1480
  if (this._pwa == null)
1356
1481
  this._pwa = matchMedia("(display-mode: standalone)").matches || (navigator == null ? void 0 : navigator.standalone) || document.referrer.includes("android-app://");
1357
1482
  return this._pwa;
1358
1483
  }
1359
- async inject(reload = false, firstLoad = true) {
1360
- 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
+ };
1361
1499
  let settings = this.settings.cache;
1362
- if (firstLoad) this.settings.all(false, reload).then(() => this.inject(false, false)).catch(() => {
1363
- });
1364
- if (settings["title"] && !document.head.querySelector('meta[property="og:title"]')) {
1365
- const meta = document.createElement("meta");
1366
- meta.setAttribute("property", "og:title");
1367
- meta.content = settings["title"];
1368
- document.head.append(meta);
1369
- }
1370
- if (!document.querySelector('meta[property="og:type"]')) {
1371
- const meta = document.createElement("meta");
1372
- meta.setAttribute("property", "og:type");
1373
- meta.content = "article";
1374
- document.head.append(meta);
1375
- }
1376
- if (settings["public_url"] && !document.querySelector('meta[property="og:url"]')) {
1377
- const meta = document.createElement("meta");
1378
- meta.setAttribute("property", "og:url");
1379
- meta.content = settings["public_url"];
1380
- document.head.append(meta);
1381
- }
1382
- if (settings["description"]) {
1383
- if (!document.querySelector('meta[property="og:description"]')) {
1384
- const meta = document.createElement("meta");
1385
- meta.setAttribute("property", "og:description");
1386
- meta.content = settings["description"];
1387
- document.head.append(meta);
1388
- }
1389
- if (!document.querySelector('meta[property="description"]')) {
1390
- const meta = document.createElement("meta");
1391
- meta.setAttribute("property", "description");
1392
- meta.content = settings["description"];
1393
- document.head.append(meta);
1394
- }
1395
- if (!document.querySelector('meta[property="twitter:card"]')) {
1396
- const meta = document.createElement("meta");
1397
- meta.setAttribute("property", "twitter:card");
1398
- meta.content = settings["description"];
1399
- document.head.append(meta);
1400
- }
1401
- }
1402
- if (settings["public_url"] && !document.querySelector('meta[property="og:image"]')) {
1403
- const meta = document.createElement("meta");
1404
- meta.setAttribute("property", "og:image");
1405
- meta.content = settings["public_url"] + "/favicon.ico";
1406
- document.head.append(meta);
1407
- }
1408
- if (!document.querySelector('meta[name="mobile-web-app-capable"]')) {
1409
- const meta = document.createElement("meta");
1410
- meta.name = "mobile-web-app-capable";
1411
- meta.content = "yes";
1412
- document.head.append(meta);
1413
- }
1414
- if (!document.querySelector('meta[name="apple-mobile-web-app-status-bar-style"]')) {
1415
- const meta = document.createElement("meta");
1416
- meta.name = "apple-mobile-web-app-status-bar-style";
1417
- meta.content = "default";
1418
- document.head.append(meta);
1419
- }
1420
- if (settings["title"] && !document.querySelector('meta[name="apple-mobile-web-app-title"]')) {
1421
- const meta = document.createElement("meta");
1422
- meta.name = "apple-mobile-web-app-title";
1423
- meta.content = settings["title"];
1424
- document.head.append(meta);
1425
- }
1426
- if (settings["public_url"] && !document.querySelector('link[rel="apple-touch-icon"]')) {
1427
- const meta = document.createElement("link");
1428
- meta.rel = "apple-touch-icon";
1429
- meta.href = settings["public_url"] + "/favicon.ico";
1430
- document.head.append(meta);
1431
- }
1432
- if (settings["public_url"] && !document.querySelector('link[rel="apple-touch-startup-image"]')) {
1433
- const meta = document.createElement("link");
1434
- meta.rel = "apple-touch-startup-image";
1435
- meta.href = settings["public_url"] + "/favicon.ico";
1436
- document.head.append(meta);
1437
- }
1438
1500
  if (settings["title"]) document.querySelectorAll(".momentum-title").forEach((el) => el.innerText = settings["title"]);
1439
1501
  if (settings["description"]) document.querySelectorAll(".momentum-description").forEach((el) => el.innerText = settings["description"]);
1440
1502
  if (settings["version"]) document.querySelectorAll(".momentum-version").forEach((el) => el.innerText = settings["version"]);
1441
1503
  if (settings["logo"]) document.querySelectorAll(".momentum-logo").forEach((el) => el.src = settings["logo"]);
1442
- if (settings["theme"]) {
1443
- document.body.classList.add(((_a = settings["theme"]) == null ? void 0 : _a.darkMode) ? "theme-dark" : "theme-light");
1444
- document.body.classList.remove(((_b = settings["theme"]) == null ? void 0 : _b.darkMode) ? "theme-light" : "theme-dark");
1445
- document.body.style.setProperty("--theme-backdrop", (_c = settings["theme"]) == null ? void 0 : _c.background);
1446
- document.body.style.setProperty("--theme-primary", (_d = settings["theme"]) == null ? void 0 : _d.primary);
1447
- document.body.style.setProperty("--theme-accent", (_e = settings["theme"]) == null ? void 0 : _e.accent);
1448
- document.body.style.setProperty("--theme-contrast", blackOrWhite((_f = settings["theme"]) == null ? void 0 : _f.background));
1449
- }
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`);
1450
1529
  if (settings["pwa"]) {
1451
- const link = document.createElement("link");
1452
- link.setAttribute("rel", "manifest");
1453
- link.setAttribute("href", this.settings.api.url + "/manifest.json");
1454
- 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
+ }
1455
1536
  setTimeout(() => {
1456
1537
  const dismissed = !!localStorage.getItem("momentum:install-prompt");
1457
1538
  if (!dismissed && !this.pwa && this.mobile) this.pwaPrompt();
1458
- }, 500);
1539
+ }, 6e4);
1459
1540
  }
1460
1541
  this.emit(PES`client/inject:c`, this.platform);
1461
1542
  }
1543
+ /**
1544
+ * Create UI prompt for user to install as PWA
1545
+ * @param platform Platform prompt, leave blank to auto-detect
1546
+ */
1462
1547
  pwaPrompt(platform) {
1548
+ if (document.querySelector(".momentum-pwa-prompt")) return;
1463
1549
  const url = this.settings.api.url;
1464
1550
  const settings = this.settings.cache;
1465
1551
  const android = (platform || this.platform) == "android";
@@ -1566,7 +1652,7 @@ class Client extends PathEventEmitter {
1566
1652
  </div>`;
1567
1653
  const close = document.createElement("button");
1568
1654
  close.classList.add("momentum-pwa-prompt-close");
1569
- close.innerText = "X";
1655
+ close.innerText = "x";
1570
1656
  close.onclick = () => {
1571
1657
  prompt.classList.add("exit");
1572
1658
  backdrop.classList.add("exit");
@@ -1582,6 +1668,10 @@ class Client extends PathEventEmitter {
1582
1668
  document.body.append(backdrop);
1583
1669
  this.emit(PES`client/pwa:c`, platform);
1584
1670
  }
1671
+ /**
1672
+ * Enable device notifications
1673
+ * @return {Promise<null>} Resolves on success
1674
+ */
1585
1675
  async enableNotifications() {
1586
1676
  const granted = await Notification.requestPermission();
1587
1677
  if (!granted) return null;
@@ -1592,6 +1682,10 @@ class Client extends PathEventEmitter {
1592
1682
  })).toJSON();
1593
1683
  return this.api.request({ url: "/api/notifications", body: subscription }).then(() => this.notifications = true);
1594
1684
  }
1685
+ /**
1686
+ * Disable device notifications
1687
+ * @return {Promise<void>} Resolves on success
1688
+ */
1595
1689
  async disableNotifications() {
1596
1690
  var _a;
1597
1691
  const subscription = await this.pushSubscription;
@@ -1601,11 +1695,36 @@ class Client extends PathEventEmitter {
1601
1695
  } }).then(() => this.notifications = false);
1602
1696
  }
1603
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
+ }
1604
1721
  class Data extends PathEventEmitter {
1605
1722
  constructor(api) {
1606
1723
  super();
1607
1724
  __publicField(this, "api");
1725
+ __publicField(this, "schema");
1608
1726
  this.api = typeof api == "string" ? new Api(api) : api;
1727
+ this.schema = new Schemas(this.api);
1609
1728
  }
1610
1729
  create(collection, document2) {
1611
1730
  if (!collection || !document2) throw new Error("Cannot create document, missing collection or document");
@@ -1651,24 +1770,6 @@ class Data extends PathEventEmitter {
1651
1770
  return resp;
1652
1771
  });
1653
1772
  }
1654
- // Schema ==========================================================================================================
1655
- deleteSchema(path) {
1656
- if (!path) throw new Error("Cannot delete schema, missing collection path");
1657
- return this.api.request({ url: `/api/` + PES`schema/${path}`, method: "DELETE" }).then(() => this.emit(PES`schema/${path}:d`, path));
1658
- }
1659
- readSchema(pathOrTree) {
1660
- return this.api.request({ url: "/api/" + PES`schema/${typeof pathOrTree == "string" ? pathOrTree : ""}` + (pathOrTree === true ? `?tree=${pathOrTree}` : "") }).then((resp) => {
1661
- this.emit(PES`schema/${typeof pathOrTree == "string" ? pathOrTree : ""}:r`, resp);
1662
- return resp;
1663
- });
1664
- }
1665
- updateSchema(schema) {
1666
- if (!schema.path) throw new Error("Cannot update schema, missing collection path");
1667
- return this.api.request({ url: "/api/" + PES`schema/${schema.path}`, body: schema }).then((resp) => {
1668
- this.emit(PES`schema/${schema.path}:${schema._id ? "u" : "c"}`, resp);
1669
- return resp;
1670
- });
1671
- }
1672
1773
  }
1673
1774
  class Email extends PathEventEmitter {
1674
1775
  constructor(api) {
@@ -1676,6 +1777,11 @@ class Email extends PathEventEmitter {
1676
1777
  __publicField(this, "api");
1677
1778
  this.api = typeof api == "string" ? new Api(api) : api;
1678
1779
  }
1780
+ /**
1781
+ * Send Email
1782
+ * @param {Mail} email Email to send
1783
+ * @return {Promise<any>} Response
1784
+ */
1679
1785
  send(email) {
1680
1786
  if (!email.to && !email.bcc || !email.body) throw new Error("Cannot send email, missing address or body");
1681
1787
  return this.api.request({ url: "/api/email", body: email }).then((response) => {
@@ -1886,46 +1992,102 @@ ${log}`;
1886
1992
  }
1887
1993
  }
1888
1994
  class Payments extends PathEventEmitter {
1889
- constructor(api, secret) {
1995
+ constructor(api, opts = {}) {
1890
1996
  super();
1891
1997
  __publicField(this, "api");
1998
+ __publicField(this, "loaded", false);
1892
1999
  __publicField(this, "stripe");
2000
+ __publicField(this, "token");
2001
+ this.opts = opts;
1893
2002
  this.api = typeof api == "string" ? new Api(api) : api;
1894
- const setup = (retry = 1) => {
1895
- try {
1896
- if (!Stripe) throw new Error("Stripe not added");
1897
- this.stripe = Stripe(secret);
1898
- } catch (err) {
1899
- if (retry > 0) setTimeout(() => setup(retry--), 250);
1900
- else console.warn("Stripe failed, did you add the library & setup your API key?", err);
1901
- }
2003
+ this.opts = {
2004
+ paymentUrl: this.api.url + "/ui/#/payment",
2005
+ ...this.opts
1902
2006
  };
1903
- setup();
1904
2007
  }
1905
- async create(amount, custom = {}) {
1906
- 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`");
1907
2010
  const request = await this.api.request({ url: "/api/payments", body: {
1908
- amount,
1909
- custom
2011
+ cart,
2012
+ invoice,
2013
+ extra
1910
2014
  } });
1911
- this.emit(PES`payments:c`, request.data.clientSecret);
1912
- return request.data.clientSecret;
2015
+ this.emit(PES`payments:c`, request.token);
2016
+ return request.token;
1913
2017
  }
1914
- async createForm(element, amount, custom) {
1915
- const token = await this.create(amount, custom);
1916
- const form = this.stripe.elements({ clientSecret: token });
1917
- form.create("payment").mount(element);
1918
- return () => this.stripe.confirmPayment({
1919
- elements: form,
1920
- redirect: "if_required",
1921
- 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);
1922
2026
  });
2027
+ return this.stripe(this.token);
1923
2028
  }
1924
2029
  async history(username) {
1925
- const history = await this.api.request({ url: `/api/` + PES`payments/${username}` });
2030
+ const history = await this.api.request({ url: `/api/payments/${username || ""}` });
1926
2031
  this.emit(PES`payments/${username}:r`, history);
1927
2032
  return history;
1928
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
+ }
1929
2091
  }
1930
2092
  class Pdf extends PathEventEmitter {
1931
2093
  constructor(api) {
@@ -2246,7 +2408,7 @@ class Momentum extends PathEventEmitter {
2246
2408
  this.forms = new Forms(this.api);
2247
2409
  this.groups = new Groups(this.api);
2248
2410
  this.logger = new Logger(this.api, (opts == null ? void 0 : opts.app) || "client", opts == null ? void 0 : opts.logLevel);
2249
- if (opts == null ? void 0 : opts.stripeSecret) this.payments = new Payments(this.api, opts.stripeSecret);
2411
+ this.payments = new Payments(this.api);
2250
2412
  this.pdf = new Pdf(this.api);
2251
2413
  this.phone = new Phone(this.api);
2252
2414
  this.settings = new Settings(this.api);
@@ -2265,7 +2427,7 @@ class Momentum extends PathEventEmitter {
2265
2427
  this.relayEvents(this.email);
2266
2428
  this.relayEvents(this.groups);
2267
2429
  this.relayEvents(this.logger);
2268
- if (this.payments) this.relayEvents(this.payments);
2430
+ this.relayEvents(this.payments);
2269
2431
  this.relayEvents(this.pdf);
2270
2432
  this.relayEvents(this.phone);
2271
2433
  this.relayEvents(this.settings);
@@ -2277,8 +2439,12 @@ class Momentum extends PathEventEmitter {
2277
2439
  const cached = this.users.cache.get(this.auth.user.username);
2278
2440
  if (cached) this.auth.user = cached;
2279
2441
  });
2280
- if (!(opts == null ? void 0 : opts.disableWorker) && "serviceWorker" in navigator) {
2281
- navigator.serviceWorker.register("/momentum-worker.js");
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."));
2282
2448
  }
2283
2449
  }
2284
2450
  }