Giter Club home page Giter Club logo

lib0's People

Contributors

aryzing avatar broofa avatar ctomacheski-evernote avatar dependabot[bot] avatar dmonad avatar dylans avatar erictheswift avatar kentomoriwaki avatar kewell-tsao avatar nikgraf avatar nilset avatar philippkuehn avatar raineorshine avatar sangdol avatar yousefed avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar

lib0's Issues

ObservableV2 does not accept interfaces

class ObservableV2<EVENTS extends {
    [key: string]: (...arg0: any[]) => void;
}> {}

rejects interfaces because interfaces don't have an index signature.
would it be possible to do this instead?

class ObservableV2<EVENTS extends {
    [key in EVENT_TYPES]: (...arg0: any[]) => void;
}, EVENT_TYPES extends keyof EVENTS = keyof EVENTS> {}

Please document that Observable.once does not respect Observable.off

Describe the bug
A clear and concise description of what the bug is.

Observable.once creates a wrapper around the function, so calling Observable.off does not prevent it from being called.

let fn = ()=>{};
item.once('update',  fn);
item.off('update',  fn);

fn, will still be invoked under this scenario.

This seems to by design, so it should be documented that off will not prevent once from being called.

Swift implementation

Just in case somebody needs a starting point for decode/encoder in Swift here is some code. Since the variable encoding of integers is one of the trickiest parts of it, these are examples of this functionality. The rest should be pretty straight forward. Maybe there is some place for it somewhere...

import Foundation

func readVarInt(bytes: [UInt8], pos: Int = 0) -> Int {
  var bytesPos = pos
  var r = bytes[bytesPos]
  var num: Int = Int(r & 0b00111111)
  var mult: Int = 64
  let sign = (r & 0b01000000) > 0 ? -1 : 1
  //  print("r=\(r) num=\(num) c=\(Int(r & 0b1)) sign=\(sign)")
  if Int(r & 0b10000000) == 0 {
    return sign * num
  }
  while true {
    bytesPos += 1
    r = bytes[bytesPos]
    num = num + Int(r & 0b01111111) * mult
    mult *= 128
    if r < 0b10000000 {
      return sign * num
    }
  }
}

func readVarUInt(bytes: [UInt8], pos: Int = 0) -> UInt {
  var bytesPos = pos
  var num: UInt = 0
  var mult: UInt = 1
  while true {
    let r = bytes[bytesPos]
    bytesPos += 1
    num = num + UInt(r & 0b01111111) * mult
    mult *= 128
    if r < 0b10000000 {
      return num
    }
  }
}

/// Just the same, but with Data as argument
func readVarUInt(data: Data, pos: Int = 0) -> UInt {
  var bytesPos = pos
  var num: UInt = 0
  var mult: UInt = 1
  while true {
    let r = data[bytesPos]
    bytesPos += 1
    num = num + UInt(r & 0b01111111) * mult
    mult *= 128
    if r < 0b10000000 {
      return num
    }
  }
}

func writeVarUInt(_ value: UInt) -> Data {
  var bytes = Data()
  var num = value
  while num > 0b01111111 {
    bytes.append(0b10000000 | (0b01111111 & UInt8(truncatingIfNeeded: num)))
    num = num / 128
  }
  bytes.append(UInt8(0b01111111 & num))
  return bytes
}

This is a unit test:

import XCTest

extension Data {
  func hexEncodedString() -> String {
    return map { String(format: "%02hhx", $0) }.joined()
  }
}

class Lib0Test: XCTestCase {
  
  func testReadVarInt() throws {
    XCTAssertEqual(10, readVarInt(bytes: [10]))
    XCTAssertEqual(128, readVarInt(bytes: [0x80, 0x02]))
    XCTAssertEqual(-1, readVarInt(bytes: [0x41]))
    XCTAssertEqual(-691529286, readVarInt(bytes: [0xc6, 0x99, 0xbf, 0x93, 0x05]))
    XCTAssertEqual(691529286, readVarInt(bytes: [0x86, 0x99, 0xbf, 0x93, 0x05]))
  }
  
  func testReadVarUInt() throws {
    XCTAssertEqual(10, readVarUInt(bytes: [10]))
    XCTAssertEqual(127, readVarUInt(bytes: [0x7f]))
    XCTAssertEqual(256, readVarUInt(bytes: [0x80, 0x02]))
    XCTAssertEqual(691529286, readVarUInt(bytes: [0xc6, 0xcc, 0xdf, 0xc9, 0x02]))
  }

  func testReadVarUIntWithData() throws {
    XCTAssertEqual(10, readVarUInt(data: Data([10])))
    XCTAssertEqual(127, readVarUInt(data: Data([0x7f])))
    XCTAssertEqual(256, readVarUInt(data: Data([0x80, 0x02])))
    XCTAssertEqual(691529286, readVarUInt(data: Data([0xc6, 0xcc, 0xdf, 0xc9, 0x02])))
  }
  
  func testWriteVarUInt() throws {
    print("Data \(writeVarUInt(10).hexEncodedString())")
    XCTAssertEqual(writeVarUInt(10).hexEncodedString(), "0a")  
    XCTAssertEqual(writeVarUInt(127).hexEncodedString(), "7f")
    XCTAssertEqual(writeVarUInt(256).hexEncodedString(), "8002")
    XCTAssertEqual(writeVarUInt(691529286).hexEncodedString(), "c6ccdfc902")
  }
  
}

