rewrite for v13

This commit is contained in:
Kevin Jahns 2019-12-05 17:32:09 +01:00
parent 1c6559b57d
commit 1fac4380fe
14 changed files with 2717 additions and 220 deletions

13
.gitignore vendored
View file

@ -1,14 +1,3 @@
dist
node_modules node_modules
bower_components
build
build_test
.directory
.codio
.settings
.jshintignore
.jshintrc
.validate.json
/y.js
/y.js.map
/y-*
.vscode .vscode

4
.gitmodules vendored
View file

@ -1,4 +0,0 @@
[submodule "dist"]
path = dist
url = https://github.com/y-js/y-webrtc.git
branch = dist

141
bin/server.js Executable file
View file

@ -0,0 +1,141 @@
#!/usr/bin/env node
const ws = require('ws')
const http = require('http')
const map = require('lib0/dist/map.js')
const wsReadyStateConnecting = 0
const wsReadyStateOpen = 1
const wsReadyStateClosing = 2 // eslint-disable-line
const wsReadyStateClosed = 3 // eslint-disable-line
const pingTimeout = 30000
const port = process.env.PORT || 4444
// @ts-ignore
const wss = new ws.Server({ noServer: true })
const server = http.createServer((request, response) => {
response.writeHead(200, { 'Content-Type': 'text/plain' })
response.end('okay')
})
/**
* Map froms topic-name to set of subscribed clients.
* @type {Map<string, Set<any>>}
*/
const topics = new Map()
/**
* @param {any} conn
* @param {object} message
*/
const send = (conn, message) => {
if (conn.readyState !== wsReadyStateConnecting && conn.readyState !== wsReadyStateOpen) {
conn.close()
}
try {
conn.send(JSON.stringify(message))
} catch (e) {
conn.close()
}
}
/**
* Setup a new client
* @param {any} conn
*/
const onconnection = conn => {
/**
* @type {Set<string>}
*/
const subscribedTopics = new Set()
let closed = false
// Check if connection is still alive
let pongReceived = true
const pingInterval = setInterval(() => {
if (!pongReceived) {
conn.close()
clearInterval(pingInterval)
} else {
pongReceived = false
try {
conn.ping()
} catch (e) {
conn.close()
}
}
}, pingTimeout)
conn.on('pong', () => {
pongReceived = true
})
conn.on('close', () => {
subscribedTopics.forEach(topicName => {
const subs = topics.get(topicName) || new Set()
subs.delete(conn)
if (subs.size === 0) {
topics.delete(topicName)
}
})
subscribedTopics.clear()
closed = true
})
conn.on('message', /** @param {object} message */ message => {
if (typeof message === 'string') {
message = JSON.parse(message)
}
if (message && message.type && !closed) {
switch (message.type) {
case 'subscribe':
/** @type {Array<string>} */ (message.topics || []).forEach(topicName => {
if (typeof topicName === 'string') {
// add conn to topic
const topic = map.setIfUndefined(topics, topicName, () => new Set())
topic.add(conn)
// add topic to conn
subscribedTopics.add(topicName)
}
})
break
case 'unsubscribe':
/** @type {Array<string>} */ (message.topics || []).forEach(topicName => {
const subs = topics.get(topicName)
if (subs) {
subs.delete(conn)
}
})
break
case 'publish':
if (message.topics) {
/**
* @type {Set<any>}
*/
const receivers = new Set()
message.topics.forEach(/** @param {string} topicName */ topicName => {
(topics.get(topicName) || []).forEach(sub => receivers.add(sub))
})
receivers.forEach(receiver => send(receiver, message))
}
break
case 'ping':
send(conn, { type: 'pong' })
}
}
})
}
wss.on('connection', onconnection)
server.on('upgrade', (request, socket, head) => {
// You may check auth of request here..
/**
* @param {any} ws
*/
const handleAuth = ws => {
wss.emit('connection', ws, request)
}
wss.handleUpgrade(request, socket, head, handleAuth)
})
server.listen(port)
console.log('Signalling server running on localhost:', port)

