jssm 5.150.1 → 5.151.0

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.
@@ -98,17 +98,54 @@ const EXPORT_FORMATS = [
98
98
  { value: 'permalink', label: 'Permalink (URL)' },
99
99
  { value: 'embed', label: 'Embed snippet' },
100
100
  ];
101
+ /** Hash parameter that carries a permalink's encoded machine: `#m=<scheme><payload>`. */
102
+ const PERMALINK_HASH_KEY = 'm';
103
+ /**
104
+ * URL-safe base64 (RFC 4648 §5) of raw bytes: standard base64 with `+`→`-`,
105
+ * `/`→`_`, and trailing `=` padding stripped, so the result rides in a URL
106
+ * fragment with no further percent-encoding.
107
+ *
108
+ * @example
109
+ * bytes_to_base64url(new TextEncoder().encode("a")); // "YQ"
110
+ */
111
+ function bytes_to_base64url(bytes) {
112
+ let binary = '';
113
+ for (const byte of bytes) {
114
+ binary += String.fromCharCode(byte);
115
+ }
116
+ return btoa(binary).replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '');
117
+ }
118
+ /** DEFLATE `bytes` (raw, headerless) via the platform `CompressionStream`. */
119
+ async function deflate_raw(bytes) {
120
+ const stream = new CompressionStream('deflate-raw');
121
+ const writer = stream.writable.getWriter();
122
+ void writer.write(bytes);
123
+ void writer.close();
124
+ return new Uint8Array(await new Response(stream.readable).arrayBuffer());
125
+ }
101
126
  /**
102
127
  * A shareable URL for the given FSL: the current page URL with the source
103
- * encoded in the hash (`#fsl=...`). A page that reads the hash on load can
104
- * restore the machine. Browser-only (uses `location`), like the rest of the
105
- * toolbar.
128
+ * compressed into the hash as `#m=<scheme><payload>`. The payload is URL-safe
129
+ * base64, so no characters need percent-escaping; `<scheme>` is a single digit
130
+ * — `1` when DEFLATE made the source smaller, `0` for the raw bytes when it did
131
+ * not — so a short machine's link is never longer than its uncompressed form.
132
+ * Decode with {@link fsl_from_permalink}. Browser-only (uses `location` and the
133
+ * platform compression streams), like the rest of the toolbar.
134
+ *
135
+ * @returns The absolute page URL carrying the encoded machine in its fragment.
106
136
  *
107
137
  * @example
108
- * permalink_for("a -> b;"); // "https://host/path#fsl=a%20-%3E%20b%3B"
138
+ * await permalink_for("a -> b;"); // "https://host/path#m=0YSAtPiBiOw" (too short to gain from DEFLATE)
139
+ * await permalink_for("Off -> On -> Off; On -> Idle -> Off;"); // "https://host/path#m=1809LU9C1U_DPA5NpadZQpmdKTipMCAA"
140
+ *
141
+ * @see fsl_from_permalink
109
142
  */
110
- function permalink_for(fsl) {
111
- return `${location.href.split('#')[0]}#fsl=${encodeURIComponent(fsl)}`;
143
+ async function permalink_for(fsl) {
144
+ const utf8 = new TextEncoder().encode(fsl);
145
+ const raw = bytes_to_base64url(utf8);
146
+ const deflated = bytes_to_base64url(await deflate_raw(utf8));
147
+ const [scheme, payload] = deflated.length < raw.length ? ['1', deflated] : ['0', raw];
148
+ return `${location.href.split('#')[0]}#${PERMALINK_HASH_KEY}=${scheme}${payload}`;
112
149
  }
113
150
  /**
114
151
  * A paste-able HTML snippet that renders the given FSL from the CDN builds: an
@@ -263,7 +300,7 @@ class FslToolbar extends LitElement {
263
300
  content = await machine_to_svg_string(host.machine);
264
301
  }
265
302
  else if (format === 'permalink') {
266
- content = permalink_for(host.fsl);
303
+ content = await permalink_for(host.fsl);
267
304
  }
268
305
  else if (format === 'embed') {
269
306
  content = embed_snippet_for(host.fsl);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "jssm",
3
- "version": "5.150.1",
3
+ "version": "5.151.0",
4
4
  "engines": {
5
5
  "node": ">=10.0.0"
6
6
  },