Typo: 'repitition' to 'repetition'

Describe the bug
Super small issue, but I noticed a typo here:

"test-extensive": "node test.js && npm test -- --repitition-time 30000 --extensive",

--repitition-time should be --repetition-time (s/i/e)

Additional context
I considered opening a pull request to fix this, but it looks like it's used from a few dependent libraries that you have r/w access to, so it might be quicker to clean up centrally. Glad to help if I can though!

IntDiffOptRleEncoder's toUint8Array returns a different result each time.

Describe the bug
After writing the content to IntDiffOptRleEncoder (also UintOptRleEncoder, IncUintOptRleEncoder), when reading the content with toUint8Array(), the content is different each time due to flush.

Expected behavior
Since toUint8Array() is obviously a function for reading values, its contents should not change with each call.

To fix
IntDiffOptRleEncoder should have a flag like mutated. In that case, the implementation would be as follows

export class IntDiffOptRleEncoder {
  constructor () {
    this.encoder = new Encoder()
    /**
     * @type {number}
     */
    this.s = 0
    this.count = 0
    this.diff = 0
    this.mutated = false
  }

  /**
   * @param {number} v
   */
  write (v) {
    this.mutated = true
    if (this.diff === v - this.s) {
      this.s = v
      this.count++
    } else {
      flushIntDiffOptRleEncoder(this)
      this.count = 1
      this.diff = v - this.s
      this.s = v
    }
  }

  toUint8Array () {
    if (this.mutated) {
      flushIntDiffOptRleEncoder(this)
      this.mutated = false
    }
    return toUint8Array(this.encoder)
  }
}

Appendices
In yjs, there is no part of IntDiffOptRleEncoder that calls toUint8Array() twice.
I also confirmed that IntDiffOptRleEncoder with this modification passes all tests in yjs.

(I am personally working on yjs for Swift yswift. I discovered this in debugging).

Decoder loops forever when constructed with a non-array value

Describe the bug
The following code loops forever due to r being undefined inside readVarUint().

import { createDecoder, readVarUint } from 'lib0/decoding';

// Create a Blob (not an actual Blob, since they aren't a thing in Node, but
// let's pretend we have one like you get in the Browser)
const blob = { size: 20, type: '' };

const decoder = createDecoder(blob);
readVarUint(decoder);

Expected behavior
createDecoder() should throw if passed an unexpected value (such as a Blob)

... or something, anything, other than locking up the CPU/browser/system for all eternity. :-)

Screenshots
CleanShot 2022-08-19 at 15 02 14@2x

recent updates broke yjs dependency

Describe the bug
A clear and concise description of what the bug is.

To Reproduce
Steps to reproduce the behavior:

  1. Go to '...'
  2. Click on '....'
  3. Open console
  4. See error

Expected behavior
A clear and concise description of what you expected to happen.

Screenshots
If applicable, add screenshots to help explain your problem. Tip: you can drag-drop images into this textarea.

image

**Environment Information** - Browser / Node.js [e.g. Chrome, Firefox, Node.js] - Which version are you using?

Additional context
Add any other context about the problem here.

increase base64 performance in browser using FileReader.readAsDataURL()

Is your feature request related to a problem? Please describe.

When using an isometric library I prefer them to use functionality from the platform where possible. This uses fromCharCode to convert from binary to base64 when FileReader.readAsDataURL() ought to work.

Describe the solution you'd like

Wrapping FileReader.readAsDataURL would be preferable, because instead of looping through each byte in JS, a native method does the whole thing.

https://developer.mozilla.org/en-US/docs/Web/API/FileReader/readAsDataURL

Describe alternatives you've considered

I considered using platform functionality everywhere if it would work, however, FileReader.readAsDataURL is not available on Node. Uint8Array is on Node. So they still need separate ways to work.

Additional context

buffer.toBase64 is used in y-webrtc I was thinking of using it for saving Y Docs to localStorage but may end up using IndexedDB. My code already uses localStorage and IndexedDB isn't supported on Firefox Private Browsing sessions so that's why I may keep using localStorage for the time being.

Version 0.2.86 breaks with jest-environment-jsdom