11
demo/index.html Normal file
View file

@ -0,0 +1,11 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8"/>
<title>y-webrtc demo</title>
</head>
<body>
<button type="button" id="y-connect-btn">Disconnect</button>
<script type="text/javascript" src="../dist/demo.js"></script>
</body>
</html>

19
demo/index.js Normal file
View file

@ -0,0 +1,19 @@
/* eslint-env browser */
import * as Y from 'yjs'
import { WebrtcProvider } from '../src/y-webrtc.js'
const ydoc = new Y.Doc()
const provider = new WebrtcProvider('prosemirror', ydoc)
const yarray = ydoc.get('prosemirror', Y.XmlFragment)
provider.on('synced', synced => {
console.log('synced!', synced)
})
yarray.observeDeep(() => {
console.log('yarray updated: ', yarray.toJSON())
})
// @ts-ignore
window.example = { provider, ydoc, yarray }

1
dist

@ -1 +0,0 @@
Subproject commit 0f203f1768cb5a206ee14fb0946f5ac218caefe9

View file

@ -1,40 +0,0 @@
/* eslint-env node */
var gulp = require('gulp')
var $ = require('gulp-load-plugins')()
var runSequence = require('run-sequence').use(gulp)
require('../yjs/gulpfile.helper.js')(gulp, {
polyfills: [],
entry: './src/WebRTC.js',
targetName: 'y-webrtc.js',
moduleName: 'yWebrtc',
specs: []
})
gulp.task('default', ['updateSubmodule'], function (cb) {
gulp.src('package.json')
.pipe($.prompt.prompt({
type: 'checkbox',
name: 'tasks',
message: 'Which tasks would you like to run?',
choices: [
'test Test this project',
'dev:browser Watch files & serve the testsuite for the browser',
'dev:nodejs Watch filse & test this project with nodejs',
'bump Bump the current state of the project',
'publish Publish this project. Creates a github tag',
'dist Build the distribution files'
]
}, function (res) {
var tasks = res.tasks.map(function (task) {
return task.split(' ')[0]
})
if (tasks.length > 0) {
console.info('gulp ' + tasks.join(' '))
runSequence(tasks, cb)
} else {
console.info('Ok, .. goodbye')
}
}))
})

4
now.json Normal file
View file

@ -0,0 +1,4 @@
{
"name": "y-webrtc",
"version": 1
}

2030
package-lock.json generated Normal file

File diff suppressed because it is too large Load diff

View file

