centrifuge 5.1.1 → 5.2.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.
@@ -0,0 +1,6 @@
1
+ type ByteArray = number[] | Uint8Array;
2
+ /**
3
+ * Apply a delta byte array to a source byte array, returning the target byte array.
4
+ */
5
+ export declare function applyDelta<T extends ByteArray>(source: T, delta: T): T;
6
+ export {};
package/build/index.js CHANGED
@@ -611,6 +611,9 @@ class Subscription extends EventEmitter$1 {
611
611
  this._promiseId = 0;
612
612
  this._inflight = false;
613
613
  this._refreshTimeout = null;
614
+ this._delta = '';
615
+ this._delta_negotiated = false;
616
+ this._prevValue = null;
614
617
  this._setOptions(options);
615
618
  // @ts-ignore – we are hiding some symbols from public API autocompletion.
616
619
  if (this._centrifuge._debugEnabled) {
@@ -755,6 +758,12 @@ class Subscription extends EventEmitter$1 {
755
758
  this._offset = result.offset || 0;
756
759
  this._epoch = result.epoch || '';
757
760
  }
761
+ if (result.delta) {
762
+ this._delta_negotiated = true;
763
+ }
764
+ else {
765
+ this._delta_negotiated = false;
766
+ }
758
767
  this._setState(exports.SubscriptionState.Subscribed);
759
768
  // @ts-ignore – we are hiding some methods from public API autocompletion.
760
769
  const ctx = this._centrifuge._getSubscribeContext(this.channel, result);
@@ -894,6 +903,9 @@ class Subscription extends EventEmitter$1 {
894
903
  req.epoch = epoch;
895
904
  }
896
905
  }
906
+ if (this._delta) {
907
+ req.delta = this._delta;
908
+ }
897
909
  const cmd = { subscribe: req };
898
910
  this._inflight = true;
899
911
  // @ts-ignore – we are hiding some symbols from public API autocompletion.
@@ -957,6 +969,12 @@ class Subscription extends EventEmitter$1 {
957
969
  this._rejectPromises({ code: exports.errorCodes.subscriptionUnsubscribed, message: this.state });
958
970
  }
959
971
  _handlePublication(pub) {
972
+ if (this._delta && this._delta_negotiated) {
973
+ // @ts-ignore – we are hiding some methods from public API autocompletion.
974
+ const { newData, newPrevValue } = this._centrifuge._codec.applyDeltaIfNeeded(pub, this._prevValue);
975
+ pub.data = newData;
976
+ this._prevValue = newPrevValue;
977
+ }
960
978
  // @ts-ignore – we are hiding some methods from public API autocompletion.
961
979
  const ctx = this._centrifuge._getPublicationContext(this.channel, pub);
962
980
  this.emit('publication', ctx);
@@ -1070,6 +1088,12 @@ class Subscription extends EventEmitter$1 {
1070
1088
  if (options.joinLeave === true) {
1071
1089
  this._joinLeave = true;
1072
1090
  }
1091
+ if (options.delta) {
1092
+ if (options.delta !== 'fossil') {
1093
+ throw new Error('unsupported delta format');
1094
+ }
1095
+ this._delta = options.delta;
1096
+ }
1073
1097
  }
1074
1098
  _getOffset() {
1075
1099
  const offset = this._offset;
@@ -1777,6 +1801,167 @@ class WebtransportTransport {
1777
1801
  }
1778
1802
  }
1779
1803
 
1804
+ /*
1805
+ Copyright 2014-2024 Dmitry Chestnykh (JavaScript port)
1806
+ Copyright 2007 D. Richard Hipp (original C version)
1807
+
1808
+ Fossil SCM delta compression algorithm, this is only the applyDelta part extracted
1809
+ from https://github.com/dchest/fossil-delta-js. The code was slightly modified
1810
+ to strip unnecessary parts. The copyright on top of this file is from the original
1811
+ repo on Github licensed under Simplified BSD License.
1812
+ */
1813
+ const zValue = [
1814
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
1815
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
1816
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, -1, -1,
1817
+ -1, -1, -1, -1, -1, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23,
1818
+ 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, -1, -1, -1, -1, 36, -1, 37,
1819
+ 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56,
1820
+ 57, 58, 59, 60, 61, 62, -1, -1, -1, 63, -1,
1821
+ ];
1822
+ // Reader reads bytes, chars, ints from array.
1823
+ class Reader {
1824
+ constructor(array) {
1825
+ this.a = array; // source array
1826
+ this.pos = 0; // current position in array
1827
+ }
1828
+ haveBytes() {
1829
+ return this.pos < this.a.length;
1830
+ }
1831
+ getByte() {
1832
+ const b = this.a[this.pos];
1833
+ this.pos++;
1834
+ if (this.pos > this.a.length)
1835
+ throw new RangeError("out of bounds");
1836
+ return b;
1837
+ }
1838
+ getChar() {
1839
+ return String.fromCharCode(this.getByte());
1840
+ }
1841
+ // Read base64-encoded unsigned integer.
1842
+ getInt() {
1843
+ let v = 0;
1844
+ let c;
1845
+ while (this.haveBytes() && (c = zValue[0x7f & this.getByte()]) >= 0) {
1846
+ v = (v << 6) + c;
1847
+ }
1848
+ this.pos--;
1849
+ return v >>> 0;
1850
+ }
1851
+ }
1852
+ // Write writes an array.
1853
+ class Writer {
1854
+ constructor() {
1855
+ this.a = [];
1856
+ }
1857
+ toByteArray(sourceType) {
1858
+ if (Array.isArray(sourceType)) {
1859
+ return this.a;
1860
+ }
1861
+ return new Uint8Array(this.a);
1862
+ }
1863
+ // Copy from array at start to end.
1864
+ putArray(a, start, end) {
1865
+ this.a.push(...a.slice(start, end));
1866
+ }
1867
+ }
1868
+ // Return a 32-bit checksum of the array.
1869
+ function checksum(arr) {
1870
+ let sum0 = 0, sum1 = 0, sum2 = 0, sum3 = 0, z = 0, N = arr.length;
1871
+ //TODO measure if this unrolling is helpful.
1872
+ while (N >= 16) {
1873
+ sum0 = (sum0 + arr[z + 0]) | 0;
1874
+ sum1 = (sum1 + arr[z + 1]) | 0;
1875
+ sum2 = (sum2 + arr[z + 2]) | 0;
1876
+ sum3 = (sum3 + arr[z + 3]) | 0;
1877
+ sum0 = (sum0 + arr[z + 4]) | 0;
1878
+ sum1 = (sum1 + arr[z + 5]) | 0;
1879
+ sum2 = (sum2 + arr[z + 6]) | 0;
1880
+ sum3 = (sum3 + arr[z + 7]) | 0;
1881
+ sum0 = (sum0 + arr[z + 8]) | 0;
1882
+ sum1 = (sum1 + arr[z + 9]) | 0;
1883
+ sum2 = (sum2 + arr[z + 10]) | 0;
1884
+ sum3 = (sum3 + arr[z + 11]) | 0;
1885
+ sum0 = (sum0 + arr[z + 12]) | 0;
1886
+ sum1 = (sum1 + arr[z + 13]) | 0;
1887
+ sum2 = (sum2 + arr[z + 14]) | 0;
1888
+ sum3 = (sum3 + arr[z + 15]) | 0;
1889
+ z += 16;
1890
+ N -= 16;
1891
+ }
1892
+ while (N >= 4) {
1893
+ sum0 = (sum0 + arr[z + 0]) | 0;
1894
+ sum1 = (sum1 + arr[z + 1]) | 0;
1895
+ sum2 = (sum2 + arr[z + 2]) | 0;
1896
+ sum3 = (sum3 + arr[z + 3]) | 0;
1897
+ z += 4;
1898
+ N -= 4;
1899
+ }
1900
+ sum3 = (((((sum3 + (sum2 << 8)) | 0) + (sum1 << 16)) | 0) + (sum0 << 24)) | 0;
1901
+ switch (N) {
1902
+ //@ts-ignore fallthrough is needed.
1903
+ case 3:
1904
+ sum3 = (sum3 + (arr[z + 2] << 8)) | 0; /* falls through */
1905
+ //@ts-ignore fallthrough is needed.
1906
+ case 2:
1907
+ sum3 = (sum3 + (arr[z + 1] << 16)) | 0; /* falls through */
1908
+ case 1:
1909
+ sum3 = (sum3 + (arr[z + 0] << 24)) | 0; /* falls through */
1910
+ }
1911
+ return sum3 >>> 0;
1912
+ }
1913
+ /**
1914
+ * Apply a delta byte array to a source byte array, returning the target byte array.
1915
+ */
1916
+ function applyDelta(source, delta) {
1917
+ let total = 0;
1918
+ const zDelta = new Reader(delta);
1919
+ const lenSrc = source.length;
1920
+ const lenDelta = delta.length;
1921
+ const limit = zDelta.getInt();
1922
+ if (zDelta.getChar() !== "\n")
1923
+ throw new Error("size integer not terminated by '\\n'");
1924
+ const zOut = new Writer();
1925
+ while (zDelta.haveBytes()) {
1926
+ const cnt = zDelta.getInt();
1927
+ let ofst;
1928
+ switch (zDelta.getChar()) {
1929
+ case "@":
1930
+ ofst = zDelta.getInt();
1931
+ if (zDelta.haveBytes() && zDelta.getChar() !== ",")
1932
+ throw new Error("copy command not terminated by ','");
1933
+ total += cnt;
1934
+ if (total > limit)
1935
+ throw new Error("copy exceeds output file size");
1936
+ if (ofst + cnt > lenSrc)
1937
+ throw new Error("copy extends past end of input");
1938
+ zOut.putArray(source, ofst, ofst + cnt);
1939
+ break;
1940
+ case ":":
1941
+ total += cnt;
1942
+ if (total > limit)
1943
+ throw new Error("insert command gives an output larger than predicted");
1944
+ if (cnt > lenDelta)
1945
+ throw new Error("insert count exceeds size of delta");
1946
+ zOut.putArray(zDelta.a, zDelta.pos, zDelta.pos + cnt);
1947
+ zDelta.pos += cnt;
1948
+ break;
1949
+ case ";":
1950
+ {
1951
+ const out = zOut.toByteArray(source);
1952
+ if (cnt !== checksum(out))
1953
+ throw new Error("bad checksum");
1954
+ if (total !== limit)
1955
+ throw new Error("generated size does not match predicted size");
1956
+ return out;
1957
+ }
1958
+ default:
1959
+ throw new Error("unknown delta operator");
1960
+ }
1961
+ }
1962
+ throw new Error("unterminated delta");
1963
+ }
1964
+
1780
1965
  /** @internal */
1781
1966
  class JsonCodec {
1782
1967
  name() {
@@ -1788,6 +1973,21 @@ class JsonCodec {
1788
1973
  decodeReplies(data) {
1789
1974
  return data.trim().split('\n').map(r => JSON.parse(r));
1790
1975
  }
1976
+ applyDeltaIfNeeded(pub, prevValue) {
1977
+ let newData, newPrevValue;
1978
+ if (pub.delta) {
1979
+ // JSON string delta.
1980
+ const valueArray = applyDelta(prevValue, new TextEncoder().encode(pub.data));
1981
+ newData = JSON.parse(new TextDecoder().decode(valueArray));
1982
+ newPrevValue = valueArray;
1983
+ }
1984
+ else {
1985
+ // Full data as JSON string.
1986
+ newData = JSON.parse(pub.data);
1987
+ newPrevValue = new TextEncoder().encode(pub.data);
1988
+ }
1989
+ return { newData, newPrevValue };
1990
+ }
1791
1991
  }
1792
1992
 
1793
1993
  const defaults = {
package/build/index.mjs CHANGED
@@ -609,6 +609,9 @@ class Subscription extends EventEmitter$1 {
609
609
  this._promiseId = 0;
610
610
  this._inflight = false;
611
611
  this._refreshTimeout = null;
612
+ this._delta = '';
613
+ this._delta_negotiated = false;
614
+ this._prevValue = null;
612
615
  this._setOptions(options);
613
616
  // @ts-ignore – we are hiding some symbols from public API autocompletion.
614
617
  if (this._centrifuge._debugEnabled) {
@@ -753,6 +756,12 @@ class Subscription extends EventEmitter$1 {
753
756
  this._offset = result.offset || 0;
754
757
  this._epoch = result.epoch || '';
755
758
  }
759
+ if (result.delta) {
760
+ this._delta_negotiated = true;
761
+ }
762
+ else {
763
+ this._delta_negotiated = false;
764
+ }
756
765
  this._setState(SubscriptionState.Subscribed);
757
766
  // @ts-ignore – we are hiding some methods from public API autocompletion.
758
767
  const ctx = this._centrifuge._getSubscribeContext(this.channel, result);
@@ -892,6 +901,9 @@ class Subscription extends EventEmitter$1 {
892
901
  req.epoch = epoch;
893
902
  }
894
903
  }
904
+ if (this._delta) {
905
+ req.delta = this._delta;
906
+ }
895
907
  const cmd = { subscribe: req };
896
908
  this._inflight = true;
897
909
  // @ts-ignore – we are hiding some symbols from public API autocompletion.
@@ -955,6 +967,12 @@ class Subscription extends EventEmitter$1 {
955
967
  this._rejectPromises({ code: errorCodes.subscriptionUnsubscribed, message: this.state });
956
968
  }
957
969
  _handlePublication(pub) {
970
+ if (this._delta && this._delta_negotiated) {
971
+ // @ts-ignore – we are hiding some methods from public API autocompletion.
972
+ const { newData, newPrevValue } = this._centrifuge._codec.applyDeltaIfNeeded(pub, this._prevValue);
973
+ pub.data = newData;
974
+ this._prevValue = newPrevValue;
975
+ }
958
976
  // @ts-ignore – we are hiding some methods from public API autocompletion.
959
977
  const ctx = this._centrifuge._getPublicationContext(this.channel, pub);
960
978
  this.emit('publication', ctx);
@@ -1068,6 +1086,12 @@ class Subscription extends EventEmitter$1 {
1068
1086
  if (options.joinLeave === true) {
1069
1087
  this._joinLeave = true;
1070
1088
  }
1089
+ if (options.delta) {
1090
+ if (options.delta !== 'fossil') {
1091
+ throw new Error('unsupported delta format');
1092
+ }
1093
+ this._delta = options.delta;
1094
+ }
1071
1095
  }
1072
1096
  _getOffset() {
1073
1097
  const offset = this._offset;
@@ -1775,6 +1799,167 @@ class WebtransportTransport {
1775
1799
  }
1776
1800
  }
1777
1801
 
1802
+ /*
1803
+ Copyright 2014-2024 Dmitry Chestnykh (JavaScript port)
1804
+ Copyright 2007 D. Richard Hipp (original C version)
1805
+
1806
+ Fossil SCM delta compression algorithm, this is only the applyDelta part extracted
1807
+ from https://github.com/dchest/fossil-delta-js. The code was slightly modified
1808
+ to strip unnecessary parts. The copyright on top of this file is from the original
1809
+ repo on Github licensed under Simplified BSD License.
1810
+ */
1811
+ const zValue = [
1812
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
1813
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
1814
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, -1, -1,
1815
+ -1, -1, -1, -1, -1, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23,
1816
+ 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, -1, -1, -1, -1, 36, -1, 37,
1817
+ 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56,
1818
+ 57, 58, 59, 60, 61, 62, -1, -1, -1, 63, -1,
1819
+ ];
1820
+ // Reader reads bytes, chars, ints from array.
1821
+ class Reader {
1822
+ constructor(array) {
1823
+ this.a = array; // source array
1824
+ this.pos = 0; // current position in array
1825
+ }
1826
+ haveBytes() {
1827
+ return this.pos < this.a.length;
1828
+ }
1829
+ getByte() {
1830
+ const b = this.a[this.pos];
1831
+ this.pos++;
1832
+ if (this.pos > this.a.length)
1833
+ throw new RangeError("out of bounds");
1834
+ return b;
1835
+ }
1836
+ getChar() {
1837
+ return String.fromCharCode(this.getByte());
1838
+ }
1839
+ // Read base64-encoded unsigned integer.
1840
+ getInt() {
1841
+ let v = 0;
1842
+ let c;
1843
+ while (this.haveBytes() && (c = zValue[0x7f & this.getByte()]) >= 0) {
1844
+ v = (v << 6) + c;
1845
+ }
1846
+ this.pos--;
1847
+ return v >>> 0;
1848
+ }
1849
+ }
1850
+ // Write writes an array.
1851
+ class Writer {
1852
+ constructor() {
1853
+ this.a = [];
1854
+ }
1855
+ toByteArray(sourceType) {
1856
+ if (Array.isArray(sourceType)) {
1857
+ return this.a;
1858
+ }
1859
+ return new Uint8Array(this.a);
1860
+ }
1861
+ // Copy from array at start to end.
1862
+ putArray(a, start, end) {
1863
+ this.a.push(...a.slice(start, end));
1864
+ }
1865
+ }
1866
+ // Return a 32-bit checksum of the array.
1867
+ function checksum(arr) {
1868
+ let sum0 = 0, sum1 = 0, sum2 = 0, sum3 = 0, z = 0, N = arr.length;
1869
+ //TODO measure if this unrolling is helpful.
1870
+ while (N >= 16) {
1871
+ sum0 = (sum0 + arr[z + 0]) | 0;
1872
+ sum1 = (sum1 + arr[z + 1]) | 0;
1873
+ sum2 = (sum2 + arr[z + 2]) | 0;
1874
+ sum3 = (sum3 + arr[z + 3]) | 0;
1875
+ sum0 = (sum0 + arr[z + 4]) | 0;
1876
+ sum1 = (sum1 + arr[z + 5]) | 0;
1877
+ sum2 = (sum2 + arr[z + 6]) | 0;
1878
+ sum3 = (sum3 + arr[z + 7]) | 0;
1879
+ sum0 = (sum0 + arr[z + 8]) | 0;
1880
+ sum1 = (sum1 + arr[z + 9]) | 0;
1881
+ sum2 = (sum2 + arr[z + 10]) | 0;
1882
+ sum3 = (sum3 + arr[z + 11]) | 0;
1883
+ sum0 = (sum0 + arr[z + 12]) | 0;
1884
+ sum1 = (sum1 + arr[z + 13]) | 0;
1885
+ sum2 = (sum2 + arr[z + 14]) | 0;
1886
+ sum3 = (sum3 + arr[z + 15]) | 0;
1887
+ z += 16;
1888
+ N -= 16;
1889
+ }
1890
+ while (N >= 4) {
1891
+ sum0 = (sum0 + arr[z + 0]) | 0;
1892
+ sum1 = (sum1 + arr[z + 1]) | 0;
1893
+ sum2 = (sum2 + arr[z + 2]) | 0;
1894
+ sum3 = (sum3 + arr[z + 3]) | 0;
1895
+ z += 4;
1896
+ N -= 4;
1897
+ }
1898
+ sum3 = (((((sum3 + (sum2 << 8)) | 0) + (sum1 << 16)) | 0) + (sum0 << 24)) | 0;
1899
+ switch (N) {
1900
+ //@ts-ignore fallthrough is needed.
1901
+ case 3:
1902
+ sum3 = (sum3 + (arr[z + 2] << 8)) | 0; /* falls through */
1903
+ //@ts-ignore fallthrough is needed.
1904
+ case 2:
1905
+ sum3 = (sum3 + (arr[z + 1] << 16)) | 0; /* falls through */
1906
+ case 1:
1907
+ sum3 = (sum3 + (arr[z + 0] << 24)) | 0; /* falls through */
1908
+ }
1909
+ return sum3 >>> 0;
1910
+ }
1911
+ /**
1912
+ * Apply a delta byte array to a source byte array, returning the target byte array.
1913
+ */
1914
+ function applyDelta(source, delta) {
1915
+ let total = 0;
1916
+ const zDelta = new Reader(delta);
1917
+ const lenSrc = source.length;
1918
+ const lenDelta = delta.length;
1919
+ const limit = zDelta.getInt();
1920
+ if (zDelta.getChar() !== "\n")
1921
+ throw new Error("size integer not terminated by '\\n'");
1922
+ const zOut = new Writer();
1923
+ while (zDelta.haveBytes()) {
1924
+ const cnt = zDelta.getInt();
1925
+ let ofst;
1926
+ switch (zDelta.getChar()) {
1927
+ case "@":
1928
+ ofst = zDelta.getInt();
1929
+ if (zDelta.haveBytes() && zDelta.getChar() !== ",")
1930
+ throw new Error("copy command not terminated by ','");
1931
+ total += cnt;
1932
+ if (total > limit)
1933
+ throw new Error("copy exceeds output file size");
1934
+ if (ofst + cnt > lenSrc)
1935
+ throw new Error("copy extends past end of input");
1936
+ zOut.putArray(source, ofst, ofst + cnt);
1937
+ break;
1938
+ case ":":
1939
+ total += cnt;
1940
+ if (total > limit)
1941
+ throw new Error("insert command gives an output larger than predicted");
1942
+ if (cnt > lenDelta)
1943
+ throw new Error("insert count exceeds size of delta");
1944
+ zOut.putArray(zDelta.a, zDelta.pos, zDelta.pos + cnt);
1945
+ zDelta.pos += cnt;
1946
+ break;
1947
+ case ";":
1948
+ {
1949
+ const out = zOut.toByteArray(source);
1950
+ if (cnt !== checksum(out))
1951
+ throw new Error("bad checksum");
1952
+ if (total !== limit)
1953
+ throw new Error("generated size does not match predicted size");
1954
+ return out;
1955
+ }
1956
+ default:
1957
+ throw new Error("unknown delta operator");
1958
+ }
1959
+ }
1960
+ throw new Error("unterminated delta");
1961
+ }
1962
+
1778
1963
  /** @internal */
1779
1964
  class JsonCodec {
1780
1965
  name() {
@@ -1786,6 +1971,21 @@ class JsonCodec {
1786
1971
  decodeReplies(data) {
1787
1972
  return data.trim().split('\n').map(r => JSON.parse(r));
1788
1973
  }
1974
+ applyDeltaIfNeeded(pub, prevValue) {
1975
+ let newData, newPrevValue;
1976
+ if (pub.delta) {
1977
+ // JSON string delta.
1978
+ const valueArray = applyDelta(prevValue, new TextEncoder().encode(pub.data));
1979
+ newData = JSON.parse(new TextDecoder().decode(valueArray));
1980
+ newPrevValue = valueArray;
1981
+ }
1982
+ else {
1983
+ // Full data as JSON string.
1984
+ newData = JSON.parse(pub.data);
1985
+ newPrevValue = new TextEncoder().encode(pub.data);
1986
+ }
1987
+ return { newData, newPrevValue };
1988
+ }
1789
1989
  }
1790
1990
 
1791
1991
  const defaults = {
@@ -0,0 +1,6 @@
1
+ type ByteArray = number[] | Uint8Array;
2
+ /**
3
+ * Apply a delta byte array to a source byte array, returning the target byte array.
4
+ */
5
+ export declare function applyDelta<T extends ByteArray>(source: T, delta: T): T;
6
+ export {};