@freegridjs/core 1.9.0 → 1.9.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.d.ts CHANGED
@@ -5,7 +5,7 @@ export { default as en } from './locales/en.js';
5
5
  declare function createGrid(host: HTMLElement, options: GridOptions): GridApi;
6
6
 
7
7
  /** FreeGrid version (kept in sync with releases.json by scripts/release.mjs). */
8
- declare const version = "1.9.0";
8
+ declare const version = "1.9.1";
9
9
 
10
10
  interface LicenseInfo {
11
11
  /** Licensed domains; each also covers every subdomain below it (`*.example.com` means the same as `example.com`). */
@@ -27,12 +27,13 @@ type LicenseResult = {
27
27
  reason: 'format' | 'signature' | 'expired' | 'domain' | 'version' | 'unsupported';
28
28
  info?: LicenseInfo;
29
29
  };
30
- /** Check a key's signature, expiry, domain and (perpetual keys) the major version. */
31
- declare function verifyLicense(key: string, host: string, opts?: {
30
+ type VerifyOptions = {
32
31
  publicKey?: JsonWebKey;
33
32
  now?: Date;
34
33
  version?: string;
35
- }): Promise<LicenseResult>;
34
+ };
35
+ /** Check a key's signature, expiry, domain and (perpetual keys) the major version. */
36
+ declare function verifyLicense(key: string, host: string, opts?: VerifyOptions): Promise<LicenseResult>;
36
37
  /** Set the license key once for every grid on the page. */
37
38
  declare function setLicenseKey(key: string): void;
38
39
 
package/dist/index.js CHANGED
@@ -3453,6 +3453,7 @@ function attachBody(grid) {
3453
3453
  const keyOf = (e) => mirrored(grid, e);
3454
3454
  let lastPointer = "mouse";
3455
3455
  let pressTimer = 0;
3456
+ let endGesture = null;
3456
3457
  let suppressClick = false;
3457
3458
  const toEvent = (e) => {
3458
3459
  const hit = renderer.cellAt(e.target);
@@ -3675,6 +3676,7 @@ function attachBody(grid) {
3675
3676
  window.removeEventListener("pointermove", move);
3676
3677
  window.removeEventListener("pointerup", cancel);
3677
3678
  window.removeEventListener("pointercancel", cancel);
3679
+ if (endGesture === cancel) endGesture = null;
3678
3680
  };
3679
3681
  const move = (ev) => {
3680
3682
  if (Math.hypot(ev.clientX - x0, ev.clientY - y0) > 8) cancel();
@@ -3690,6 +3692,8 @@ function attachBody(grid) {
3690
3692
  }
3691
3693
  grid.contextMenu.open(x0, y0, hit.rowIndex, hit.colId);
3692
3694
  }, 550);
3695
+ endGesture?.();
3696
+ endGesture = cancel;
3693
3697
  window.addEventListener("pointermove", move);
3694
3698
  window.addEventListener("pointerup", cancel);
3695
3699
  window.addEventListener("pointercancel", cancel);
@@ -3723,11 +3727,16 @@ function attachBody(grid) {
3723
3727
  const up = () => {
3724
3728
  window.removeEventListener("pointermove", move);
3725
3729
  window.removeEventListener("pointerup", up);
3730
+ window.removeEventListener("pointercancel", up);
3726
3731
  root.classList.remove("fg-range-dragging");
3727
3732
  if (dragging) suppressClick = true;
3733
+ if (endGesture === up) endGesture = null;
3728
3734
  };
3735
+ endGesture?.();
3736
+ endGesture = up;
3729
3737
  window.addEventListener("pointermove", move);
3730
3738
  window.addEventListener("pointerup", up);
3739
+ window.addEventListener("pointercancel", up);
3731
3740
  };
3732
3741
  const onKeyDown = (e) => {
3733
3742
  if (e.target !== root && e.target !== renderer.gridEl) return;
@@ -3826,6 +3835,8 @@ function attachBody(grid) {
3826
3835
  root.addEventListener("paste", onPaste);
3827
3836
  root.addEventListener("cut", onCut);
3828
3837
  return () => {
3838
+ clearTimeout(pressTimer);
3839
+ endGesture?.();
3829
3840
  body.removeEventListener("click", onClick);
3830
3841
  body.removeEventListener("dblclick", onDblClick);
3831
3842
  body.removeEventListener("pointerdown", onPointerDown);
@@ -7633,7 +7644,7 @@ var PAID_FEATURES = {
7633
7644
  };
7634
7645
 
7635
7646
  // src/version.ts
7636
- var version = "1.9.0";
7647
+ var version = "1.9.1";
7637
7648
 
7638
7649
  // src/license.ts
7639
7650
  var PUBLIC_KEY = { kty: "EC", crv: "P-256", x: "sUTKkglZmk-HMKPNub8p7AeLBKesHCU8irIkw9rJlbE", y: "j0U_aKRDR7iApY9UpaD3hUemhBLZJwDv9-QiT_hUyw8" };
@@ -7648,11 +7659,17 @@ var b64urlDecode = (s) => {
7648
7659
  };
7649
7660
  function isDevHost(host) {
7650
7661
  const h = host.toLowerCase().replace(/^\[|\]$/g, "");
7651
- return !h || h === "localhost" || h === "127.0.0.1" || h === "::1" || h === "0.0.0.0" || h.endsWith(".localhost") || h.endsWith(".test") || h.endsWith(".local") || /^(10|192\.168|172\.(1[6-9]|2\d|3[01]))\./.test(h);
7662
+ if (!h || h === "localhost" || h === "127.0.0.1" || h === "::1" || h === "0.0.0.0" || h.endsWith(".localhost") || h.endsWith(".test") || h.endsWith(".local")) return true;
7663
+ const ip = /^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$/.exec(h);
7664
+ if (!ip) return false;
7665
+ const [a, b, c, d] = ip.slice(1).map(Number);
7666
+ if ([a, b, c, d].some((n) => n > 255)) return false;
7667
+ return a === 10 || a === 192 && b === 168 || a === 172 && b >= 16 && b <= 31;
7652
7668
  }
7653
7669
  function domainMatches(host, domains) {
7654
7670
  const h = host.toLowerCase().replace(/\.$/, "");
7655
7671
  return domains.some((d) => {
7672
+ if (typeof d !== "string") return false;
7656
7673
  const raw = d.toLowerCase().replace(/\.$/, "");
7657
7674
  const dom = raw.startsWith("*.") ? raw.slice(2) : raw;
7658
7675
  if (!dom) return false;
@@ -7666,20 +7683,32 @@ var isDay = (s) => {
7666
7683
  return d.getUTCMonth() === Number(m[2]) - 1 && d.getUTCDate() === Number(m[3]);
7667
7684
  };
7668
7685
  var today = (now) => `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, "0")}-${String(now.getDate()).padStart(2, "0")}`;
7669
- async function verifyLicense(key, host, opts = {}) {
7686
+ function checkTerms(key, host, opts = {}) {
7670
7687
  const parts = typeof key === "string" ? key.trim().split(".") : [];
7671
- if (parts.length !== 3 || parts[0] !== "FG1") return { ok: false, reason: "format" };
7688
+ if (parts.length !== 3 || parts[0] !== "FG1") return { fail: { ok: false, reason: "format" } };
7672
7689
  let info;
7673
7690
  try {
7674
7691
  info = JSON.parse(new TextDecoder().decode(b64urlDecode(parts[1])));
7675
- if (!Array.isArray(info.domains) || !isDay(info.expires)) return { ok: false, reason: "format" };
7676
- if (info.major !== void 0 && !(Number.isInteger(info.major) && info.major > 0)) return { ok: false, reason: "format" };
7677
- if (info.plan !== void 0 && info.plan !== "company" && info.plan !== "personal") return { ok: false, reason: "format" };
7692
+ if (!info || !Array.isArray(info.domains) || !info.domains.every((d) => typeof d === "string") || !isDay(info.expires)) return { fail: { ok: false, reason: "format" } };
7693
+ if (info.major !== void 0 && !(Number.isInteger(info.major) && info.major > 0)) return { fail: { ok: false, reason: "format" } };
7694
+ if (info.plan !== void 0 && info.plan !== "company" && info.plan !== "personal") return { fail: { ok: false, reason: "format" } };
7678
7695
  } catch {
7679
- return { ok: false, reason: "format" };
7696
+ return { fail: { ok: false, reason: "format" } };
7680
7697
  }
7681
- const subtle = typeof crypto !== "undefined" ? crypto.subtle : void 0;
7682
- if (!subtle) return { ok: false, reason: "unsupported", info };
7698
+ let fail = null;
7699
+ if (today(opts.now ?? /* @__PURE__ */ new Date()) > info.expires) fail = { ok: false, reason: "expired", info };
7700
+ else if (!domainMatches(host, info.domains)) fail = { ok: false, reason: "domain", info };
7701
+ else if (info.major !== void 0 && majorOf(opts.version ?? version) > info.major) fail = { ok: false, reason: "version", info };
7702
+ return { parts, info, fail };
7703
+ }
7704
+ var hasSubtle = () => typeof crypto !== "undefined" && !!crypto.subtle;
7705
+ async function verifyLicense(key, host, opts = {}) {
7706
+ const checked = checkTerms(key, host, opts);
7707
+ if (!("info" in checked)) return checked.fail;
7708
+ const { parts, info } = checked;
7709
+ const terms = () => checked.fail;
7710
+ if (!hasSubtle()) return terms() ?? { ok: false, reason: "unsupported", info };
7711
+ const subtle = crypto.subtle;
7683
7712
  try {
7684
7713
  const pub = await subtle.importKey("jwk", opts.publicKey ?? PUBLIC_KEY, { name: "ECDSA", namedCurve: "P-256" }, false, ["verify"]);
7685
7714
  const signed = new TextEncoder().encode(`${parts[0]}.${parts[1]}`);
@@ -7688,10 +7717,7 @@ async function verifyLicense(key, host, opts = {}) {
7688
7717
  } catch {
7689
7718
  return { ok: false, reason: "signature", info };
7690
7719
  }
7691
- if (today(opts.now ?? /* @__PURE__ */ new Date()) > info.expires) return { ok: false, reason: "expired", info };
7692
- if (!domainMatches(host, info.domains)) return { ok: false, reason: "domain", info };
7693
- if (info.major !== void 0 && majorOf(opts.version ?? version) > info.major) return { ok: false, reason: "version", info };
7694
- return { ok: true, info };
7720
+ return terms() ?? { ok: true, info };
7695
7721
  }
7696
7722
  var globalKey;
7697
7723
  var statuses = /* @__PURE__ */ new Map();
@@ -7729,23 +7755,26 @@ function reportUsage(info, host = typeof location !== "undefined" ? location.hos
7729
7755
  }
7730
7756
  function checkLicense(key = globalKey, host = typeof location !== "undefined" ? location.hostname : "") {
7731
7757
  if (isDevHost(host)) return Promise.resolve(true);
7732
- const k = key ?? globalKey;
7758
+ return keyState(key ?? globalKey, host).then((s) => s === "ok" || s === "unsupported");
7759
+ }
7760
+ function keyState(k, host) {
7733
7761
  const id = `${k ?? ""}@${host}`;
7734
7762
  let status = statuses.get(id);
7735
7763
  if (status) return status;
7736
7764
  if (!k) {
7737
7765
  console.info(NOTICE.missing);
7738
7766
  reportUsage({ version, licensed: false }, host);
7739
- status = Promise.resolve(false);
7767
+ status = Promise.resolve("none");
7740
7768
  } else {
7741
7769
  status = verifyLicense(k, host).then((res) => {
7742
7770
  reportUsage({ version, licensed: res.ok, company: res.ok ? res.info.company : void 0 }, host);
7743
- if (res.ok || res.reason === "unsupported") return res.ok || res.reason === "unsupported";
7771
+ if (res.ok) return "ok";
7772
+ if (res.reason === "unsupported") return "unsupported";
7744
7773
  const extra = res.info ? ` (${res.info.domains.join(", ")}, ${res.info.major ? `${res.info.major}.x` : res.info.expires})` : "";
7745
7774
  console.warn(`${NOTICE[res.reason]}${extra}
7746
7775
  ${PRICING_URL}`);
7747
- return false;
7748
- }, () => false);
7776
+ return "bad";
7777
+ }, () => "bad");
7749
7778
  }
7750
7779
  statuses.set(id, status);
7751
7780
  return status;
@@ -7792,41 +7821,64 @@ function badgeRequested(host = currentHost()) {
7792
7821
  return fetchDomainStatus(host).then((s) => s?.badge === true);
7793
7822
  }
7794
7823
  async function needsFreeBadge(key, host = currentHost()) {
7824
+ await Promise.resolve();
7795
7825
  if (await checkLicense(key, host)) return false;
7796
7826
  return badgeRequested(host);
7797
7827
  }
7798
7828
  var GATE_KEY = "freegrid:gate";
7829
+ var MAX_VERIFIED_KEYS = 5;
7799
7830
  var keyIdOf = (key) => {
7800
7831
  if (!key) return "";
7801
7832
  const parts = key.trim().split(".");
7802
7833
  return parts[parts.length - 1].slice(-16);
7803
7834
  };
7835
+ var readGate = (host) => {
7836
+ const c = JSON.parse(localStorage.getItem(GATE_KEY) ?? "null");
7837
+ if (!c || typeof c !== "object" || c.host !== host || typeof c.trialEnded !== "boolean" || !Array.isArray(c.verifiedKeys) || !c.verifiedKeys.every((k) => typeof k === "string")) return null;
7838
+ return { host, trialEnded: c.trialEnded, ...typeof c.trialEnds === "string" ? { trialEnds: c.trialEnds } : {}, verifiedKeys: c.verifiedKeys };
7839
+ };
7804
7840
  function paidAllowed(key, host = currentHost()) {
7805
7841
  if (isDevHost(host)) return true;
7842
+ const k = key ?? globalKey;
7843
+ if (k && !hasSubtle()) {
7844
+ const checked = checkTerms(k, host);
7845
+ if ("info" in checked && !checked.fail) return true;
7846
+ }
7806
7847
  try {
7807
- const cache2 = JSON.parse(localStorage.getItem(GATE_KEY) ?? "null");
7808
- if (!cache2 || cache2.host !== host || cache2.keyId !== keyIdOf(key ?? globalKey)) return true;
7809
- return cache2.paid !== false;
7848
+ const cache2 = readGate(host);
7849
+ if (!cache2) return true;
7850
+ const id = keyIdOf(k);
7851
+ if (id && cache2.verifiedKeys.includes(id)) return true;
7852
+ return !cache2.trialEnded;
7810
7853
  } catch {
7811
7854
  return true;
7812
7855
  }
7813
7856
  }
7814
- var writeGate = (cache2) => {
7857
+ var updateGate = (host, patch) => {
7815
7858
  try {
7816
- localStorage.setItem(GATE_KEY, JSON.stringify(cache2));
7859
+ let cur = null;
7860
+ try {
7861
+ cur = readGate(host);
7862
+ } catch {
7863
+ }
7864
+ localStorage.setItem(GATE_KEY, JSON.stringify(patch(cur ?? { host, trialEnded: false, verifiedKeys: [] })));
7817
7865
  } catch {
7818
7866
  }
7819
7867
  };
7820
7868
  async function resolveGate(key, host = currentHost()) {
7869
+ if (isDevHost(host)) return true;
7870
+ await Promise.resolve();
7821
7871
  const k = key ?? globalKey;
7822
- const keyId = keyIdOf(k);
7823
- if (await checkLicense(k, host)) {
7824
- writeGate({ host, keyId, paid: true });
7872
+ const state = await keyState(k, host);
7873
+ if (state === "ok") {
7874
+ const id = keyIdOf(k);
7875
+ updateGate(host, (c) => ({ ...c, verifiedKeys: [id, ...c.verifiedKeys.filter((x) => x !== id)].slice(0, MAX_VERIFIED_KEYS) }));
7825
7876
  return true;
7826
7877
  }
7878
+ if (state === "unsupported") return true;
7827
7879
  const trial = (await fetchDomainStatus(host))?.trial;
7828
7880
  if (!trial) return true;
7829
- writeGate({ host, keyId, paid: trial.active, trialEnds: trial.ends });
7881
+ updateGate(host, (c) => ({ ...c, trialEnded: !trial.active, trialEnds: trial.ends }));
7830
7882
  const notice = `${trial.active ? "trial" : "ended"}@${host}`;
7831
7883
  if (!gateNotices.has(notice)) {
7832
7884
  gateNotices.add(notice);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@freegridjs/core",
3
- "version": "1.9.0",
3
+ "version": "1.9.1",
4
4
  "description": "FreeGrid — a fast data grid for the web: virtual scrolling, grouping, pivot, charts, Excel export, 7 languages. Free core; paid features free with a personal key.",
5
5
  "license": "SEE LICENSE IN LICENSE",
6
6
  "type": "module",
@@ -48,7 +48,7 @@
48
48
  "unpkg": "./dist/cdn/freegrid.min.js",
49
49
  "jsdelivr": "./dist/cdn/freegrid.min.js",
50
50
  "scripts": {
51
- "build": "tsup && node scripts/copy-css.mjs",
51
+ "build": "node scripts/clean-dist.mjs && tsup && node scripts/copy-css.mjs",
52
52
  "test": "vitest run",
53
53
  "typecheck": "tsc -p tsconfig.json"
54
54
  }