@ -1,80 +1,63 @@
{ {
"name": "y-webrtc", "name": "y-webrtc",
"version": "8.0.7", "version": "8.0.7",
"description": "WebRTC Connector for Yjs", "description": "WebRTC provider for Yjs",
"main": "./src/WebRTC.js", "main": "./dist/y-webrtc.js",
"module": "./src/y-webrtc.js",
"sideEffects": false,
"scripts": { "scripts": {
"test": "node --harmony ./node_modules/.bin/gulp test", "now-start": "node ./bin/server.js",
"lint": "./node_modules/.bin/standard", "dist": "rm -rf dist && rollup -c",
"build": "./node_modules/.bin/gulp build" "lint": "standard",
"preversion": "npm run lint && npm run dist"
}, },
"pre-commit": [ "bin": {
"lint", "y-webrtc-server": "./bin/server.js"
"test" },
"files": [
"dist/*",
"bin/*",
"src/*"
], ],
"standard": {
"parser": "babel-eslint",
"ignore": [
"build/**",
"dist/**"
]
},
"repository": { "repository": {
"type": "git", "type": "git",
"url": "https://github.com/y-js/y-webrtc.git" "url": "git+https://github.com/yjs/y-webrtc.git"
}, },
"keywords": [ "keywords": [
"Yjs", "Yjs"
"OT",
"Collaboration",
"Synchronization",
"ShareJS",
"Coweb",
"Concurrency"
], ],
"author": "Kevin Jahns <kevin.jahns@rwth-aachen.de>", "author": "Kevin Jahns <kevin.jahns@protonmail.com>",
"license": "MIT", "license": "MIT",
"bugs": { "bugs": {
"url": "https://github.com/y-js/y-webrtc/issues" "url": "https://github.com/yjs/y-webrtc/issues"
}, },
"homepage": "http://y-js.org", "homepage": "https://github.com/yjs/y-webrtc#readme",
"dependencies": { "standard": {
"simplewebrtc": "^1.18.0" "ignore": [
"/dist",
"/node_modules"
]
}, },
"dependencies": {},
"devDependencies": { "devDependencies": {
"gulp-uglify": "^2.0.0", "simple-peer": "^9.6.2",
"babel-eslint": "^4.1.2", "lib0": "^0.1.4",
"babel-plugin-transform-runtime": "^6.1.18", "rollup": "^1.27.8",
"babel-preset-es2015": "^6.1.18", "rollup-cli": "^1.0.9",
"babel-runtime": "^6.1.18", "rollup-plugin-commonjs": "^10.1.0",
"babelify": "^7.2.0", "rollup-plugin-node-resolve": "^5.2.0",
"browserify": "^12.0.1", "rollup-plugin-terser": "^5.1.2",
"gulp": "^3.9.0", "standard": "^12.0.1",
"gulp-babel": "^5.2.1", "y-protocols": "^0.1.0",
"gulp-bump": "^1.0.0", "yjs": "13.0.0-102"
"gulp-concat": "^2.6.0",
"gulp-filter": "^3.0.1",
"gulp-git": "^1.6.0",
"gulp-jasmine": "^2.0.1",
"gulp-jasmine-browser": "^0.2.3",
"gulp-load-plugins": "^1.0.0",
"gulp-prompt": "^0.1.2",
"gulp-rename": "^1.2.2",
"gulp-serve": "^1.2.0",
"gulp-shell": "^0.5.1",
"gulp-sourcemaps": "^1.5.2",
"gulp-tag-version": "^1.3.0",
"gulp-util": "^3.0.6",
"gulp-watch": "^4.3.5",
"minimist": "^1.2.0",
"pre-commit": "^1.1.1",
"promise-polyfill": "^2.1.0",
"run-sequence": "^1.1.4",
"standard": "^5.2.2",
"vinyl-buffer": "^1.0.0",
"vinyl-source-stream": "^1.1.0"
}, },
"peerDependencies": { "peerDependenies": {
"yjs": "^9.0.0 || ^10.0.0 || ^11.0.0 || ^12.0.0" "yjs": ">=13.0.0-102"
},
"optionalDependencies": {
"ws": "^7.2.0"
},
"engines": {
"node": "10.x"
} }
} }

100
rollup.config.js Normal file
View file

@ -0,0 +1,100 @@
import nodeResolve from 'rollup-plugin-node-resolve'
import commonjs from 'rollup-plugin-commonjs'
import { terser } from 'rollup-plugin-terser'
// If truthy, it expects all y-* dependencies in the upper directory.
// This is only necessary if you want to test and make changes to several repositories.
const localImports = process.env.LOCALIMPORTS
if (localImports) {
console.info('Using local imports')
}
const customModules = new Set([
'y-websocket',
'y-codemirror',
'y-ace',
'y-textarea',
'y-quill',
'y-dom',
'y-prosemirror',
'y-monaco'
])
/**
* @type {Set<any>}
*/
const customLibModules = new Set([
'lib0',
'y-protocols'
])
// @ts-ignore We use this for debugging
const debugResolve = {
resolveId (importee) {
if (localImports) {
if (importee === 'yjs') {
return `${process.cwd()}/../yjs/src/index.js`
}
if (customModules.has(importee.split('/')[0])) {
return `${process.cwd()}/../${importee}/src/${importee}.js`
}
if (customLibModules.has(importee.split('/')[0])) {
return `${process.cwd()}/../${importee}`
}
}
return null
}
}
const minificationPlugins = process.env.PRODUCTION ? [terser({
module: true,
compress: {
hoist_vars: true,
module: true,
passes: 1,
pure_getters: true,
unsafe_comps: true,
unsafe_undefined: true
},
mangle: {
toplevel: true
}
})] : []
const plugins = [
debugResolve,
nodeResolve({
mainFields: ['module', 'browser', 'main']
}),
commonjs(),
...minificationPlugins
]
export default [
{
input: './demo/index.js',
output: [{
name: 'demo',
file: 'dist/demo.js',
format: 'iife',
sourcemap: true
}],
plugins
}, {
input: './src/y-webrtc.js',
external: id => /^(lib0|yjs|y-protocols|simple-peer)/.test(id),
output: [{
name: 'y-webrtc',
file: 'dist/y-webrtc.js',
format: 'cjs',
sourcemap: true,
paths: path => {
if (/^lib0\//.test(path)) {
return `lib0/dist${path.slice(4)}`
} else if (/^y-protocols\//.test(path)) {
return `y-protocols/dist${path.slice(11)}`
}
return path
}
}]
}
]