Describe the bug
When running tests using jest and jest-environment-jsdom, version 0.2.86 of this package causes a failure:

 FAIL  ./test.js
  โ— Test suite failed to run

    Jest encountered an unexpected token

    Jest failed to parse a file. This happens e.g. when your code or its dependencies use non-standard JavaScript syntax, or when Jest is not configured to support such syntax.

    Out of the box Jest supports Babel, which will be used to transform your files into valid JS based on your Babel configuration.

    By default "node_modules" folder is ignored by transformers.

    Here's what you can do:
     โ€ข If you are trying to use ECMAScript Modules, see https://jestjs.io/docs/ecmascript-modules for how to enable it.
     โ€ข If you are trying to use TypeScript, see https://jestjs.io/docs/getting-started#using-typescript
     โ€ข To have some of your "node_modules" files transformed, you can specify a custom "transformIgnorePatterns" in your config.
     โ€ข If you need a custom transformation specify a "transform" option in your config.
     โ€ข If you simply want to mock your non-JS modules (e.g. binary assets) you can stub them out with the "moduleNameMapper" config option.

    You'll find more details and examples of these config options in the docs:
    https://jestjs.io/docs/configuration
    For information about custom transformations, see:
    https://jestjs.io/docs/code-transformation

    Details:

    /tmp/test/node_modules/lib0/webcrypto.js:3
    export const subtle = crypto.subtle
    ^^^^^^

    SyntaxError: Unexpected token 'export'

      3 |  */
      4 |
    > 5 | const random = require( 'lib0/random' );
        |                ^
      6 |
      7 | test( 'it works', () => {
      8 |     const element = document.createElement('div');

      at Runtime.createScriptFromCode (node_modules/jest-runtime/build/index.js:1505:14)
      at Object.<anonymous> (node_modules/lib0/dist/random.cjs:7:17)
      at Object.require (test.js:5:16)

Test Suites: 1 failed, 1 total
Tests:       0 total
Snapshots:   0 total
Time:        0.325 s

It worked with 0.2.85.

To Reproduce
Steps to reproduce the behavior:

  1. Create an empty directory for the test.
  2. Run npm add jest jest-environment-jsdom lib0
  3. Create the following file named test.js:
    /**
     * @jest-environment jsdom
     */
    
    const random = require( 'lib0/random' );
    
    test( 'it works', () => {
        const element = document.createElement('div');
        expect( element ).not.toBeNull();
    } );
  4. Run npm exec jest test.js
  5. See error

Expected behavior
Test passes, along the lines of

 PASS  ./test.js
  โœ“ it works (2 ms)

Test Suites: 1 passed, 1 total
Tests:       1 passed, 1 total
Snapshots:   0 total
Time:        0.379 s

Environment Information

  • Node.js
  • Which version are you using? v18.17.1

Additional context
The cause is 4b4faaa. With jest-environment-jsdom the conditions available are "browser", "require" (not "import"), and "default", but that commit assumes "browser" always comes with "import".

A detailed analysis of a similar problem in another package, along with potential workarounds, may be found at microsoft/accessibility-insights-web#5421 (comment).

IDB blocked event forces a page reload

Checklist

Is your feature request related to a problem? Please describe.

When calling idb functions, it is possible that the page is forced to refresh in rtop when onblocked triggers:

lib0/indexeddb.js

Lines 19 to 28 in 8af099f

export const rtop = request => promise.create((resolve, reject) => {
/* istanbul ignore next */
// @ts-ignore
request.onerror = event => reject(new Error(event.target.error))
/* istanbul ignore next */
// @ts-ignore
request.onblocked = () => location.reload()
// @ts-ignore
request.onsuccess = event => resolve(event.target.result)
})

Perhaps there is something causing the db to be blocked in my specific case that I can investigate. Currently it is preventing me from calling clearData in y-indexeddb.

However, I'm concerned with the low-level behavior here. The risk of a forced page refresh in a production app is pretty bad. I can't think of a case where this would be a positive user experience.

Describe the solution you'd like

The app should be able to handle the blocked event with its own logic, at least by opting in, if not by default. This would allow one to ignore the blocked idb request, retry the request, show a message to the user, etc.

I would recommend rejecting with a custom Error type that can be caught and detected higher up.

Describe alternatives you've considered

location.reload cannot be monkey patched, so there is very little that can be down currently to stop it.

I'm happy to help with a PR if this is a valid request.

map.setIfUndefined doesn't consider inheritance in parameters

Describe the bug
When we attempt to call map.setIfUndefined(A, 'key', () => new B) where class B extends the class used in Map A, we get the following:

Argument of type 'Map<string, A>' is not assignable to parameter of type 'Map<string, B>'.
  Type 'A' is missing the following properties from type 'B': ... ts(2345)

This seems to be related to its definition:

 * @function
 * @template V,K
 * @template {Map<K,V>} MAP
 * @param {MAP} map
 * @param {K} key
 * @param {function():V} createT
 * @return {V}

We have worked around the issue by copying the logic inline instead of using map.setifUndefined, but it would be great to be able to use it with inherited classes.

To Reproduce
Steps to reproduce the behavior:

Use the following JS, and attempt to generate types with tsc:

import * as map from 'lib0/map'

class A {
  constructor() {
    this.property1 = 1
  }
}

class B extends A {
  constructor() {
    super()
    this.property2 = 2
  }
}

/**
 * @type {Map<string, A>}
 */
const set = new Map()

const item = map.setIfUndefined(set, 'key', () => new B)

Expected behavior
We can call map.setIfUndefined(A, 'key', () => new B) where class B extends the class used in Map A, and the function adds a new instance of B to the map without errors.

Screenshots
If applicable, add screenshots to help explain your problem. Tip: you can drag-drop images into this textarea.

Environment Information

  • Node 18.18.2

Additional context
Add any other context about the problem here.

Issue with crypto in random.js

Hi,

I thought I'd just raise an issue for this as I was unsure of a good workaround.

I ran into an issue when using Yjs with Angular. Namely, since it is still run under a node environment, the lib0 library attempts to use node's crypto instead of browser crypto. (see this line

lib0/random.js

Line 11 in 17d4bee

const nodeCrypto = env.isNode ? require('crypto') : null
)

However, Angular did not like this since it doesn't want to expose core node modules in this way to the frontend (understandable) - it would give an error saying it could not resolve requiring crypto.

My workaround, in this case, was just to remove this line altogether and modify the rest of the file such that it only uses browser crypto.

I just thought I'd make you, and anyone else that got as far as figuring this out, aware of the issue.
Maybe perhaps there is a better way of determining the environment?

Circular dependency between buffer.js, encoding.js, and decoding.js

Describe the bug
I was running yjs code through rollup and got the following error:

(!) Circular dependencies
node_modules/lib0/encoding.js -> node_modules/lib0/buffer.js -> node_modules/lib0/encoding.js
node_modules/lib0/buffer.js -> node_modules/lib0/decoding.js -> node_modules/lib0/buffer.js

Not sure of the best way to go about fixing these since I'm not familiar with the codebase, but thought I'd make you aware of it if you weren't already. :)

ObservableV2 does not accept an interface as its generic parameter

Describe the bug
ObservableV2 does not accept A as a generic parameter if A is not a @typedef.
Related to:

To Reproduce

import { ObservableV2 } from 'lib0/observable';

class A {
  /** @type {() => void} */ a;
}

/** @type {ObservableV2<A>} */
const what = /** @type {any} */ ({});

See this TypeScript playground link.
(Note that the implementation of lib0/observable has been copied into the link - the TS playground seems to have problems importing its type definitons from npm)

Expected behavior
No type errors

See this TypeScript playground link for a demo of a potential solution.

Screenshots
N/A

Environment Information

  • Platform: any
  • Version: 0.2.85

Additional context
this is a potential solution:

/**
 * @template {{[key in keyof EVENTS]: function(...any):void}} EVENTS
 */

it is implemented in the link in the "expected behavior" section above

it isn't valid JSDoc syntax though, so it may not be an option if you're planning on generating docs from the source.

tree.findNode TypeScript definition error

I'm getting the following type error when compiling a project that depends on lib0:

node_modules/lib0/tree.d.ts(54,22): error TS2315: Type 'N' is not generic.

This line seems to cause the error:

lib0/tree.js

Line 297 in 73006db

* @return {N<V>|null}

The N class does not have a @template line in its doc comment.

(Also, thanks for the fantastic work you've done on Yjs and all of the supporting libraries.)

Projects using lib0 after 0.2.70 don't build with parcel

Follow-up on yjs/yjs#511, this issue tracker is probably a better place for it.

I'm not sure if this is a parcel bug or a problem with this library, but after release 0.2.70 projects that are using parcel 2 don't build anymore. I created a minimal example, just importing yjs, and it fails right away on building.

The repository is https://github.com/JanMisker/yjs-test

Please advise whether this should be resolved in Parcel or within this library...

react-native support

I am interested in exploring the use yjs in react-native and expo, using valitio-yjs and y-websocket. I've started by porting the valtio-yjs-demo to react-native. Here is a an expo snack for the purpose of developing and testing the implementation.

Unfortunately, there are some issues with importing yjs in react-native, specifically related to lib0 as dependency.

The snack fails to resolve yjs as a dependency due to lib0:

Failed to resolve dependency 'yjs@^13.5.24' (export 'Observable' (imported as 'Observable') was not found in 'lib0/observable' (module has no exports)

If you download the example and run it locally, the metro.config.js file, when configured to resolve .mjs and .cjs files, successfully imports and runs in the web browser. But runs into some errors when trying to run it in the iOS simulator:

While trying to resolve module `isomorphic.js` from file `~/valtio-yjs-expo/node_modules/lib0/dist/random.cjs`, the package `~/valtio-yjs-expo/node_modules/isomorphic.js/package.json` was successfully found. However, this package itself specifies a `main` module field that could not be resolved (`~/valtio-yjs-expo/node_modules/isomorphic.js/browser.mjs`. Indeed, none of these files exist:

This issue is also referenced upstream at dai-shi/valtio-yjs/issues/20 and yjs/yjs/issues/381.

Steps to reproduce the issue:

  1. Download the zip
  2. install the dependencies yarn
  3. run in the browser yarn web - success
  4. run in the iOS simulator yarn ios - fail

Expected behavior
A clear and concise description of what you expected to happen.

Environment Information

"yjs": "^13.5.24",
"lib0": "^0.2.43",
"valtio": "^1.2.9",
"valtio-yjs": "^0.3.0",
"y-websocket": "^1.3.18"

ES Module error when importing types

Describe the bug
I am unable to import and use the generated types because this is an es module, so I can import the cjs files fine from dist but then I don't get the types. Is there a way to use the typescript d.ts files at all?

To Reproduce

import { createEncoder } from 'lib0/encoding';

const encoder = createEncoder();

console.log(encoder);

Save as index.ts.

  1. tsc index.ts
  2. node dist/index.ts
    this results in:
internal/modules/cjs/loader.js:1092
      throw new ERR_REQUIRE_ESM(filename, parentPath, packageJsonPath);
      ^

Error [ERR_REQUIRE_ESM]: Must use import to load ES Module: /Users/robindiddams/work/lib0test/node_modules/lib0/encoding.js
require() of ES modules is not supported.

according to this post on discuss.yjs.dev im supposed to do it like this:

const { createEncoder } = require('lib0/dist/encoding.cjs');

but then I lose my type definitions ๐Ÿ˜ข .

Expected behavior
I expect to be able to use the types declarations in a typescript nodejs app.

Screenshots
I've made a repo with the code if you want to look more https://github.com/Robindiddams/lib0test

Environment Information

  • Nodejs version: v14.9.0
  • Typescript version: 4.1.3
  • macos 11.1

Additional context
Using in a nodejs server, so not running in the background

Version 0.2.88 breaks testing Yjs with react-scripts test

Describe the bug
When running tests for Yjs with react-scripts test, import of lib0/webcrypto inside lib0/dist/random.cjs causes test failure with "SyntaxError: Unexpected token 'export'".

 FAIL  src/yjs.test.js
  โ— Test suite failed to run

    Jest encountered an unexpected token

    Jest failed to parse a file. This happens e.g. when your code or its dependencies use non-standard JavaScript syntax, or when Jest is not configured to support such syntax.

    Out of the box Jest supports Babel, which will be used to transform your files into valid JS based on your Babel configuration.

    By default "node_modules" folder is ignored by transformers.

    Here's what you can do:
     โ€ข If you are trying to use ECMAScript Modules, see https://jestjs.io/docs/ecmascript-modules for how to enable it.
     โ€ข If you are trying to use TypeScript, see https://jestjs.io/docs/getting-started#using-typescript
     โ€ข To have some of your "node_modules" files transformed, you can specify a custom "transformIgnorePatterns" in your config.
     โ€ข If you need a custom transformation specify a "transform" option in your config.
     โ€ข If you simply want to mock your non-JS modules (e.g. binary assets) you can stub them out with the "moduleNameMapper" config option.

    You'll find more details and examples of these config options in the docs:
    https://jestjs.io/docs/configuration
    For information about custom transformations, see:
    https://jestjs.io/docs/code-transformation

    Details:

    /workspaces/lib0-webcrypto-jest-fail/node_modules/lib0/webcrypto.js:3
    export const subtle = crypto.subtle
    ^^^^^^

    SyntaxError: Unexpected token 'export'

    > 1 | const yjs = require('yjs');
        |             ^
      2 |
      3 | describe('yjs', () => {
      4 |     it('fails', () => {

      at Runtime.createScriptFromCode (node_modules/jest-runtime/build/index.js:1728:14)
      at Object.<anonymous> (src/yjs.test.js:1:13)

Test Suites: 1 failed, 1 passed, 2 total
Tests:       1 passed, 1 total
Snapshots:   0 total
Time:        1.395 s

To Reproduce
Steps to reproduce the behavior:

  1. Create empty CRA project with npx create-react-app example-app
  2. Add dependencies via npm add [email protected]
  3. Create test src/yjs.test.js with the content:
const yjs = require('yjs');

describe('yjs', () => {
    it('fails', () => {
        expect(yjs).toBeDefined();
    });
});
  1. Run tests with npm run test -- --watchAll=false
  2. See error

Expected behavior
The test should not fail.

Environment Information

Additional context
I created public repository for reproducing the issue in https://github.com/krotovic/lib0-webcrypto-jest-fail

implement isomorphic setTimeout & setInterval

typescript uses nodejs types when calling setTimes() => NodeJS.Timeout. Sadly, the default setTimeout function is not isomorphic as it depends on node types that are not available in some environments.

Missing close method on LocalStoragePolyfill

I'm seeing channel.bc.close is not a function when attempting to disconnect a broadcast channel. This is showing up on my CI server when attempting to upgrade lib0 beyond 0.2.58. I believe this was introduced in this commit which added a call to channel.bc.close.

I assume my CI server is using LocalStoragePolyfill which doesn't respond to close. Should that method be added?

I can provide further details on this if needed.

Websocket does not support binary messages

Reproduction steps:

websocket.send(new Uint8Array([0, 1, 2, 3]).buffer)

Cause:

lib0/websocket.js

Lines 128 to 132 in 1c3bc44

send (message) {
if (this.ws) {
this.ws.send(JSON.stringify(message))
}
}

it would also be nice to be able to disable ping/pong (since ping and pong frames are already built into the protocol)

Iframe issue in Brave browser with environment.js

We don't directly use lib0 but we use YJS and we allow to integrate a page from our app in an iframe, the page in the iframe doesn't load because of this error:
image

It seems this issue only happens in brave browser

Jest reports BroadcastChannel leak

Hi! ๐Ÿ‘‹

Firstly, thanks for your work on this project! ๐Ÿ™‚

Today I used patch-package to patch [email protected] for the project I'm working on.

After I updated to node@18 and jest@28 I found out jest reports some of my tests leak.

Jest has detected the following 1 open handle potentially keeping Jest from exiting:

  โ—  MESSAGEPORT
      at node_modules/lib0/broadcastchannel.js:66:16
      at Object.setIfUndefined (node_modules/lib0/map.js:49:24)
      at map.setIfUndefined (node_modules/lib0/broadcastchannel.js:64:3)
      at Object.subscribe (node_modules/lib0/broadcastchannel.js:83:39)
      at WebsocketProvider.bc.subscribe [as connectBc] (node_modules/y-websocket/src/y-websocket.js:345:7)
      at WebsocketProvider.connect (node_modules/y-websocket/src/y-websocket.js:394:12)
      at new WebsocketProvider (node_modules/y-websocket/src/y-websocket.js:305:12)

I found out that broadcastchannel module keeps BroadcastChannel alive for every room even with zero subscribers.

Here is the diff that solved my problem:

diff --git a/node_modules/lib0/dist/broadcastchannel-7655b66f.cjs b/node_modules/lib0/dist/broadcastchannel-7655b66f.cjs
index 12a6f51..fc82374 100644
--- a/node_modules/lib0/dist/broadcastchannel-7655b66f.cjs
+++ b/node_modules/lib0/dist/broadcastchannel-7655b66f.cjs
@@ -74,7 +74,17 @@ const subscribe = (room, f) => getChannel(room).subs.add(f);
  * @param {string} room
  * @param {function(any, any):any} f
  */
-const unsubscribe = (room, f) => getChannel(room).subs.delete(f);
+const unsubscribe = (room, f) => {
+    const channel = getChannel(room);
+    const unsubscribed = channel.subs.delete(f);
+    if (unsubscribed) {
+        if (channel.subs.size === 0) {
+            channel.bc.close();
+            channels.delete(room);
+        }
+    }
+    return unsubscribed;
+}
 
 /**
  * Publish data to all subscribers (including subscribers on this tab)

This issue body was partially generated by patch-package.

Usage with webpack

Hi there!

I am starting to use yjs, and ran into an issue with this library not being correctly used/transpiled by the bundling I'm using. The app is a next.js application, which uses webpack etc to bundle everything. I am trying to run the yjs function applyUpdate, which seems to have a dep somewhere on the binary lib in lib0.

This is the error I get:

TypeError: contentRefs[(info & lib0_binary_js__WEBPACK_IMPORTED_MODULE_17__.BITS5)] is not a function

Also when I have tried to import other libraries from lib0, I get similar errors.

Thanks so much!

isNode detection not always working

Describe the bug

Remix recently moved their development libs to depend on @jspm/core. Unfortunately this package will polyfill window.process to make it look the like node, so that the following line returns true in the browser:

lib0/environment.js

Lines 15 to 16 in bd69ab4

export const isNode = typeof process !== 'undefined' && process.release &&
/node|io\.js/.test(process.release.name)

I realise this is not strictly a bug in lib0, but maybe the isNode detection could be tweaked to work in browser environments patched with @jspm/core?

To Reproduce
Load y-js into any project with running via @remix-run/[email protected] and the browser logic thinks it's running in node.

Expected behavior
Be able to detect the browser environment despite process being patched by @jspm/core.

Environment Information

  • Latest firefox & safari

Module iso-browser.js does not provide an export named 'default'

I'm running yjs (13.2.0) in es-dev-server (1.54.1).

I get this error in the Chrome console (85.0.4167.0):

Uncaught SyntaxError: The requested module '../isomorphic.js/iso-browser.js' does not provide an export named 'default'

The error is triggered by this import

import iso from 'isomorphic.js'

dmonad/isomorphic.js@0bee192 seems to have indeed removed the default export.

Not importing the default export solves the error:

import * as iso from 'isomorphic.js'

sideEffects and tree-shaking

I ported some parts of lib0 to my open source lib zeed, because I needed some special changes for my usecase. The code is here: https://github.com/holtwick/zeed/tree/master/src/common/bin

While doing this, I noticed that there are some parts in the code, that will not be tree-shaken. Mainly global variables. Since the package.json states, that sideEffects: false I thought this might be of interest.

I collected some learning during the process, which might help: https://github.com/holtwick/zeed/tree/master/demos/sideeffects#readme

Anyway, the binary encoder you wrote is just great! ๐ŸŽ‰

Cannot start server becouse of wrong imports

Describe the bug
When I am running on node 16.14.2 on Ubuntu I am getting this error:
/var/www/ps-article-storage/dev/.pm2/logs/ps-article-storage-error.log last 15 lines:
0|ps-artic | at Object. (/var/www/storage/dev/public_html/node_modules/lib0/dist/webcrypto.node.cjs:5:19)
0|ps-artic | at Module._compile (internal/modules/cjs/loader.js:1063:30)
0|ps-artic | at Object.Module._extensions..js (internal/modules/cjs/loader.js:1092:10)
0|ps-artic | at Module.load (internal/modules/cjs/loader.js:928:32)
0|ps-artic | at Function.Module._load (internal/modules/cjs/loader.js:769:14)
0|ps-artic | at Module.require (internal/modules/cjs/loader.js:952:19) {
0|ps-artic | code: 'MODULE_NOT_FOUND',
0|ps-artic | requireStack: [
0|ps-artic | '/var/www/storage/dev/public_html/node_modules/lib0/dist/webcrypto.node.cjs',
0|ps-artic | '/var/www/storage/dev/public_html/node_modules/lib0/dist/random.cjs',
0|ps-artic | '/var/www/storage/dev/public_html/node_modules/yjs/dist/yjs.cjs',
0|ps-artic | '/var/www/storage/dev/public_html/utils.js',
0|ps-artic | '/var/www/storage/dev/public_html/server.js'
0|ps-artic | ]
0|ps-artic | }
On Mac same node version this issue is not reproduced.

To Reproduce
using server from y-websocket/bin/utils:

node server

Expected behavior
should start the server

Reduce install size

Is your feature request related to a problem? Please describe.

Install size for lib0 seems to be around 2.2MB. What causes the bloat is inclusion of the compiled test suite:

  • dist/test.js.map (428Kb)
  • dist/test.cjs.map (423Kb)
  • dist/test.js (220Kb)
  • dist/test.cjs (204Kb)

Describe the solution you'd like

These files could be added to .npmignore or other means of excluding them.

Circular Dependency (encoding.js and buffer.js)

Describe the bug
There is a circular dependency between encoding.js and buffer.js. Not sure why this is necessary as encodeAny in buffer.js is the cause of this and it can easily be moved to buffer.js. That said, depending on if anyone is using this it might be a breaking change unfortunately, requiring a major release.

This isn't a big deal but does cause issues in some workflows (ie rollup).

verifyJWT assumes non-standard expiration claim format (ms instead of seconds)

Describe the bug

The jwt logic assumes exp is in ms timestamp format (time.getUnixTime is just Date.now), but the JWT RFC (https://www.rfc-editor.org/rfc/rfc7519#section-4.1.4) states it should be seconds since epoch, not ms. This means that if something is using lib0 to validate a JWT token, but that JWT token was generated to the RFC, the expiration check will fail.

To Reproduce
Steps to reproduce the behavior:

  1. Generate a standards compliant JWT token (e.g., with a JWT library in another platform)
  2. Verify the token with lib0
  3. See false expiration error

Expected behavior
Expect epoch seconds for exp claims.

Possibly add logic to support both since the bug was out there.

Relevant code:

if (payload.exp != null && time.getUnixTime() > payload.exp) {

ObservableV2 template is too permissive

Describe the bug

The ObservableV2 class allows the specification of a type which defines the set of events and callbacks, but the template is overly permissive. Namely, the EVENT parameter can constrain the callbacks such that every callback must return a value. This possibility makes it very difficult to write well-typed abstractions over ObservableV2 (they must handle the case where EVENTS has been instantiated this way).

To Reproduce

Here's a small example of this in typescript:

function foo() {
    let x = new ObservableV2<{"hi": () => number}>();
    x.on('hi', () => {}); // ERROR HERE
}

Which results in the error:

Argument of type '() => void' is not assignable to parameter of type '() => number'.
  Type 'void' is not assignable to type 'number'.ts(2345)

Expected behavior
ObservableV2 should always allow callbacks that return void.

Additional context
I ran into this problem while trying to write a utility function called waitUntil that runs a test on all emitted events and resolves a promise when it successfully passes the test. The untyped form is basically:

const waitUntil = (observable, event, test) => Promise.new(resolve => {
    function cb(..args) {
        if (test(...args)) {
            observable.off(event, cb);
            resolve(args);
        }
    }
    
    observable.on(event, cb);
});

This lets me write nice little functions like:

const waitUntilConnected = (client) => 
    waitUntil(client, "update", status => (status === "connected"));

Unfortunately it's impossible (or at least beyond me) to make the function cb inside waitUntil typecheck because the observable could require that callbacks for this event return a value.

`import "yjs"` has been broken for browser contexts since `[email protected]` due to remapping exports

Describe the bug

lib0/webcrypto has been broken for browser contexts since [email protected] due to remapping exports (2617ed3).

To Reproduce

# cargo install deno

git clone https://github.com/spence/lib0-webcrypto.git
cd lib0-webcrypto
deno task start

# open http://localhost:8080 and show console

Expected Behavior

Via pinning lib0/[email protected]:
image

Actual Behavior

Without pinning:
image

Environment Information

  • Browser (e.g., Chrome 119.0.6024.0)

Additional context

You can see the published source of each version via source maps.

0.2.72 (works)

curl -sS https://esm.sh/v132/[email protected]/es2022/webcrypto.js.map | jq
{
  "mappings": ";AAEO,IAAMA,EAAS,OAAO,OAChBC,EAAkB,OAAO,gBAAgB,KAAK,MAAM",
  "names": [
    "subtle",
    "getRandomValues"
  ],
  "sourceRoot": "/",
  "sources": [
    "../esmd/npm/[email protected]/node_modules/.pnpm/[email protected]/node_modules/lib0/webcrypto.browser.js"
  ],
  "sourcesContent": [
    "/* eslint-env browser */\n\nexport const subtle = crypto.subtle\nexport const getRandomValues = crypto.getRandomValues.bind(crypto)\n"
  ],
  "version": 3
}

0.2.73 (broken)

curl -sS https://esm.sh/v132/[email protected]/es2022/webcrypto.js.map | jq
{
  "mappings": ";AACA,OAAS,aAAAA,MAAiB,8DAEnB,IAAMC,EAA6BD,EAAW,OACxCE,EAAsCF,EAAW,gBAAgB,KAAKA,CAAS",
  "names": [
    "webcrypto",
    "subtle",
    "getRandomValues"
  ],
  "sourceRoot": "/",
  "sources": [
    "../esmd/npm/[email protected]/node_modules/.pnpm/[email protected]/node_modules/lib0/webcrypto.node.js"
  ],
  "sourcesContent": [
    "\nimport { webcrypto } from 'node:crypto'\n\nexport const subtle = /** @type {any} */ (webcrypto).subtle\nexport const getRandomValues = /** @type {any} */ (webcrypto).getRandomValues.bind(webcrypto)\n"
  ],
  "version": 3
}

Related:

node 12

"node": ">=13"

lib0 seems to require node >= 13:

npm WARN notsup Unsupported engine for [email protected]: wanted: {"node":">=13"} (current: {"node":"12.14.1","npm":"6.13.4"})

While node 12 is the current stable version I think you might want to consider changing to node >= 12 if it does not have any side effects.

Circular module dependencies in encoding.js, buffer.js, decoding.js

Circular module dependencies are present in:

  • encoding.js <--> buffer.js
  • decoding.js <--> buffer.js

Version:

  • 0.2.58

Note: this was reported while using solid-start build tool:

Circular dependency: node_modules/.pnpm/[email protected]/node_modules/lib0/encoding.js -> node_modules/.pnpm/[email protected]/node_modules/lib0/buffer.js -> node_modules/.pnpm/[email protected]/node_modules/lib0/encoding.js
Circular dependency: node_modules/.pnpm/[email protected]/node_modules/lib0/buffer.js -> node_modules/.pnpm/[email protected]/node_modules/lib0/decoding.js -> node_modules/.pnpm/[email protected]/node_modules/lib0/buffer.js

support and honor NO_COLOR env variable

Is your feature request related to a problem? Please describe.
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
NO_COLOR is de-facto standard and it frustrates me when some libs, tools wahtever don't support it

Describe the solution you'd like
A clear and concise description of what you want to happen.
honor NO_COLOR=true

Describe alternatives you've considered
A clear and concise description of any alternative solutions or features you've considered.
none

see also https://no-color.org/

Circular Dependency (encoding.js, decoding.js and buffer.js)

I couldn't reopen the other issue, so I created a new one.

I stumbled upon this issue, because I had encountered some strange issues using rollup in watch mode. I temporarily fixed the issue and it seems to be the cause. My fix was rather simple, I simply inlined all the occurences of "createUint8ArrayViewFromArrayBuffer()" to "new Uint8Array()" and dropped the buffer.js import from encoding.js and decoding.js.

Could you please fix this somehow? It's possible that this is a rollup issue, but it seems to be easily solvable.

Originally posted by @szilu in #19 (comment)

typescript errors as of 0.2.75

Describe the bug
The jsdoc types added to the is functions in 0.2.75 export typescript definitions which do not typecheck

To Reproduce
npm run clean && npm run types && npm run lint yields the following errors

function.d.ts:12:62 - error TS2344: Type 'TYPE' does not satisfy the constraint 'abstract new (...args: any) => any'.

12 export function is<TYPE>(n: any, T: TYPE): n is InstanceType<TYPE>;
                                                                ~~~~

  function.d.ts:12:20
    12 export function is<TYPE>(n: any, T: TYPE): n is InstanceType<TYPE>;
                          ~~~~
    This type parameter might need an `extends abstract new (...args: any) => any` constraint.

function.d.ts:13:74 - error TS2344: Type 'TYPE' does not satisfy the constraint 'abstract new (...args: any) => any'.

13 export function isTemplate<TYPE>(T: TYPE): (n: any) => n is InstanceType<TYPE>;
                                                                            ~~~~

  function.d.ts:13:28
    13 export function isTemplate<TYPE>(T: TYPE): (n: any) => n is InstanceType<TYPE>;
                                  ~~~~
    This type parameter might need an `extends abstract new (...args: any) => any` constraint.

isomorphic.d.ts:1:10 - error TS2303: Circular definition of import alias 'performance'.

1 export { performance, cryptoRandomBuffer } from "isomorphic.js";
           ~~~~~~~~~~~

isomorphic.d.ts:1:23 - error TS2303: Circular definition of import alias 'cryptoRandomBuffer'.

1 export { performance, cryptoRandomBuffer } from "isomorphic.js";
                        ~~~~~~~~~~~~~~~~~~

Expected behavior
Lints pass

Environment Information

  • node v18.16.0

Additional Information
you may want to add the linting as a prepublish script

Mutex Implementation seems incorrect

Describe the bug
Short Background: I am evaluating at the moment, wether we should switch to yjs from automerge in our software

Looking at the mutex code

  let token = true
  return (f, g) => {
    if (token) {
      token = false
      try {
        f()
      } finally {
        token = true
      }
    } else if (g !== undefined) {
      g()
    }
  }
}```

I am wondering, what that code should accomplish and what it actually does.

The mutex functionality only works, if the mutex is called within the mutex. Something like that:

const mutex = createMutex()

mutex(() => mutex(() => console.log('test')))


Here the mutex prevents the inner code from being executed. But it will not execute the inner function at all and does not inform about it (that is what the `g` function is for i guess, but that is not how I expect a mutex to work and that is also not how it was implemented in yjs). As javascript is single threaded (from a developer point of view) it is not even possible to enter this mutex without nesting a mutex call as shown above.

**Expected behavior**
What i think it should solve: if `f` being an async function, the mutex should not allow other async functions to execute until the running function has ended. See https://github.com/DirtyHairy/async-mutex

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.