canvas-globe 0.1.6 → 1.0.0-beta.2
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/CHANGELOG.md +15 -0
- package/LICENSE.md +251 -0
- package/LICENSING.md +26 -66
- package/README.md +18 -20
- package/THIRD_PARTY_NOTICES.md +1 -1
- package/codemeta.json +2 -2
- package/dist/canvas-globe.umd.js +164 -23
- package/package.json +5 -3
- package/src/geo-globe.js +72 -4
- package/src/index.js +9 -1
- package/src/license.js +91 -18
- package/src/version.js +2 -0
- package/types/index.d.ts +17 -5
- package/LICENSE +0 -674
package/dist/canvas-globe.umd.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/*! canvas-globe | Copyright (C) 2026 Harsh Jhunjhunuwala |
|
|
1
|
+
/*! canvas-globe | Copyright (C) 2026 Harsh Jhunjhunuwala | Proprietary commercial software | https://github.com/Shree-hari/canvas-globe */
|
|
2
2
|
(function (root, factory) {
|
|
3
3
|
if (typeof exports === "object" && typeof module !== "undefined") module.exports = factory();
|
|
4
4
|
else if (typeof define === "function" && define.amd) define(factory);
|
|
@@ -1313,39 +1313,115 @@ function drawFitted(ctx, media, box) {
|
|
|
1313
1313
|
return true;
|
|
1314
1314
|
}
|
|
1315
1315
|
|
|
1316
|
-
|
|
1316
|
+
// Keep in sync with package.json. Release checks enforce this value.
|
|
1317
|
+
const CANVAS_GLOBE_VERSION = "1.0.0-beta.2";
|
|
1318
|
+
|
|
1319
|
+
/** Local license-key checks and production-use presentation helpers. */
|
|
1320
|
+
|
|
1317
1321
|
const DEFAULT_LICENSE_KEY = "0000-0000-000-0000";
|
|
1322
|
+
const LICENSE_KEY_PREFIX = "GLO";
|
|
1323
|
+
const LICENSE_PAGE_URL =
|
|
1324
|
+
"https://canvasglobe.swiftools.com/pricing?utm_source=canvas-globe&utm_medium=runtime-notice";
|
|
1325
|
+
|
|
1326
|
+
// This remains false while the latest published line is GPLv3. The commercial
|
|
1327
|
+
// release checklist requires an intentional switch after the agreement and
|
|
1328
|
+
// website copy have been approved and deployed.
|
|
1329
|
+
const COMMERCIAL_LICENSE_MODE = true;
|
|
1330
|
+
|
|
1331
|
+
const PRIVATE_HOST_PATTERNS = [
|
|
1332
|
+
/^localhost$/,
|
|
1333
|
+
/\.localhost$/,
|
|
1334
|
+
/\.local$/,
|
|
1335
|
+
/\.test$/,
|
|
1336
|
+
/^127(?:\.\d{1,3}){3}$/,
|
|
1337
|
+
/^10(?:\.\d{1,3}){3}$/,
|
|
1338
|
+
/^192\.168(?:\.\d{1,3}){2}$/,
|
|
1339
|
+
/^172\.(?:1[6-9]|2\d|3[01])(?:\.\d{1,3}){2}$/,
|
|
1340
|
+
/^::1$/,
|
|
1341
|
+
];
|
|
1342
|
+
|
|
1343
|
+
const normalizeLicenseKey = (value) =>
|
|
1344
|
+
typeof value === "string" ? value.trim() : "";
|
|
1345
|
+
|
|
1346
|
+
/** Returns development, production or unknown for a browser location. */
|
|
1347
|
+
function inspectRuntime(locationValue = globalThis.location) {
|
|
1348
|
+
if (!locationValue) return { kind: "unknown", hostname: "", public: false };
|
|
1349
|
+
const protocol = String(locationValue.protocol || "").toLowerCase();
|
|
1350
|
+
const hostname = String(locationValue.hostname || "").toLowerCase().replace(/^\[|\]$/g, "");
|
|
1351
|
+
if (!hostname || (protocol && protocol !== "http:" && protocol !== "https:")) {
|
|
1352
|
+
return { kind: "unknown", hostname, public: false };
|
|
1353
|
+
}
|
|
1354
|
+
const local = PRIVATE_HOST_PATTERNS.some((pattern) => pattern.test(hostname));
|
|
1355
|
+
return { kind: local ? "development" : "production", hostname, public: !local };
|
|
1356
|
+
}
|
|
1318
1357
|
|
|
1319
|
-
/**
|
|
1320
|
-
function inspectLicenseKey(value) {
|
|
1321
|
-
const key =
|
|
1358
|
+
/** Checks a license key locally. Commercial keys begin with GLO. */
|
|
1359
|
+
function inspectLicenseKey(value, mode = COMMERCIAL_LICENSE_MODE) {
|
|
1360
|
+
const key = normalizeLicenseKey(value);
|
|
1322
1361
|
if (!key) return { valid: false, kind: "missing", key: "" };
|
|
1323
|
-
if (key === DEFAULT_LICENSE_KEY) {
|
|
1324
|
-
|
|
1362
|
+
if (key === DEFAULT_LICENSE_KEY) return { valid: false, kind: "placeholder", key };
|
|
1363
|
+
if (!mode) return { valid: true, kind: "provided", key };
|
|
1364
|
+
if (key.startsWith(LICENSE_KEY_PREFIX) && key.length > LICENSE_KEY_PREFIX.length) {
|
|
1365
|
+
return { valid: true, kind: "licensed", key };
|
|
1325
1366
|
}
|
|
1326
|
-
return { valid:
|
|
1367
|
+
return { valid: false, kind: "invalid", key: "" };
|
|
1368
|
+
}
|
|
1369
|
+
|
|
1370
|
+
/** Resolves the same local result for callers that already use this helper. */
|
|
1371
|
+
async function verifyLicenseKey(value, mode = COMMERCIAL_LICENSE_MODE) {
|
|
1372
|
+
return inspectLicenseKey(value, mode);
|
|
1373
|
+
}
|
|
1374
|
+
|
|
1375
|
+
/** Returns whether a valid local license key is available. */
|
|
1376
|
+
function hasLicenseKey(value, mode = COMMERCIAL_LICENSE_MODE) {
|
|
1377
|
+
return inspectLicenseKey(value, mode).valid;
|
|
1327
1378
|
}
|
|
1328
1379
|
|
|
1329
|
-
/**
|
|
1330
|
-
|
|
1331
|
-
|
|
1380
|
+
/**
|
|
1381
|
+
* Returns the notice that the commercial release displays for public use.
|
|
1382
|
+
* The mode argument exists for release tooling and tests, not as a public
|
|
1383
|
+
* option that applications can use to suppress licensing UI.
|
|
1384
|
+
*/
|
|
1385
|
+
function getLicensePresentation(
|
|
1386
|
+
value,
|
|
1387
|
+
locationValue = globalThis.location,
|
|
1388
|
+
mode = COMMERCIAL_LICENSE_MODE,
|
|
1389
|
+
) {
|
|
1390
|
+
const status = inspectLicenseKey(value, mode);
|
|
1391
|
+
const runtime = inspectRuntime(locationValue);
|
|
1392
|
+
if (!mode || status.valid || !runtime.public) {
|
|
1393
|
+
return { status, runtime, notice: null };
|
|
1394
|
+
}
|
|
1395
|
+
return {
|
|
1396
|
+
status,
|
|
1397
|
+
runtime,
|
|
1398
|
+
notice: {
|
|
1399
|
+
text: "CanvasGlobe: Purchase a license",
|
|
1400
|
+
ariaLabel: "CanvasGlobe requires a license for production use. Open licensing options.",
|
|
1401
|
+
url: LICENSE_PAGE_URL,
|
|
1402
|
+
},
|
|
1403
|
+
};
|
|
1332
1404
|
}
|
|
1333
1405
|
|
|
1334
|
-
/** Reports
|
|
1335
|
-
function reportLicenseStatus(value) {
|
|
1336
|
-
const
|
|
1337
|
-
|
|
1338
|
-
// environments. Console messaging applies when it is used in a browser.
|
|
1406
|
+
/** Reports local license-key problems in the browser console. */
|
|
1407
|
+
function reportLicenseStatus(value, mode = COMMERCIAL_LICENSE_MODE) {
|
|
1408
|
+
const presentation = getLicensePresentation(value, globalThis.location, mode);
|
|
1409
|
+
const { status } = presentation;
|
|
1339
1410
|
if (typeof location === "undefined") return status;
|
|
1340
1411
|
if (status.kind === "missing") {
|
|
1341
1412
|
console.error(
|
|
1342
|
-
|
|
1413
|
+
mode
|
|
1414
|
+
? `canvas-globe: a license is required for production use. ${LICENSE_PAGE_URL}`
|
|
1415
|
+
: "canvas-globe: please provide a valid license key. For help, email globe@swiftools.com",
|
|
1343
1416
|
);
|
|
1344
1417
|
} else if (status.kind === "placeholder") {
|
|
1345
1418
|
console.warn(
|
|
1346
|
-
|
|
1347
|
-
|
|
1419
|
+
mode
|
|
1420
|
+
? `canvas-globe: the placeholder is not a production license. ${LICENSE_PAGE_URL}`
|
|
1421
|
+
: `canvas-globe: ${DEFAULT_LICENSE_KEY} license key is not valid for production use. For help, email globe@swiftools.com`,
|
|
1348
1422
|
);
|
|
1423
|
+
} else if (mode && status.kind === "invalid") {
|
|
1424
|
+
console.error(`canvas-globe: enter the GLO license key supplied after purchase. ${LICENSE_PAGE_URL}`);
|
|
1349
1425
|
}
|
|
1350
1426
|
return status;
|
|
1351
1427
|
}
|
|
@@ -1366,7 +1442,7 @@ function reportLicenseStatus(value) {
|
|
|
1366
1442
|
|
|
1367
1443
|
|
|
1368
1444
|
const DEFAULTS = {
|
|
1369
|
-
licenseKey:
|
|
1445
|
+
licenseKey: null,
|
|
1370
1446
|
mode: "globe",
|
|
1371
1447
|
projection: "equirectangular",
|
|
1372
1448
|
theme: "atlas",
|
|
@@ -1486,6 +1562,8 @@ class GeoGlobe {
|
|
|
1486
1562
|
this._media = new Map();
|
|
1487
1563
|
this._markerMedia = new Map();
|
|
1488
1564
|
this._counterShown = null;
|
|
1565
|
+
this._licenseHit = null;
|
|
1566
|
+
this._licenseHovered = false;
|
|
1489
1567
|
|
|
1490
1568
|
this._applyWorld();
|
|
1491
1569
|
this._applyMarkers(this.o.markers);
|
|
@@ -1517,7 +1595,9 @@ class GeoGlobe {
|
|
|
1517
1595
|
setOptions(patch = {}) {
|
|
1518
1596
|
patch = this._expandLooks(patch);
|
|
1519
1597
|
Object.assign(this.o, patch);
|
|
1520
|
-
if ("licenseKey" in patch)
|
|
1598
|
+
if ("licenseKey" in patch) {
|
|
1599
|
+
reportLicenseStatus(patch.licenseKey);
|
|
1600
|
+
}
|
|
1521
1601
|
if ("world" in patch) this._applyWorld();
|
|
1522
1602
|
if ("markers" in patch) this._applyMarkers(patch.markers || []);
|
|
1523
1603
|
if ("zoom" in patch) this._zoom = clamp(patch.zoom, this.o.minZoom, this.o.maxZoom);
|
|
@@ -2058,6 +2138,7 @@ class GeoGlobe {
|
|
|
2058
2138
|
this.hits = this.o.mode === "map" ? this._paintMap(w, h) : this._paintGlobe(w, h);
|
|
2059
2139
|
this._dirty = false;
|
|
2060
2140
|
this.o.onRender?.(this);
|
|
2141
|
+
this._paintLicenseNotice(w, h);
|
|
2061
2142
|
return this;
|
|
2062
2143
|
}
|
|
2063
2144
|
|
|
@@ -2090,6 +2171,7 @@ class GeoGlobe {
|
|
|
2090
2171
|
this._markerMedia.clear();
|
|
2091
2172
|
this._tip = null;
|
|
2092
2173
|
this._live = null;
|
|
2174
|
+
this._licenseHit = null;
|
|
2093
2175
|
return this;
|
|
2094
2176
|
}
|
|
2095
2177
|
|
|
@@ -2469,7 +2551,7 @@ class GeoGlobe {
|
|
|
2469
2551
|
c.style.cursor = "default";
|
|
2470
2552
|
return;
|
|
2471
2553
|
}
|
|
2472
|
-
c.style.cursor = this._hovered || this._hoveredCountry ? "pointer" : this._drag ? "grabbing" : "grab";
|
|
2554
|
+
c.style.cursor = this._licenseHovered || this._hovered || this._hoveredCountry ? "pointer" : this._drag ? "grabbing" : "grab";
|
|
2473
2555
|
}
|
|
2474
2556
|
|
|
2475
2557
|
_local(e) {
|
|
@@ -2485,6 +2567,11 @@ class GeoGlobe {
|
|
|
2485
2567
|
return null;
|
|
2486
2568
|
}
|
|
2487
2569
|
|
|
2570
|
+
_licenseHitAt(x, y) {
|
|
2571
|
+
const hit = this._licenseHit;
|
|
2572
|
+
return Boolean(hit && x >= hit.x && x <= hit.x + hit.w && y >= hit.y && y <= hit.y + hit.h);
|
|
2573
|
+
}
|
|
2574
|
+
|
|
2488
2575
|
_bind() {
|
|
2489
2576
|
const c = this.canvas;
|
|
2490
2577
|
|
|
@@ -2508,6 +2595,11 @@ class GeoGlobe {
|
|
|
2508
2595
|
this._onMove = (e) => {
|
|
2509
2596
|
const [x, y] = this._local(e);
|
|
2510
2597
|
this._pointer = { x: e.clientX, y: e.clientY };
|
|
2598
|
+
const licenseHovered = this._licenseHitAt(x, y);
|
|
2599
|
+
if (licenseHovered !== this._licenseHovered) {
|
|
2600
|
+
this._licenseHovered = licenseHovered;
|
|
2601
|
+
this._cursor();
|
|
2602
|
+
}
|
|
2511
2603
|
if (this._pointers.has(e.pointerId)) this._pointers.set(e.pointerId, [x, y]);
|
|
2512
2604
|
|
|
2513
2605
|
if (this._pinch && this._pointers.size === 2) {
|
|
@@ -2573,6 +2665,7 @@ class GeoGlobe {
|
|
|
2573
2665
|
this._onLeave = (e) => {
|
|
2574
2666
|
this._onUp(e);
|
|
2575
2667
|
this._pointer = null;
|
|
2668
|
+
this._licenseHovered = false;
|
|
2576
2669
|
if (this._hovered || this._hoveredCountry) {
|
|
2577
2670
|
if (this._hovered) this.o.onHover?.(null, null);
|
|
2578
2671
|
if (this._hoveredCountry) this.o.onCountryHover?.(null, null);
|
|
@@ -2587,6 +2680,11 @@ class GeoGlobe {
|
|
|
2587
2680
|
this._onClick = (e) => {
|
|
2588
2681
|
if (this._dragMoved) return;
|
|
2589
2682
|
const [x, y] = this._local(e);
|
|
2683
|
+
if (this._licenseHitAt(x, y)) {
|
|
2684
|
+
const opened = globalThis.open?.(this._licenseHit.url, "_blank", "noopener,noreferrer");
|
|
2685
|
+
if (opened) opened.opener = null;
|
|
2686
|
+
return;
|
|
2687
|
+
}
|
|
2590
2688
|
const hit = this._hitAt(x, y);
|
|
2591
2689
|
if (hit) {
|
|
2592
2690
|
this.o.onClick?.(hit.marker, { x: hit.x, y: hit.y });
|
|
@@ -3339,6 +3437,49 @@ class GeoGlobe {
|
|
|
3339
3437
|
ctx.restore();
|
|
3340
3438
|
}
|
|
3341
3439
|
|
|
3440
|
+
/** Licensing notice for public production use of the commercial edition. */
|
|
3441
|
+
_paintLicenseNotice(w, h) {
|
|
3442
|
+
const presentation = getLicensePresentation(this.o.licenseKey);
|
|
3443
|
+
const notice = presentation.notice;
|
|
3444
|
+
this._licenseHit = null;
|
|
3445
|
+
this.canvas.removeAttribute?.("data-canvas-globe-license-notice");
|
|
3446
|
+
if (!notice) return;
|
|
3447
|
+
|
|
3448
|
+
const { ctx } = this;
|
|
3449
|
+
const fontSize = Math.max(10, Math.min(13, Math.round(Math.min(w, h) * 0.03)));
|
|
3450
|
+
const horizontal = 10;
|
|
3451
|
+
const vertical = 7;
|
|
3452
|
+
const margin = Math.max(8, Math.round(Math.min(w, h) * 0.025));
|
|
3453
|
+
|
|
3454
|
+
ctx.save();
|
|
3455
|
+
ctx.font = `600 ${fontSize}px Inter,system-ui,sans-serif`;
|
|
3456
|
+
const textWidth = ctx.measureText(notice.text).width;
|
|
3457
|
+
const boxWidth = Math.min(w - margin * 2, textWidth + horizontal * 2);
|
|
3458
|
+
const boxHeight = fontSize + vertical * 2;
|
|
3459
|
+
const x = Math.max(margin, w - boxWidth - margin);
|
|
3460
|
+
const y = Math.max(margin, h - boxHeight - margin);
|
|
3461
|
+
|
|
3462
|
+
ctx.globalAlpha = 0.94;
|
|
3463
|
+
ctx.fillStyle = "rgba(9, 18, 35, 0.92)";
|
|
3464
|
+
ctx.strokeStyle = "rgba(255, 255, 255, 0.3)";
|
|
3465
|
+
ctx.lineWidth = 1;
|
|
3466
|
+
ctx.beginPath();
|
|
3467
|
+
if (typeof ctx.roundRect === "function") ctx.roundRect(x, y, boxWidth, boxHeight, 6);
|
|
3468
|
+
else ctx.rect(x, y, boxWidth, boxHeight);
|
|
3469
|
+
ctx.fill();
|
|
3470
|
+
ctx.stroke();
|
|
3471
|
+
|
|
3472
|
+
ctx.globalAlpha = 1;
|
|
3473
|
+
ctx.fillStyle = "#ffffff";
|
|
3474
|
+
ctx.textAlign = "center";
|
|
3475
|
+
ctx.textBaseline = "middle";
|
|
3476
|
+
ctx.fillText(notice.text, x + boxWidth / 2, y + boxHeight / 2, boxWidth - horizontal * 2);
|
|
3477
|
+
ctx.restore();
|
|
3478
|
+
|
|
3479
|
+
this._licenseHit = { x, y, w: boxWidth, h: boxHeight, url: notice.url };
|
|
3480
|
+
this.canvas.setAttribute?.("data-canvas-globe-license-notice", "visible");
|
|
3481
|
+
}
|
|
3482
|
+
|
|
3342
3483
|
_paintCountries(t, trace, textured) {
|
|
3343
3484
|
if (this.o.landStyle === "dots" || this.o.landStyle === "none") return;
|
|
3344
3485
|
const { ctx } = this;
|
|
@@ -4349,5 +4490,5 @@ function defineGeoGlobe(tag = "geo-globe") {
|
|
|
4349
4490
|
defineGeoGlobe();
|
|
4350
4491
|
|
|
4351
4492
|
const CanvasGlobe = GeoGlobe; const createCanvasGlobe = createGlobe;
|
|
4352
|
-
return { GeoGlobe, CanvasGlobe, createGlobe, createCanvasGlobe, GeoGlobeElement, defineGeoGlobe, themes, presets, scenes, countryPalette, exportPresets, exportSize, fromCSV, fromRows, parseCSV, geocode, countryPoint, locateViewer, locateViewerPrecise, timeZoneLocation, countryLocation, placeLocation, recordCanvas, downloadBlob, canRecord, supportedRecordingType, SphereTexture, Media, mapAspect, colorScale, subsolarPoint, greatCircle, angularDistance, pointInGeometry, geometryBounds, projections, world, DEFAULT_LICENSE_KEY, inspectLicenseKey, hasLicenseKey, default: createGlobe };
|
|
4493
|
+
return { GeoGlobe, CanvasGlobe, createGlobe, createCanvasGlobe, GeoGlobeElement, defineGeoGlobe, themes, presets, scenes, countryPalette, exportPresets, exportSize, fromCSV, fromRows, parseCSV, geocode, countryPoint, locateViewer, locateViewerPrecise, timeZoneLocation, countryLocation, placeLocation, recordCanvas, downloadBlob, canRecord, supportedRecordingType, SphereTexture, Media, mapAspect, colorScale, subsolarPoint, greatCircle, angularDistance, pointInGeometry, geometryBounds, projections, world, DEFAULT_LICENSE_KEY, LICENSE_KEY_PREFIX, LICENSE_PAGE_URL, inspectRuntime, inspectLicenseKey, verifyLicenseKey, hasLicenseKey, default: createGlobe };
|
|
4353
4494
|
});
|
package/package.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "canvas-globe",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "1.0.0-beta.2",
|
|
4
4
|
"description": "Build interactive 3D globes and flat world maps in JavaScript or React with Canvas 2D, without WebGL, map tiles, API keys, or runtime network calls.",
|
|
5
|
-
"license": "
|
|
5
|
+
"license": "SEE LICENSE IN LICENSE.md",
|
|
6
6
|
"author": {
|
|
7
7
|
"name": "Harsh Jhunjhunuwala"
|
|
8
8
|
},
|
|
@@ -75,7 +75,7 @@
|
|
|
75
75
|
"README.md",
|
|
76
76
|
"CHANGELOG.md",
|
|
77
77
|
"COPYRIGHT",
|
|
78
|
-
"LICENSE",
|
|
78
|
+
"LICENSE.md",
|
|
79
79
|
"LICENSING.md",
|
|
80
80
|
"THIRD_PARTY_NOTICES.md",
|
|
81
81
|
"codemeta.json",
|
|
@@ -101,6 +101,8 @@
|
|
|
101
101
|
"typecheck": "tsc --noEmit -p tsconfig.json",
|
|
102
102
|
"typecheck:ecosystem": "tsc --noEmit -p tsconfig.ecosystem.json",
|
|
103
103
|
"smoke:package": "node scripts/package-smoke.mjs",
|
|
104
|
+
"commercial:check": "node scripts/commercial-release-check.mjs",
|
|
105
|
+
"commercial:prepare": "node scripts/prepare-commercial-release.mjs",
|
|
104
106
|
"release:check": "npm run test && npm run typecheck && npm run typecheck:ecosystem && npm run test:ecosystem && npm run build && node scripts/release-check.mjs && npm run smoke:package",
|
|
105
107
|
"prepublishOnly": "npm run test && npm run typecheck && npm run build && node scripts/release-check.mjs"
|
|
106
108
|
},
|
package/src/geo-globe.js
CHANGED
|
@@ -11,11 +11,14 @@ import { SphereTexture } from "./texture.js";
|
|
|
11
11
|
import { Media, drawFitted } from "./media.js";
|
|
12
12
|
import { scenes, sceneKeys } from "./scenes.js";
|
|
13
13
|
import { exportSize } from "./export.js";
|
|
14
|
-
import {
|
|
14
|
+
import {
|
|
15
|
+
getLicensePresentation,
|
|
16
|
+
reportLicenseStatus,
|
|
17
|
+
} from "./license.js";
|
|
15
18
|
import { D2R, R2D, TAU, clamp, wrapLon, resolveProjection, projectionBounds, ortho, orthoInverse, greatCircle, circleAround, distanceMeters, subsolarPoint, pointInGeometry, geometryBounds, normalizeShapes, withAlpha } from "./geo.js";
|
|
16
19
|
|
|
17
20
|
const DEFAULTS = {
|
|
18
|
-
licenseKey:
|
|
21
|
+
licenseKey: null,
|
|
19
22
|
mode: "globe",
|
|
20
23
|
projection: "equirectangular",
|
|
21
24
|
theme: "atlas",
|
|
@@ -135,6 +138,8 @@ export class GeoGlobe {
|
|
|
135
138
|
this._media = new Map();
|
|
136
139
|
this._markerMedia = new Map();
|
|
137
140
|
this._counterShown = null;
|
|
141
|
+
this._licenseHit = null;
|
|
142
|
+
this._licenseHovered = false;
|
|
138
143
|
|
|
139
144
|
this._applyWorld();
|
|
140
145
|
this._applyMarkers(this.o.markers);
|
|
@@ -166,7 +171,9 @@ export class GeoGlobe {
|
|
|
166
171
|
setOptions(patch = {}) {
|
|
167
172
|
patch = this._expandLooks(patch);
|
|
168
173
|
Object.assign(this.o, patch);
|
|
169
|
-
if ("licenseKey" in patch)
|
|
174
|
+
if ("licenseKey" in patch) {
|
|
175
|
+
reportLicenseStatus(patch.licenseKey);
|
|
176
|
+
}
|
|
170
177
|
if ("world" in patch) this._applyWorld();
|
|
171
178
|
if ("markers" in patch) this._applyMarkers(patch.markers || []);
|
|
172
179
|
if ("zoom" in patch) this._zoom = clamp(patch.zoom, this.o.minZoom, this.o.maxZoom);
|
|
@@ -707,6 +714,7 @@ export class GeoGlobe {
|
|
|
707
714
|
this.hits = this.o.mode === "map" ? this._paintMap(w, h) : this._paintGlobe(w, h);
|
|
708
715
|
this._dirty = false;
|
|
709
716
|
this.o.onRender?.(this);
|
|
717
|
+
this._paintLicenseNotice(w, h);
|
|
710
718
|
return this;
|
|
711
719
|
}
|
|
712
720
|
|
|
@@ -739,6 +747,7 @@ export class GeoGlobe {
|
|
|
739
747
|
this._markerMedia.clear();
|
|
740
748
|
this._tip = null;
|
|
741
749
|
this._live = null;
|
|
750
|
+
this._licenseHit = null;
|
|
742
751
|
return this;
|
|
743
752
|
}
|
|
744
753
|
|
|
@@ -1118,7 +1127,7 @@ export class GeoGlobe {
|
|
|
1118
1127
|
c.style.cursor = "default";
|
|
1119
1128
|
return;
|
|
1120
1129
|
}
|
|
1121
|
-
c.style.cursor = this._hovered || this._hoveredCountry ? "pointer" : this._drag ? "grabbing" : "grab";
|
|
1130
|
+
c.style.cursor = this._licenseHovered || this._hovered || this._hoveredCountry ? "pointer" : this._drag ? "grabbing" : "grab";
|
|
1122
1131
|
}
|
|
1123
1132
|
|
|
1124
1133
|
_local(e) {
|
|
@@ -1134,6 +1143,11 @@ export class GeoGlobe {
|
|
|
1134
1143
|
return null;
|
|
1135
1144
|
}
|
|
1136
1145
|
|
|
1146
|
+
_licenseHitAt(x, y) {
|
|
1147
|
+
const hit = this._licenseHit;
|
|
1148
|
+
return Boolean(hit && x >= hit.x && x <= hit.x + hit.w && y >= hit.y && y <= hit.y + hit.h);
|
|
1149
|
+
}
|
|
1150
|
+
|
|
1137
1151
|
_bind() {
|
|
1138
1152
|
const c = this.canvas;
|
|
1139
1153
|
|
|
@@ -1157,6 +1171,11 @@ export class GeoGlobe {
|
|
|
1157
1171
|
this._onMove = (e) => {
|
|
1158
1172
|
const [x, y] = this._local(e);
|
|
1159
1173
|
this._pointer = { x: e.clientX, y: e.clientY };
|
|
1174
|
+
const licenseHovered = this._licenseHitAt(x, y);
|
|
1175
|
+
if (licenseHovered !== this._licenseHovered) {
|
|
1176
|
+
this._licenseHovered = licenseHovered;
|
|
1177
|
+
this._cursor();
|
|
1178
|
+
}
|
|
1160
1179
|
if (this._pointers.has(e.pointerId)) this._pointers.set(e.pointerId, [x, y]);
|
|
1161
1180
|
|
|
1162
1181
|
if (this._pinch && this._pointers.size === 2) {
|
|
@@ -1222,6 +1241,7 @@ export class GeoGlobe {
|
|
|
1222
1241
|
this._onLeave = (e) => {
|
|
1223
1242
|
this._onUp(e);
|
|
1224
1243
|
this._pointer = null;
|
|
1244
|
+
this._licenseHovered = false;
|
|
1225
1245
|
if (this._hovered || this._hoveredCountry) {
|
|
1226
1246
|
if (this._hovered) this.o.onHover?.(null, null);
|
|
1227
1247
|
if (this._hoveredCountry) this.o.onCountryHover?.(null, null);
|
|
@@ -1236,6 +1256,11 @@ export class GeoGlobe {
|
|
|
1236
1256
|
this._onClick = (e) => {
|
|
1237
1257
|
if (this._dragMoved) return;
|
|
1238
1258
|
const [x, y] = this._local(e);
|
|
1259
|
+
if (this._licenseHitAt(x, y)) {
|
|
1260
|
+
const opened = globalThis.open?.(this._licenseHit.url, "_blank", "noopener,noreferrer");
|
|
1261
|
+
if (opened) opened.opener = null;
|
|
1262
|
+
return;
|
|
1263
|
+
}
|
|
1239
1264
|
const hit = this._hitAt(x, y);
|
|
1240
1265
|
if (hit) {
|
|
1241
1266
|
this.o.onClick?.(hit.marker, { x: hit.x, y: hit.y });
|
|
@@ -1988,6 +2013,49 @@ export class GeoGlobe {
|
|
|
1988
2013
|
ctx.restore();
|
|
1989
2014
|
}
|
|
1990
2015
|
|
|
2016
|
+
/** Licensing notice for public production use of the commercial edition. */
|
|
2017
|
+
_paintLicenseNotice(w, h) {
|
|
2018
|
+
const presentation = getLicensePresentation(this.o.licenseKey);
|
|
2019
|
+
const notice = presentation.notice;
|
|
2020
|
+
this._licenseHit = null;
|
|
2021
|
+
this.canvas.removeAttribute?.("data-canvas-globe-license-notice");
|
|
2022
|
+
if (!notice) return;
|
|
2023
|
+
|
|
2024
|
+
const { ctx } = this;
|
|
2025
|
+
const fontSize = Math.max(10, Math.min(13, Math.round(Math.min(w, h) * 0.03)));
|
|
2026
|
+
const horizontal = 10;
|
|
2027
|
+
const vertical = 7;
|
|
2028
|
+
const margin = Math.max(8, Math.round(Math.min(w, h) * 0.025));
|
|
2029
|
+
|
|
2030
|
+
ctx.save();
|
|
2031
|
+
ctx.font = `600 ${fontSize}px Inter,system-ui,sans-serif`;
|
|
2032
|
+
const textWidth = ctx.measureText(notice.text).width;
|
|
2033
|
+
const boxWidth = Math.min(w - margin * 2, textWidth + horizontal * 2);
|
|
2034
|
+
const boxHeight = fontSize + vertical * 2;
|
|
2035
|
+
const x = Math.max(margin, w - boxWidth - margin);
|
|
2036
|
+
const y = Math.max(margin, h - boxHeight - margin);
|
|
2037
|
+
|
|
2038
|
+
ctx.globalAlpha = 0.94;
|
|
2039
|
+
ctx.fillStyle = "rgba(9, 18, 35, 0.92)";
|
|
2040
|
+
ctx.strokeStyle = "rgba(255, 255, 255, 0.3)";
|
|
2041
|
+
ctx.lineWidth = 1;
|
|
2042
|
+
ctx.beginPath();
|
|
2043
|
+
if (typeof ctx.roundRect === "function") ctx.roundRect(x, y, boxWidth, boxHeight, 6);
|
|
2044
|
+
else ctx.rect(x, y, boxWidth, boxHeight);
|
|
2045
|
+
ctx.fill();
|
|
2046
|
+
ctx.stroke();
|
|
2047
|
+
|
|
2048
|
+
ctx.globalAlpha = 1;
|
|
2049
|
+
ctx.fillStyle = "#ffffff";
|
|
2050
|
+
ctx.textAlign = "center";
|
|
2051
|
+
ctx.textBaseline = "middle";
|
|
2052
|
+
ctx.fillText(notice.text, x + boxWidth / 2, y + boxHeight / 2, boxWidth - horizontal * 2);
|
|
2053
|
+
ctx.restore();
|
|
2054
|
+
|
|
2055
|
+
this._licenseHit = { x, y, w: boxWidth, h: boxHeight, url: notice.url };
|
|
2056
|
+
this.canvas.setAttribute?.("data-canvas-globe-license-notice", "visible");
|
|
2057
|
+
}
|
|
2058
|
+
|
|
1991
2059
|
_paintCountries(t, trace, textured) {
|
|
1992
2060
|
if (this.o.landStyle === "dots" || this.o.landStyle === "none") return;
|
|
1993
2061
|
const { ctx } = this;
|
package/src/index.js
CHANGED
|
@@ -16,6 +16,14 @@ export { locateViewer, locateViewerPrecise, timeZoneLocation, countryLocation, p
|
|
|
16
16
|
export { recordCanvas, downloadBlob, canRecord, supportedRecordingType } from "./recorder.js";
|
|
17
17
|
export { SphereTexture } from "./texture.js";
|
|
18
18
|
export { Media, drawFitted } from "./media.js";
|
|
19
|
-
export {
|
|
19
|
+
export {
|
|
20
|
+
DEFAULT_LICENSE_KEY,
|
|
21
|
+
LICENSE_KEY_PREFIX,
|
|
22
|
+
LICENSE_PAGE_URL,
|
|
23
|
+
inspectRuntime,
|
|
24
|
+
inspectLicenseKey,
|
|
25
|
+
verifyLicenseKey,
|
|
26
|
+
hasLicenseKey,
|
|
27
|
+
} from "./license.js";
|
|
20
28
|
export { mapAspect, colorScale, subsolarPoint, greatCircle, angularDistance, pointInGeometry, geometryBounds, projections } from "./geo.js";
|
|
21
29
|
export { default } from "./geo-globe.js";
|
package/src/license.js
CHANGED
|
@@ -1,36 +1,109 @@
|
|
|
1
|
-
/**
|
|
1
|
+
/** Local license-key checks and production-use presentation helpers. */
|
|
2
|
+
|
|
2
3
|
export const DEFAULT_LICENSE_KEY = "0000-0000-000-0000";
|
|
4
|
+
export const LICENSE_KEY_PREFIX = "GLO";
|
|
5
|
+
export const LICENSE_PAGE_URL =
|
|
6
|
+
"https://canvasglobe.swiftools.com/pricing?utm_source=canvas-globe&utm_medium=runtime-notice";
|
|
7
|
+
|
|
8
|
+
// This remains false while the latest published line is GPLv3. The commercial
|
|
9
|
+
// release checklist requires an intentional switch after the agreement and
|
|
10
|
+
// website copy have been approved and deployed.
|
|
11
|
+
export const COMMERCIAL_LICENSE_MODE = true;
|
|
12
|
+
|
|
13
|
+
const PRIVATE_HOST_PATTERNS = [
|
|
14
|
+
/^localhost$/,
|
|
15
|
+
/\.localhost$/,
|
|
16
|
+
/\.local$/,
|
|
17
|
+
/\.test$/,
|
|
18
|
+
/^127(?:\.\d{1,3}){3}$/,
|
|
19
|
+
/^10(?:\.\d{1,3}){3}$/,
|
|
20
|
+
/^192\.168(?:\.\d{1,3}){2}$/,
|
|
21
|
+
/^172\.(?:1[6-9]|2\d|3[01])(?:\.\d{1,3}){2}$/,
|
|
22
|
+
/^::1$/,
|
|
23
|
+
];
|
|
24
|
+
|
|
25
|
+
const normalizeLicenseKey = (value) =>
|
|
26
|
+
typeof value === "string" ? value.trim() : "";
|
|
27
|
+
|
|
28
|
+
/** Returns development, production or unknown for a browser location. */
|
|
29
|
+
export function inspectRuntime(locationValue = globalThis.location) {
|
|
30
|
+
if (!locationValue) return { kind: "unknown", hostname: "", public: false };
|
|
31
|
+
const protocol = String(locationValue.protocol || "").toLowerCase();
|
|
32
|
+
const hostname = String(locationValue.hostname || "").toLowerCase().replace(/^\[|\]$/g, "");
|
|
33
|
+
if (!hostname || (protocol && protocol !== "http:" && protocol !== "https:")) {
|
|
34
|
+
return { kind: "unknown", hostname, public: false };
|
|
35
|
+
}
|
|
36
|
+
const local = PRIVATE_HOST_PATTERNS.some((pattern) => pattern.test(hostname));
|
|
37
|
+
return { kind: local ? "development" : "production", hostname, public: !local };
|
|
38
|
+
}
|
|
3
39
|
|
|
4
|
-
/**
|
|
5
|
-
export function inspectLicenseKey(value) {
|
|
6
|
-
const key =
|
|
40
|
+
/** Checks a license key locally. Commercial keys begin with GLO. */
|
|
41
|
+
export function inspectLicenseKey(value, mode = COMMERCIAL_LICENSE_MODE) {
|
|
42
|
+
const key = normalizeLicenseKey(value);
|
|
7
43
|
if (!key) return { valid: false, kind: "missing", key: "" };
|
|
8
|
-
if (key === DEFAULT_LICENSE_KEY) {
|
|
9
|
-
|
|
44
|
+
if (key === DEFAULT_LICENSE_KEY) return { valid: false, kind: "placeholder", key };
|
|
45
|
+
if (!mode) return { valid: true, kind: "provided", key };
|
|
46
|
+
if (key.startsWith(LICENSE_KEY_PREFIX) && key.length > LICENSE_KEY_PREFIX.length) {
|
|
47
|
+
return { valid: true, kind: "licensed", key };
|
|
10
48
|
}
|
|
11
|
-
return { valid:
|
|
49
|
+
return { valid: false, kind: "invalid", key: "" };
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/** Resolves the same local result for callers that already use this helper. */
|
|
53
|
+
export async function verifyLicenseKey(value, mode = COMMERCIAL_LICENSE_MODE) {
|
|
54
|
+
return inspectLicenseKey(value, mode);
|
|
12
55
|
}
|
|
13
56
|
|
|
14
|
-
/** Returns whether a
|
|
15
|
-
export function hasLicenseKey(value) {
|
|
16
|
-
return inspectLicenseKey(value).valid;
|
|
57
|
+
/** Returns whether a valid local license key is available. */
|
|
58
|
+
export function hasLicenseKey(value, mode = COMMERCIAL_LICENSE_MODE) {
|
|
59
|
+
return inspectLicenseKey(value, mode).valid;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Returns the notice that the commercial release displays for public use.
|
|
64
|
+
* The mode argument exists for release tooling and tests, not as a public
|
|
65
|
+
* option that applications can use to suppress licensing UI.
|
|
66
|
+
*/
|
|
67
|
+
export function getLicensePresentation(
|
|
68
|
+
value,
|
|
69
|
+
locationValue = globalThis.location,
|
|
70
|
+
mode = COMMERCIAL_LICENSE_MODE,
|
|
71
|
+
) {
|
|
72
|
+
const status = inspectLicenseKey(value, mode);
|
|
73
|
+
const runtime = inspectRuntime(locationValue);
|
|
74
|
+
if (!mode || status.valid || !runtime.public) {
|
|
75
|
+
return { status, runtime, notice: null };
|
|
76
|
+
}
|
|
77
|
+
return {
|
|
78
|
+
status,
|
|
79
|
+
runtime,
|
|
80
|
+
notice: {
|
|
81
|
+
text: "CanvasGlobe: Purchase a license",
|
|
82
|
+
ariaLabel: "CanvasGlobe requires a license for production use. Open licensing options.",
|
|
83
|
+
url: LICENSE_PAGE_URL,
|
|
84
|
+
},
|
|
85
|
+
};
|
|
17
86
|
}
|
|
18
87
|
|
|
19
|
-
/** Reports
|
|
20
|
-
export function reportLicenseStatus(value) {
|
|
21
|
-
const
|
|
22
|
-
|
|
23
|
-
// environments. Console messaging applies when it is used in a browser.
|
|
88
|
+
/** Reports local license-key problems in the browser console. */
|
|
89
|
+
export function reportLicenseStatus(value, mode = COMMERCIAL_LICENSE_MODE) {
|
|
90
|
+
const presentation = getLicensePresentation(value, globalThis.location, mode);
|
|
91
|
+
const { status } = presentation;
|
|
24
92
|
if (typeof location === "undefined") return status;
|
|
25
93
|
if (status.kind === "missing") {
|
|
26
94
|
console.error(
|
|
27
|
-
|
|
95
|
+
mode
|
|
96
|
+
? `canvas-globe: a license is required for production use. ${LICENSE_PAGE_URL}`
|
|
97
|
+
: "canvas-globe: please provide a valid license key. For help, email globe@swiftools.com",
|
|
28
98
|
);
|
|
29
99
|
} else if (status.kind === "placeholder") {
|
|
30
100
|
console.warn(
|
|
31
|
-
|
|
32
|
-
|
|
101
|
+
mode
|
|
102
|
+
? `canvas-globe: the placeholder is not a production license. ${LICENSE_PAGE_URL}`
|
|
103
|
+
: `canvas-globe: ${DEFAULT_LICENSE_KEY} license key is not valid for production use. For help, email globe@swiftools.com`,
|
|
33
104
|
);
|
|
105
|
+
} else if (mode && status.kind === "invalid") {
|
|
106
|
+
console.error(`canvas-globe: enter the GLO license key supplied after purchase. ${LICENSE_PAGE_URL}`);
|
|
34
107
|
}
|
|
35
108
|
return status;
|
|
36
109
|
}
|
package/src/version.js
ADDED
package/types/index.d.ts
CHANGED
|
@@ -381,10 +381,7 @@ export interface RenderMarkerContext {
|
|
|
381
381
|
export type TooltipKind = "marker" | "cluster" | "country";
|
|
382
382
|
|
|
383
383
|
export interface GeoGlobeOptions {
|
|
384
|
-
/**
|
|
385
|
-
* License key supplied for a GPLv3-compatible project or with a commercial
|
|
386
|
-
* order. Never sent over the network and never used to disable features.
|
|
387
|
-
*/
|
|
384
|
+
/** Commercial license key supplied after purchase. Keys begin with `GLO`. */
|
|
388
385
|
licenseKey?: string | null;
|
|
389
386
|
/** "globe" (orthographic, spinnable) or "map" (flat). Default "globe". */
|
|
390
387
|
mode?: "globe" | "map";
|
|
@@ -609,13 +606,28 @@ export { GeoGlobe as CanvasGlobe };
|
|
|
609
606
|
/** Brand-aligned alias for `createGlobe`. */
|
|
610
607
|
export { createGlobe as createCanvasGlobe };
|
|
611
608
|
export declare const DEFAULT_LICENSE_KEY: "0000-0000-000-0000";
|
|
609
|
+
/** Prefix used by CanvasGlobe commercial license keys. */
|
|
610
|
+
export declare const LICENSE_KEY_PREFIX: "GLO";
|
|
611
|
+
/** Canonical licensing page used by CanvasGlobe notices. */
|
|
612
|
+
export declare const LICENSE_PAGE_URL: string;
|
|
613
|
+
/** Classifies a browser location without making a network request. */
|
|
614
|
+
export declare function inspectRuntime(locationValue?: {
|
|
615
|
+
protocol?: string;
|
|
616
|
+
hostname?: string;
|
|
617
|
+
} | null): {
|
|
618
|
+
kind: "development" | "production" | "unknown";
|
|
619
|
+
hostname: string;
|
|
620
|
+
public: boolean;
|
|
621
|
+
};
|
|
612
622
|
export interface LicenseKeyStatus {
|
|
613
623
|
valid: boolean;
|
|
614
|
-
kind: "missing" | "placeholder" | "provided";
|
|
624
|
+
kind: "missing" | "placeholder" | "provided" | "invalid" | "licensed";
|
|
615
625
|
key: string;
|
|
616
626
|
}
|
|
617
627
|
/** Returns the configured license-key status. */
|
|
618
628
|
export declare function inspectLicenseKey(value: unknown): LicenseKeyStatus;
|
|
629
|
+
/** Resolves the same local license-key result. */
|
|
630
|
+
export declare function verifyLicenseKey(value: unknown): Promise<LicenseKeyStatus>;
|
|
619
631
|
/** Returns whether a configured license key is available. */
|
|
620
632
|
export declare function hasLicenseKey(value: unknown): boolean;
|
|
621
633
|
export declare const themes: Record<ThemeName, Theme>;
|