View file

@ -1,102 +0,0 @@
/* global Y */
'use strict'
var SimpleWebRTC = require('simplewebrtc')
function extend (Y) {
class WebRTC extends Y.AbstractConnector {
constructor (y, options) {
if (options === undefined) {
throw new Error('Options must not be undefined!')
}
if (options.room == null) {
throw new Error('You must define a room name!')
}
options.role = 'slave'
super(y, options)
this.webrtcOptions = {
url: options.url || 'https://yjs.dbis.rwth-aachen.de:5078',
room: options.room
}
var swr = new SimpleWebRTC(this.webrtcOptions)
this.swr = swr
var self = this
swr.once('connectionReady', function (userId) {
// SimpleWebRTC (swr) is initialized
swr.joinRoom(self.webrtcOptions.room)
swr.once('joinedRoom', function () {
self.setUserId(userId)
/*
var i
// notify the connector class about all the users that already
// joined the session
for(i in self.swr.webrtc.peers){
self.userJoined(self.swr.webrtc.peers[i].id, "master")
}*/
swr.on('channelMessage', function (peer, room_, message) {
// The client received a message
// Check if the connector is already initialized,
// only then forward the message to the connector class
if (message.type != null) {
self.receiveMessage(peer.id, message.payload)
}
})
})
swr.on('createdPeer', function (peer) {
// a new peer/client joined the session.
// Notify the connector class, if the connector
// is already initialized
self.userJoined(peer.id, 'master')
})
swr.on('peerStreamRemoved', function (peer) {
// a client left the session.
// Notify the connector class, if the connector
// is already initialized
self.userLeft(peer.id)
})
})
}
disconnect () {
this.swr.leaveRoom()
super.disconnect()
}
reconnect () {
this.swr.joinRoom(this.webrtcOptions.room)
super.reconnect()
}
send (uid, message) {
var self = this
// we have to make sure that the message is sent under all circumstances
var send = function () {
// check if the clients still exists
var peer = self.swr.webrtc.getPeers(uid)[0]
var success
if (peer) {
// success is true, if the message is successfully sent
success = peer.sendDirectly('simplewebrtc', 'yjs', message)
}
if (!success) {
// resend the message if it didn't work
setTimeout(send, 500)
}
}
// try to send the message
send()
}
broadcast (message) {
this.swr.sendDirectlyToAll('simplewebrtc', 'yjs', message)
}
isDisconnected () {
return false
}
}
Y.extend('webrtc', WebRTC)
}
module.exports = extend
if (typeof Y !== 'undefined') {
extend(Y)
}

305
src/y-webrtc.js Normal file
View file

@ -0,0 +1,305 @@
import * as ws from 'lib0/websocket.js'
import * as map from 'lib0/map.js'
import * as error from 'lib0/error.js'
import * as random from 'lib0/random.js'
import * as encoding from 'lib0/encoding.js'
import * as decoding from 'lib0/decoding.js'
import { Observable } from 'lib0/observable.js'
import * as Y from 'yjs' // eslint-disable-line
import Peer from 'simple-peer/simplepeer.min.js'
import * as syncProtocol from 'y-protocols/sync.js'
import * as awarenessProtocol from 'y-protocols/awareness.js'
const messageSync = 0
const messageQueryAwareness = 3
const messageAwareness = 1
/**
* @param {WebrtcRoom} webrtcRoom
*/
const checkIsSynced = webrtcRoom => {
let synced = true
webrtcRoom.peers.forEach(peer => {
if (!peer.syncedRooms.has(webrtcRoom.name)) {
synced = false
}
})
if ((!synced && webrtcRoom.synced) || (synced && !webrtcRoom.synced)) {
webrtcRoom.synced = synced
webrtcRoom.provider.emit('synced', [{ synced }])
}
}
/**
* @param {SignalingConn} signaling
* @param {WebrtcConn} peerConn
* @param {Uint8Array} buf
* @return {encoding.Encoder?}
*/
const readPeerMessage = (signaling, peerConn, buf) => {
const decoder = decoding.createDecoder(buf)
const encoder = encoding.createEncoder()
const messageType = decoding.readVarUint(decoder)
const roomName = decoding.readVarString(decoder)
const webrtcRoom = signaling.rooms.get(roomName)
if (webrtcRoom === undefined) {
return null
}
const provider = webrtcRoom.provider
const doc = webrtcRoom.doc
let sendReply = false
switch (messageType) {
case messageSync:
encoding.writeVarUint(encoder, messageSync)
encoding.writeVarString(encoder, roomName)
const syncMessageType = syncProtocol.readSyncMessage(decoder, encoder, doc, webrtcRoom.provider)
if (syncMessageType === syncProtocol.messageYjsSyncStep2 && !webrtcRoom.synced) {
peerConn.syncedRooms.add(roomName)
checkIsSynced(webrtcRoom)
}
if (syncMessageType === syncProtocol.messageYjsSyncStep1) {
sendReply = true
}
break
case messageQueryAwareness:
encoding.writeVarUint(encoder, messageAwareness)
encoding.writeVarString(encoder, roomName)
encoding.writeVarUint8Array(encoder, awarenessProtocol.encodeAwarenessUpdate(provider.awareness, Array.from(provider.awareness.getStates().keys())))
sendReply = true
break
case messageAwareness:
awarenessProtocol.applyAwarenessUpdate(provider.awareness, decoding.readVarUint8Array(decoder), provider)
break
default:
console.error('Unable to compute message')
return encoder
}
if (!sendReply) {
// nothing has been written, no answer created
return null
}
return encoder
}
/**
* @param {WebrtcConn} webrtcConn
* @param {encoding.Encoder} encoder
*/
const send = (webrtcConn, encoder) => {
webrtcConn.peer.send(encoding.toUint8Array(encoder))
}
/**
* @param {WebrtcRoom} webrtcRoom
* @param {encoding.Encoder} encoder
*/
const broadcast = (webrtcRoom, encoder) => {
const m = encoding.toUint8Array(encoder)
webrtcRoom.peers.forEach(peer => peer.peer.send(m))
}
export class WebrtcConn {
/**
* @param {SignalingConn} signalingConn
* @param {boolean} initiator
* @param {string} remotePeerId
* @param {Array<string>} announcedTopics
*/
constructor (signalingConn, initiator, remotePeerId, announcedTopics) {
this.remotePeerId = remotePeerId
this.closed = false
this.connected = false
/**
* @type {Set<string>}
*/
this.syncedRooms = new Set()
/**
* @type {any}
*/
this.peer = new Peer({ initiator })
this.peer.on('signal', data => {
signalingConn.send({ type: 'publish', topics: announcedTopics, to: remotePeerId, from: signalingConn.peerId, messageType: 'signal', data })
})
this.peer.on('connect', () => {
this.connected = true
announcedTopics.forEach(roomName => {
const room = signalingConn.rooms.get(roomName)
if (room) {
// add peer to room
room.peers.add(this)
// send sync step 1
const provider = room.provider
const doc = provider.doc
const awareness = provider.awareness
const encoder = encoding.createEncoder()
encoding.writeVarUint(encoder, messageSync)
encoding.writeVarString(encoder, room.name)
syncProtocol.writeSyncStep1(encoder, doc)
send(this, encoder)
const awarenessStates = awareness.getStates()
if (awarenessStates.size > 0) {
const encoder = encoding.createEncoder()
encoding.writeVarUint(encoder, messageAwareness)
encoding.writeVarString(encoder, room.name)
encoding.writeVarUint8Array(encoder, awarenessProtocol.encodeAwarenessUpdate(awareness, Array.from(awarenessStates.keys())))
send(this, encoder)
}
}
})
})
this.peer.on('close', () => {
this.connected = false
this.closed = true
signalingConn.conns.delete(this.remotePeerId)
signalingConn.rooms.forEach(room => {
room.peers.delete(this)
checkIsSynced(room)
})
this.peer.destroy()
})
this.peer.on('error', () => {
this.connected = false
this.closed = true
})
this.peer.on('data', data => {
const answer = readPeerMessage(signalingConn, this, data)
if (answer !== null) {
send(this, answer)
}
})
}
}
export class WebrtcRoom {
/**
* @param {Y.Doc} doc
* @param {WebrtcProvider} provider
* @param {string} name
*/
constructor (doc, provider, name) {
/**
* @type {Set<WebrtcConn>}
*/
this.peers = new Set()
this.doc = doc
this.provider = provider
this.synced = false
this.name = name
}
}
export class SignalingConn extends ws.WebsocketClient {
constructor (url) {
super(url)
this.peerId = random.uuidv4()
/**
* @type {Map<string,WebrtcRoom>}
*/
this.rooms = new Map()
/**
* @type {Map<string,WebrtcConn>}
*/
this.conns = new Map()
this.afterOpen.push(() => ({ type: 'subscribe', topics: Array.from(this.rooms.keys()) }))
this.afterOpen.push(() => ({ type: 'publish', messageType: 'announce', topics: Array.from(this.rooms.keys()), from: this.peerId }))
this.on('message', m => {
if (m.from === this.peerId || (m.to !== undefined && m.to !== this.peerId)) {
return
}
switch (m.type) {
case 'publish': {
switch (m.messageType) {
case 'announce':
map.setIfUndefined(this.conns, m.from, () => new WebrtcConn(this, true, m.from, m.topics))
break
case 'signal':
if (m.to === this.peerId) {
map.setIfUndefined(this.conns, m.from, () => new WebrtcConn(this, false, m.from, m.topics)).peer.signal(m.data)
}
break
}
}
}
})
}
}
/**
* @type {Map<string, SignalingConn>}
*/
const conns = new Map()
/**
* @extends Observable<string>
*/
export class WebrtcProvider extends Observable {
/**
* @param {string} room
* @param {Y.Doc} doc
* @param {Object} [opts]
* @param {string} [opts.url]
*/
constructor (room, doc, { url = 'wss://y-webrtc-rgksxuhaol.now.sh' } = {}) {
super()
this.url = url
this.room = room
this.doc = doc
this.conn = map.setIfUndefined(conns, url, () => new SignalingConn(url))
if (this.conn.rooms.has(room)) {
throw error.create('A Yjs Doc connected to that room already exists!')
}
if (this.conn.connected) {
this.conn.send({ type: 'subscribe', topics: [room] })
}
const webrtcRoom = new WebrtcRoom(doc, this, room)
this.conn.rooms.set(room, webrtcRoom)
/**
* @type {awarenessProtocol.Awareness}
*/
this.awareness = new awarenessProtocol.Awareness(doc)
/**
* Listens to Yjs updates and sends them to remote peers
*
* @param {Uint8Array} update
* @param {any} origin
*/
this._docUpdateHandler = (update, origin) => {
if (origin !== this || origin === null) {
const encoder = encoding.createEncoder()
encoding.writeVarUint(encoder, messageSync)
encoding.writeVarString(encoder, room)
syncProtocol.writeUpdate(encoder, update)
broadcast(webrtcRoom, encoder)
}
}
/**
* Listens to Awareness updates and sends them to remote peers
*
* @param {any} changed
* @param {any} origin
*/
this._awarenessUpdateHandler = ({ added, updated, removed }, origin) => {
const changedClients = added.concat(updated).concat(removed)
const encoder = encoding.createEncoder()
encoding.writeVarUint(encoder, messageAwareness)
encoding.writeVarString(encoder, room)
encoding.writeVarUint8Array(encoder, awarenessProtocol.encodeAwarenessUpdate(this.awareness, changedClients))
broadcast(webrtcRoom, encoder)
}
this.doc.on('update', this._docUpdateHandler)
this.awareness.on('change', this._awarenessUpdateHandler)
window.addEventListener('beforeunload', () => {
awarenessProtocol.removeAwarenessStates(this.awareness, [doc.clientID], 'window unload')
})
}
destroy () {
if (this.conn.connected) {
this.conn.send({ type: 'unsubscribe', topics: [this.room] })
}
this.conn.rooms.delete(this.room)
this.doc.off('update', this._docUpdateHandler)
this.awareness.off('change', this._awarenessUpdateHandler)
super.destroy()
}
}

62
tsconfig.json Normal file
View file

@ -0,0 +1,62 @@
{
"compilerOptions": {
/* Basic Options */
"target": "es2018",
"lib": ["es2018", "dom"], /* Specify library files to be included in the compilation. */
"allowJs": true, /* Allow javascript files to be compiled. */
"checkJs": true, /* Report errors in .js files. */
// "jsx": "preserve", /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */
// "declaration": true, /* Generates corresponding '.d.ts' file. */
// "declarationMap": true, /* Generates a sourcemap for each corresponding '.d.ts' file. */
// "sourceMap": true, /* Generates corresponding '.map' file. */
// "outFile": "./", /* Concatenate and emit output to single file. */
// "outDir": "./build", /* Redirect output structure to the directory. */
// "rootDir": "./", /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */
// "composite": true, /* Enable project compilation */
// "removeComments": true, /* Do not emit comments to output. */
"noEmit": true, /* Do not emit outputs. */
// "importHelpers": true, /* Import emit helpers from 'tslib'. */
// "downlevelIteration": true, /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */
// "isolatedModules": true, /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */
/* Strict Type-Checking Options */
"strict": true, /* Enable all strict type-checking options. */
"noImplicitAny": false, /* Raise error on expressions and declarations with an implied 'any' type. */
// "strictNullChecks": true, /* Enable strict null checks. */
// "strictFunctionTypes": true, /* Enable strict checking of function types. */
// "strictPropertyInitialization": true, /* Enable strict checking of property initialization in classes. */
// "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */
// "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */
/* Additional Checks */
// "noUnusedLocals": true, /* Report errors on unused locals. */
// "noUnusedParameters": true, /* Report errors on unused parameters. */
// "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */
// "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */
/* Module Resolution Options */
"moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */
"baseUrl": "./", /* Base directory to resolve non-absolute module names. */
"paths": {
"yjs": ["../yjs/src/index.js"],
"lib0/*": ["../lib0/*"]
},
// "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */
// "typeRoots": [], /* List of folders to include type definitions from. */
// "types": [], /* Type declaration files to be included in compilation. */
// "allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */
"esModuleInterop": true, /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */
// "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */
/* Source Map Options */
// "sourceRoot": "", /* Specify the location where debugger should locate TypeScript files instead of source locations. */
// "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */
// "inlineSourceMap": true, /* Emit a single file with source maps instead of having a separate file. */
// "inlineSources": true, /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */
/* Experimental Options */
// "experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */
// "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */
"maxNodeModuleJsDepth": 5
}
}