This commit is contained in:
commit
88c2e54ece
41
.gitea/workflows/deploy.yml
Normal file
41
.gitea/workflows/deploy.yml
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
name: Deploy
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- master
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Keyscan
|
||||||
|
run: |
|
||||||
|
ssh-keyscan git.koptilnya.xyz >> ~/.ssh/known_hosts
|
||||||
|
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
ssh-key: ${{ secrets.SSH_PRIVATE_KEY }}
|
||||||
|
ssh-strict: false
|
||||||
|
persist-credentials: false
|
||||||
|
|
||||||
|
- name: Build
|
||||||
|
run: docker build -t bpmn-api:latest .
|
||||||
|
|
||||||
|
- name: Stop old container
|
||||||
|
run: docker rm -f bpmn-api || true
|
||||||
|
|
||||||
|
- name: Run
|
||||||
|
run: |
|
||||||
|
docker run -d \
|
||||||
|
--name bpmn-api \
|
||||||
|
--network traefik \
|
||||||
|
--label "traefik.enable=true" \
|
||||||
|
--label "traefik.http.routers.bpmn-api.rule=Host(\`bpmn-api.koptilnya.xyz\`) && PathPrefix(\`/bpmn\`)" \
|
||||||
|
--label "traefik.http.middlewares.bpmn-api.stripprefix.prefixes=/bpmn" \
|
||||||
|
--label "traefik.http.routers.bpmn-api.entrypoints=websecure" \
|
||||||
|
--label "traefik.http.routers.bpmn-api.tls.certresolver=myresolver" \
|
||||||
|
--label "traefik.http.services.bpmn-api.loadbalancer.server.port=80" \
|
||||||
|
bpmn-api:latest
|
||||||
4
.gitignore
vendored
Normal file
4
.gitignore
vendored
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
dist
|
||||||
|
node_modules
|
||||||
|
tmp
|
||||||
|
dbDir
|
||||||
28
.idea/codeStyles/Project.xml
generated
Normal file
28
.idea/codeStyles/Project.xml
generated
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
<component name="ProjectCodeStyleConfiguration">
|
||||||
|
<code_scheme name="Project" version="173">
|
||||||
|
<JSCodeStyleSettings version="0">
|
||||||
|
<option name="USE_SEMICOLON_AFTER_STATEMENT" value="false" />
|
||||||
|
<option name="FORCE_SEMICOLON_STYLE" value="true" />
|
||||||
|
<option name="SPACE_BEFORE_GENERATOR_MULT" value="true" />
|
||||||
|
<option name="USE_DOUBLE_QUOTES" value="false" />
|
||||||
|
<option name="FORCE_QUOTE_STYlE" value="true" />
|
||||||
|
<option name="ENFORCE_TRAILING_COMMA" value="Remove" />
|
||||||
|
<option name="SPACES_WITHIN_OBJECT_LITERAL_BRACES" value="true" />
|
||||||
|
<option name="SPACES_WITHIN_IMPORTS" value="true" />
|
||||||
|
</JSCodeStyleSettings>
|
||||||
|
<codeStyleSettings language="JavaScript">
|
||||||
|
<option name="KEEP_BLANK_LINES_IN_CODE" value="1" />
|
||||||
|
<option name="ALIGN_MULTILINE_PARAMETERS" value="false" />
|
||||||
|
<option name="ALIGN_MULTILINE_FOR" value="false" />
|
||||||
|
<option name="SPACE_BEFORE_METHOD_PARENTHESES" value="true" />
|
||||||
|
<option name="TERNARY_OPERATION_SIGNS_ON_NEXT_LINE" value="true" />
|
||||||
|
<option name="KEEP_SIMPLE_BLOCKS_IN_ONE_LINE" value="true" />
|
||||||
|
<option name="KEEP_SIMPLE_METHODS_IN_ONE_LINE" value="true" />
|
||||||
|
<indentOptions>
|
||||||
|
<option name="INDENT_SIZE" value="2" />
|
||||||
|
<option name="CONTINUATION_INDENT_SIZE" value="2" />
|
||||||
|
<option name="TAB_SIZE" value="2" />
|
||||||
|
</indentOptions>
|
||||||
|
</codeStyleSettings>
|
||||||
|
</code_scheme>
|
||||||
|
</component>
|
||||||
5
.idea/codeStyles/codeStyleConfig.xml
generated
Normal file
5
.idea/codeStyles/codeStyleConfig.xml
generated
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
<component name="ProjectCodeStyleConfiguration">
|
||||||
|
<state>
|
||||||
|
<option name="USE_PER_PROJECT_SETTINGS" value="true" />
|
||||||
|
</state>
|
||||||
|
</component>
|
||||||
6
.idea/inspectionProfiles/Project_Default.xml
generated
Normal file
6
.idea/inspectionProfiles/Project_Default.xml
generated
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
<component name="InspectionProjectProfileManager">
|
||||||
|
<profile version="1.0">
|
||||||
|
<option name="myName" value="Project Default" />
|
||||||
|
<inspection_tool class="StandardJS" enabled="true" level="ERROR" enabled_by_default="true" />
|
||||||
|
</profile>
|
||||||
|
</component>
|
||||||
6
.idea/vcs.xml
generated
Normal file
6
.idea/vcs.xml
generated
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="VcsDirectoryMappings">
|
||||||
|
<mapping directory="$PROJECT_DIR$" vcs="Git" />
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
199
.idea/workspace.xml
generated
Normal file
199
.idea/workspace.xml
generated
Normal file
@ -0,0 +1,199 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="AutoImportSettings">
|
||||||
|
<option name="autoReloadType" value="SELECTIVE" />
|
||||||
|
</component>
|
||||||
|
<component name="ChangeListManager">
|
||||||
|
<list default="true" id="52895fea-6930-4acf-96db-7dbc302cd5a2" name="Changes" comment="deploy">
|
||||||
|
<change beforePath="$PROJECT_DIR$/.idea/codeStyles/Project.xml" beforeDir="false" />
|
||||||
|
<change beforePath="$PROJECT_DIR$/.idea/codeStyles/codeStyleConfig.xml" beforeDir="false" />
|
||||||
|
<change beforePath="$PROJECT_DIR$/.idea/inspectionProfiles/Project_Default.xml" beforeDir="false" />
|
||||||
|
<change beforePath="$PROJECT_DIR$/.idea/vcs.xml" beforeDir="false" />
|
||||||
|
<change beforePath="$PROJECT_DIR$/.idea/workspace.xml" beforeDir="false" />
|
||||||
|
</list>
|
||||||
|
<option name="SHOW_DIALOG" value="false" />
|
||||||
|
<option name="HIGHLIGHT_CONFLICTS" value="true" />
|
||||||
|
<option name="HIGHLIGHT_NON_ACTIVE_CHANGELIST" value="false" />
|
||||||
|
<option name="LAST_RESOLUTION" value="IGNORE" />
|
||||||
|
</component>
|
||||||
|
<component name="Git.Settings">
|
||||||
|
<option name="RECENT_GIT_ROOT_PATH" value="$PROJECT_DIR$" />
|
||||||
|
</component>
|
||||||
|
<component name="GitToolBoxStore">
|
||||||
|
<option name="recentBranches">
|
||||||
|
<RecentBranches>
|
||||||
|
<option name="branchesForRepo">
|
||||||
|
<list>
|
||||||
|
<RecentBranchesForRepo>
|
||||||
|
<option name="branches">
|
||||||
|
<list>
|
||||||
|
<RecentBranch>
|
||||||
|
<option name="branchName" value="master" />
|
||||||
|
<option name="lastUsedInstant" value="1763690829" />
|
||||||
|
</RecentBranch>
|
||||||
|
</list>
|
||||||
|
</option>
|
||||||
|
<option name="repositoryRootUrl" value="file://$PROJECT_DIR$" />
|
||||||
|
</RecentBranchesForRepo>
|
||||||
|
</list>
|
||||||
|
</option>
|
||||||
|
</RecentBranches>
|
||||||
|
</option>
|
||||||
|
</component>
|
||||||
|
<component name="PackageJsonUpdateNotifier">
|
||||||
|
<dismissed value="$PROJECT_DIR$/package.json" />
|
||||||
|
</component>
|
||||||
|
<component name="ProjectColorInfo">{
|
||||||
|
"associatedIndex": 6
|
||||||
|
}</component>
|
||||||
|
<component name="ProjectId" id="35ee0K0VUzZ5kXiT81QCwaajSuA" />
|
||||||
|
<component name="ProjectViewState">
|
||||||
|
<option name="autoscrollFromSource" value="true" />
|
||||||
|
<option name="hideEmptyMiddlePackages" value="true" />
|
||||||
|
<option name="showLibraryContents" value="true" />
|
||||||
|
</component>
|
||||||
|
<component name="PropertiesComponent"><![CDATA[{
|
||||||
|
"keyToString": {
|
||||||
|
"ModuleVcsDetector.initialDetectionPerformed": "true",
|
||||||
|
"RunOnceActivity.ShowReadmeOnStart": "true",
|
||||||
|
"RunOnceActivity.git.unshallow": "true",
|
||||||
|
"git-widget-placeholder": "master",
|
||||||
|
"last_opened_file_path": "/Users/nvkrug/Work/forms/yjs-backend",
|
||||||
|
"node.js.detected.package.eslint": "true",
|
||||||
|
"node.js.detected.package.tslint": "true",
|
||||||
|
"node.js.selected.package.eslint": "(autodetect)",
|
||||||
|
"node.js.selected.package.tslint": "(autodetect)",
|
||||||
|
"nodejs_package_manager_path": "npm",
|
||||||
|
"vue.rearranger.settings.migration": "true"
|
||||||
|
}
|
||||||
|
}]]></component>
|
||||||
|
<component name="RecentsManager">
|
||||||
|
<key name="CopyFile.RECENT_KEYS">
|
||||||
|
<recent name="$PROJECT_DIR$" />
|
||||||
|
</key>
|
||||||
|
</component>
|
||||||
|
<component name="SharedIndexes">
|
||||||
|
<attachedChunks>
|
||||||
|
<set>
|
||||||
|
<option value="bundled-js-predefined-d6986cc7102b-f27c65a3e318-JavaScript-WS-251.23774.424" />
|
||||||
|
</set>
|
||||||
|
</attachedChunks>
|
||||||
|
</component>
|
||||||
|
<component name="TaskManager">
|
||||||
|
<task active="true" id="Default" summary="Default task">
|
||||||
|
<changelist id="52895fea-6930-4acf-96db-7dbc302cd5a2" name="Changes" comment="" />
|
||||||
|
<created>1763474739965</created>
|
||||||
|
<option name="number" value="Default" />
|
||||||
|
<option name="presentableId" value="Default" />
|
||||||
|
<updated>1763474739965</updated>
|
||||||
|
<workItem from="1763474741250" duration="1322000" />
|
||||||
|
<workItem from="1763559458059" duration="3124000" />
|
||||||
|
<workItem from="1763650898532" duration="4224000" />
|
||||||
|
<workItem from="1763671564081" duration="114000" />
|
||||||
|
<workItem from="1763690743110" duration="122000" />
|
||||||
|
</task>
|
||||||
|
<task id="LOCAL-00001" summary="Initial commit">
|
||||||
|
<option name="closed" value="true" />
|
||||||
|
<created>1763650921933</created>
|
||||||
|
<option name="number" value="00001" />
|
||||||
|
<option name="presentableId" value="LOCAL-00001" />
|
||||||
|
<option name="project" value="LOCAL" />
|
||||||
|
<updated>1763650921933</updated>
|
||||||
|
</task>
|
||||||
|
<task id="LOCAL-00002" summary="deploy">
|
||||||
|
<option name="closed" value="true" />
|
||||||
|
<created>1763652368961</created>
|
||||||
|
<option name="number" value="00002" />
|
||||||
|
<option name="presentableId" value="LOCAL-00002" />
|
||||||
|
<option name="project" value="LOCAL" />
|
||||||
|
<updated>1763652368961</updated>
|
||||||
|
</task>
|
||||||
|
<task id="LOCAL-00003" summary="deploy">
|
||||||
|
<option name="closed" value="true" />
|
||||||
|
<created>1763652697122</created>
|
||||||
|
<option name="number" value="00003" />
|
||||||
|
<option name="presentableId" value="LOCAL-00003" />
|
||||||
|
<option name="project" value="LOCAL" />
|
||||||
|
<updated>1763652697122</updated>
|
||||||
|
</task>
|
||||||
|
<task id="LOCAL-00004" summary="deploy">
|
||||||
|
<option name="closed" value="true" />
|
||||||
|
<created>1763652748434</created>
|
||||||
|
<option name="number" value="00004" />
|
||||||
|
<option name="presentableId" value="LOCAL-00004" />
|
||||||
|
<option name="project" value="LOCAL" />
|
||||||
|
<updated>1763652748434</updated>
|
||||||
|
</task>
|
||||||
|
<task id="LOCAL-00005" summary="deploy">
|
||||||
|
<option name="closed" value="true" />
|
||||||
|
<created>1763652805038</created>
|
||||||
|
<option name="number" value="00005" />
|
||||||
|
<option name="presentableId" value="LOCAL-00005" />
|
||||||
|
<option name="project" value="LOCAL" />
|
||||||
|
<updated>1763652805038</updated>
|
||||||
|
</task>
|
||||||
|
<task id="LOCAL-00006" summary="deploy">
|
||||||
|
<option name="closed" value="true" />
|
||||||
|
<created>1763653055665</created>
|
||||||
|
<option name="number" value="00006" />
|
||||||
|
<option name="presentableId" value="LOCAL-00006" />
|
||||||
|
<option name="project" value="LOCAL" />
|
||||||
|
<updated>1763653055665</updated>
|
||||||
|
</task>
|
||||||
|
<task id="LOCAL-00007" summary="deploy">
|
||||||
|
<option name="closed" value="true" />
|
||||||
|
<created>1763653146312</created>
|
||||||
|
<option name="number" value="00007" />
|
||||||
|
<option name="presentableId" value="LOCAL-00007" />
|
||||||
|
<option name="project" value="LOCAL" />
|
||||||
|
<updated>1763653146312</updated>
|
||||||
|
</task>
|
||||||
|
<task id="LOCAL-00008" summary="deploy">
|
||||||
|
<option name="closed" value="true" />
|
||||||
|
<created>1763653348112</created>
|
||||||
|
<option name="number" value="00008" />
|
||||||
|
<option name="presentableId" value="LOCAL-00008" />
|
||||||
|
<option name="project" value="LOCAL" />
|
||||||
|
<updated>1763653348112</updated>
|
||||||
|
</task>
|
||||||
|
<task id="LOCAL-00009" summary="deploy">
|
||||||
|
<option name="closed" value="true" />
|
||||||
|
<created>1763653685850</created>
|
||||||
|
<option name="number" value="00009" />
|
||||||
|
<option name="presentableId" value="LOCAL-00009" />
|
||||||
|
<option name="project" value="LOCAL" />
|
||||||
|
<updated>1763653685850</updated>
|
||||||
|
</task>
|
||||||
|
<task id="LOCAL-00010" summary="deploy">
|
||||||
|
<option name="closed" value="true" />
|
||||||
|
<created>1763654828401</created>
|
||||||
|
<option name="number" value="00010" />
|
||||||
|
<option name="presentableId" value="LOCAL-00010" />
|
||||||
|
<option name="project" value="LOCAL" />
|
||||||
|
<updated>1763654828401</updated>
|
||||||
|
</task>
|
||||||
|
<option name="localTasksCounter" value="11" />
|
||||||
|
<servers />
|
||||||
|
</component>
|
||||||
|
<component name="TypeScriptGeneratedFilesManager">
|
||||||
|
<option name="version" value="3" />
|
||||||
|
</component>
|
||||||
|
<component name="Vcs.Log.Tabs.Properties">
|
||||||
|
<option name="TAB_STATES">
|
||||||
|
<map>
|
||||||
|
<entry key="MAIN">
|
||||||
|
<value>
|
||||||
|
<State />
|
||||||
|
</value>
|
||||||
|
</entry>
|
||||||
|
</map>
|
||||||
|
</option>
|
||||||
|
</component>
|
||||||
|
<component name="VcsManagerConfiguration">
|
||||||
|
<option name="CHECK_CODE_SMELLS_BEFORE_PROJECT_COMMIT" value="false" />
|
||||||
|
<option name="CHECK_NEW_TODO" value="false" />
|
||||||
|
<MESSAGE value="Initial commit" />
|
||||||
|
<MESSAGE value="deploy" />
|
||||||
|
<option name="LAST_COMMIT_MESSAGE" value="deploy" />
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
14
.vscode/launch.json
vendored
Normal file
14
.vscode/launch.json
vendored
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
{
|
||||||
|
// Use IntelliSense to learn about possible attributes.
|
||||||
|
// Hover to view descriptions of existing attributes.
|
||||||
|
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
|
||||||
|
"version": "0.2.0",
|
||||||
|
"configurations": [
|
||||||
|
{
|
||||||
|
"type": "node",
|
||||||
|
"request": "launch",
|
||||||
|
"name": "websocket server",
|
||||||
|
"program": "${workspaceFolder}/bin/server.js"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
12
Dockerfile
Normal file
12
Dockerfile
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
FROM node:22-alpine
|
||||||
|
|
||||||
|
RUN mkdir -p /home/node/app/node_modules && chown -R node:node /home/node/app
|
||||||
|
WORKDIR /home/node/app
|
||||||
|
RUN corepack enable
|
||||||
|
COPY package*.json ./
|
||||||
|
USER node
|
||||||
|
RUN yarn install
|
||||||
|
COPY --chown=node:node . .
|
||||||
|
ENV PORT=80
|
||||||
|
EXPOSE 80
|
||||||
|
CMD [ "npm", "start" ]
|
||||||
21
LICENSE
Normal file
21
LICENSE
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
The MIT License (MIT)
|
||||||
|
|
||||||
|
Copyright (c) 2025 Kevin Jahns <kevin.jahns@protonmail.com>.
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
||||||
81
README.md
Normal file
81
README.md
Normal file
@ -0,0 +1,81 @@
|
|||||||
|
|
||||||
|
# y-websocket-server :tophat:
|
||||||
|
> Simple backend for [y-websocket](https://github.com/yjs/y-websocket)
|
||||||
|
|
||||||
|
The Websocket Provider is a solid choice if you want a central source that
|
||||||
|
handles authentication and authorization. Websockets also send header
|
||||||
|
information and cookies, so you can use existing authentication mechanisms with
|
||||||
|
this server.
|
||||||
|
|
||||||
|
## Quick Start
|
||||||
|
|
||||||
|
### Install dependencies
|
||||||
|
|
||||||
|
```sh
|
||||||
|
npm i @y/websocket-server
|
||||||
|
```
|
||||||
|
|
||||||
|
### Start a y-websocket server
|
||||||
|
|
||||||
|
This repository implements a basic server that you can adopt to your specific use-case. [(source code)](./src/)
|
||||||
|
|
||||||
|
Start a y-websocket server:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
HOST=localhost PORT=1234 npx y-websocket
|
||||||
|
```
|
||||||
|
|
||||||
|
### Client Code:
|
||||||
|
|
||||||
|
```js
|
||||||
|
import * as Y from 'yjs'
|
||||||
|
import { WebsocketProvider } from 'y-websocket'
|
||||||
|
|
||||||
|
const doc = new Y.Doc()
|
||||||
|
const wsProvider = new WebsocketProvider('ws://localhost:1234', 'my-roomname', doc)
|
||||||
|
|
||||||
|
wsProvider.on('status', event => {
|
||||||
|
console.log(event.status) // logs "connected" or "disconnected"
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
## Websocket Server
|
||||||
|
|
||||||
|
Start a y-websocket server:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
HOST=localhost PORT=1234 npx y-websocket
|
||||||
|
```
|
||||||
|
|
||||||
|
Since npm symlinks the `y-websocket` executable from your local `./node_modules/.bin` folder, you can simply run npx. The `PORT` environment variable already defaults to 1234, and `HOST` defaults to `localhost`.
|
||||||
|
|
||||||
|
### Websocket Server with Persistence
|
||||||
|
|
||||||
|
Persist document updates in a LevelDB database.
|
||||||
|
|
||||||
|
See [LevelDB Persistence](https://github.com/yjs/y-leveldb) for more info.
|
||||||
|
|
||||||
|
```sh
|
||||||
|
HOST=localhost PORT=1234 YPERSISTENCE=./dbDir npx y-websocket
|
||||||
|
```
|
||||||
|
|
||||||
|
### Websocket Server with HTTP callback
|
||||||
|
|
||||||
|
Send a debounced callback to an HTTP server (`POST`) on document update. Note that this implementation doesn't implement a retry logic in case the `CALLBACK_URL` does not work.
|
||||||
|
|
||||||
|
Can take the following ENV variables:
|
||||||
|
|
||||||
|
* `CALLBACK_URL` : Callback server URL
|
||||||
|
* `CALLBACK_DEBOUNCE_WAIT` : Debounce time between callbacks (in ms). Defaults to 2000 ms
|
||||||
|
* `CALLBACK_DEBOUNCE_MAXWAIT` : Maximum time to wait before callback. Defaults to 10 seconds
|
||||||
|
* `CALLBACK_TIMEOUT` : Timeout for the HTTP call. Defaults to 5 seconds
|
||||||
|
* `CALLBACK_OBJECTS` : JSON of shared objects to get data (`'{"SHARED_OBJECT_NAME":"SHARED_OBJECT_TYPE}'`)
|
||||||
|
|
||||||
|
```sh
|
||||||
|
CALLBACK_URL=http://localhost:3000/ CALLBACK_OBJECTS='{"prosemirror":"XmlFragment"}' npm start
|
||||||
|
```
|
||||||
|
This sends a debounced callback to `localhost:3000` 2 seconds after receiving an update (default `DEBOUNCE_WAIT`) with the data of an XmlFragment named `"prosemirror"` in the body.
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
[The MIT License](./LICENSE) © Kevin Jahns
|
||||||
4357
package-lock.json
generated
Normal file
4357
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
80
package.json
Normal file
80
package.json
Normal file
@ -0,0 +1,80 @@
|
|||||||
|
{
|
||||||
|
"name": "@y/websocket-server",
|
||||||
|
"version": "0.1.1",
|
||||||
|
"description": "Backend for y-websocket",
|
||||||
|
"type": "module",
|
||||||
|
"sideEffects": false,
|
||||||
|
"funding": {
|
||||||
|
"type": "GitHub Sponsors ❤",
|
||||||
|
"url": "https://github.com/sponsors/dmonad"
|
||||||
|
},
|
||||||
|
"scripts": {
|
||||||
|
"start": "node ./src/server.js",
|
||||||
|
"dist": "rm -rf dist && rollup -c && tsc",
|
||||||
|
"lint": "standard && tsc",
|
||||||
|
"test": "npm run lint",
|
||||||
|
"preversion": "npm run lint && npm run dist && test -e dist/src/server.d.ts && test -e dist/server.cjs"
|
||||||
|
},
|
||||||
|
"bin": {
|
||||||
|
"y-websocket-server": "src/server.js",
|
||||||
|
"y-websocket": "src/server.js"
|
||||||
|
},
|
||||||
|
"files": [
|
||||||
|
"dist/*",
|
||||||
|
"src/*"
|
||||||
|
],
|
||||||
|
"exports": {
|
||||||
|
"./package.json": "./package.json",
|
||||||
|
"./utils": {
|
||||||
|
"import": "./src/utils.js",
|
||||||
|
"require": "./dist/utils.cjs",
|
||||||
|
"types": "./dist/src/utils.d.ts",
|
||||||
|
"default": "./src/utils.js"
|
||||||
|
},
|
||||||
|
"./callback": {
|
||||||
|
"import": "./src/callback.js",
|
||||||
|
"require": "./dist/callback.cjs",
|
||||||
|
"types": "./dist/src/callback.d.ts",
|
||||||
|
"default": "./src/callback.js"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"repository": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "git+https://github.com/yjs/y-websocket-server.git"
|
||||||
|
},
|
||||||
|
"keywords": [
|
||||||
|
"Yjs"
|
||||||
|
],
|
||||||
|
"author": "Kevin Jahns <kevin.jahns@protonmail.com>",
|
||||||
|
"license": "MIT",
|
||||||
|
"bugs": {
|
||||||
|
"url": "https://github.com/yjs/y-websocket-server/issues"
|
||||||
|
},
|
||||||
|
"homepage": "https://github.com/yjs/y-websocket-server#readme",
|
||||||
|
"standard": {
|
||||||
|
"ignore": [
|
||||||
|
"/dist",
|
||||||
|
"/node_modules"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@y/protocols": "^1.0.6-1",
|
||||||
|
"lib0": "^0.2.102"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@types/node": "^24.0.1",
|
||||||
|
"@types/ws": "^8.5.10",
|
||||||
|
"rollup": "^4.39.0",
|
||||||
|
"standard": "^17.1.2",
|
||||||
|
"typescript": "^5.8.3",
|
||||||
|
"ws": "^6.2.1",
|
||||||
|
"yjs": "^14.0.0-7"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"yjs": "^14.0.0-7"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"npm": ">=8.0.0",
|
||||||
|
"node": ">=16.0.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
12
rollup.config.mjs
Normal file
12
rollup.config.mjs
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
export default [{
|
||||||
|
input: ['./src/server.js', './src/utils.js', './src/callback.js'],
|
||||||
|
external: id => /^(lib0|yjs|@y|ws|lodash\.debounce|http|y-leveldb)/.test(id),
|
||||||
|
output: [{
|
||||||
|
dir: 'dist',
|
||||||
|
format: 'cjs',
|
||||||
|
sourcemap: true,
|
||||||
|
entryFileNames: '[name].cjs',
|
||||||
|
chunkFileNames: '[name]-[hash].cjs'
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
]
|
||||||
75
src/callback.js
Normal file
75
src/callback.js
Normal file
@ -0,0 +1,75 @@
|
|||||||
|
import http from 'http'
|
||||||
|
import * as number from 'lib0/number'
|
||||||
|
|
||||||
|
const CALLBACK_URL = process.env.CALLBACK_URL ? new URL(process.env.CALLBACK_URL) : null
|
||||||
|
const CALLBACK_TIMEOUT = number.parseInt(process.env.CALLBACK_TIMEOUT || '5000')
|
||||||
|
const CALLBACK_OBJECTS = process.env.CALLBACK_OBJECTS ? JSON.parse(process.env.CALLBACK_OBJECTS) : {}
|
||||||
|
|
||||||
|
export const isCallbackSet = !!CALLBACK_URL
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {import('./utils.js').WSSharedDoc} doc
|
||||||
|
*/
|
||||||
|
export const callbackHandler = (doc) => {
|
||||||
|
const room = doc.name
|
||||||
|
const dataToSend = {
|
||||||
|
room,
|
||||||
|
data: {}
|
||||||
|
}
|
||||||
|
const sharedObjectList = Object.keys(CALLBACK_OBJECTS)
|
||||||
|
sharedObjectList.forEach(sharedObjectName => {
|
||||||
|
const sharedObjectType = CALLBACK_OBJECTS[sharedObjectName]
|
||||||
|
dataToSend.data[sharedObjectName] = {
|
||||||
|
type: sharedObjectType,
|
||||||
|
content: getContent(sharedObjectName, sharedObjectType, doc).toJSON()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
CALLBACK_URL && callbackRequest(CALLBACK_URL, CALLBACK_TIMEOUT, dataToSend)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {URL} url
|
||||||
|
* @param {number} timeout
|
||||||
|
* @param {Object} data
|
||||||
|
*/
|
||||||
|
const callbackRequest = (url, timeout, data) => {
|
||||||
|
data = JSON.stringify(data)
|
||||||
|
const options = {
|
||||||
|
hostname: url.hostname,
|
||||||
|
port: url.port,
|
||||||
|
path: url.pathname,
|
||||||
|
timeout,
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'Content-Length': Buffer.byteLength(data)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const req = http.request(options)
|
||||||
|
req.on('timeout', () => {
|
||||||
|
console.warn('Callback request timed out.')
|
||||||
|
req.abort()
|
||||||
|
})
|
||||||
|
req.on('error', (e) => {
|
||||||
|
console.error('Callback request error.', e)
|
||||||
|
req.abort()
|
||||||
|
})
|
||||||
|
req.write(data)
|
||||||
|
req.end()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string} objName
|
||||||
|
* @param {string} objType
|
||||||
|
* @param {import('./utils.js').WSSharedDoc} doc
|
||||||
|
*/
|
||||||
|
const getContent = (objName, objType, doc) => {
|
||||||
|
switch (objType) {
|
||||||
|
case 'Array': return doc.getArray(objName)
|
||||||
|
case 'Map': return doc.getMap(objName)
|
||||||
|
case 'Text': return doc.getText(objName)
|
||||||
|
case 'XmlFragment': return doc.getXmlFragment(objName)
|
||||||
|
case 'XmlElement': return doc.getXmlElement(objName)
|
||||||
|
default : return {}
|
||||||
|
}
|
||||||
|
}
|
||||||
31
src/server.js
Executable file
31
src/server.js
Executable file
@ -0,0 +1,31 @@
|
|||||||
|
#!/usr/bin/env node
|
||||||
|
|
||||||
|
import WebSocket from 'ws'
|
||||||
|
import http from 'http'
|
||||||
|
import * as number from 'lib0/number'
|
||||||
|
import { setupWSConnection } from './utils.js'
|
||||||
|
|
||||||
|
const wss = new WebSocket.Server({ noServer: true })
|
||||||
|
const host = process.env.HOST || 'localhost'
|
||||||
|
const port = number.parseInt(process.env.PORT || '1234')
|
||||||
|
|
||||||
|
const server = http.createServer((_request, response) => {
|
||||||
|
response.writeHead(200, { 'Content-Type': 'text/plain' })
|
||||||
|
response.end('okay')
|
||||||
|
})
|
||||||
|
|
||||||
|
wss.on('connection', setupWSConnection)
|
||||||
|
|
||||||
|
server.on('upgrade', (request, socket, head) => {
|
||||||
|
// You may check auth of request here..
|
||||||
|
// Call `wss.HandleUpgrade` *after* you checked whether the client has access
|
||||||
|
// (e.g. by checking cookies, or url parameters).
|
||||||
|
// See https://github.com/websockets/ws#client-authentication
|
||||||
|
wss.handleUpgrade(request, socket, head, /** @param {any} ws */ ws => {
|
||||||
|
wss.emit('connection', ws, request)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
server.listen(port, host, () => {
|
||||||
|
console.log(`running at '${host}' on port ${port}`)
|
||||||
|
})
|
||||||
280
src/utils.js
Normal file
280
src/utils.js
Normal file
@ -0,0 +1,280 @@
|
|||||||
|
import * as Y from 'yjs'
|
||||||
|
import * as syncProtocol from '@y/protocols/sync'
|
||||||
|
import * as awarenessProtocol from '@y/protocols/awareness'
|
||||||
|
|
||||||
|
import * as encoding from 'lib0/encoding'
|
||||||
|
import * as decoding from 'lib0/decoding'
|
||||||
|
import * as map from 'lib0/map'
|
||||||
|
|
||||||
|
import * as eventloop from 'lib0/eventloop'
|
||||||
|
|
||||||
|
import { callbackHandler, isCallbackSet } from './callback.js'
|
||||||
|
|
||||||
|
const CALLBACK_DEBOUNCE_WAIT = parseInt(process.env.CALLBACK_DEBOUNCE_WAIT || '2000')
|
||||||
|
const CALLBACK_DEBOUNCE_MAXWAIT = parseInt(process.env.CALLBACK_DEBOUNCE_MAXWAIT || '10000')
|
||||||
|
|
||||||
|
const debouncer = eventloop.createDebouncer(CALLBACK_DEBOUNCE_WAIT, CALLBACK_DEBOUNCE_MAXWAIT)
|
||||||
|
|
||||||
|
const wsReadyStateConnecting = 0
|
||||||
|
const wsReadyStateOpen = 1
|
||||||
|
const wsReadyStateClosing = 2 // eslint-disable-line
|
||||||
|
const wsReadyStateClosed = 3 // eslint-disable-line
|
||||||
|
|
||||||
|
// disable gc when using snapshots!
|
||||||
|
const gcEnabled = process.env.GC !== 'false' && process.env.GC !== '0'
|
||||||
|
// const persistenceDir = process.env.YPERSISTENCE
|
||||||
|
/**
|
||||||
|
* @type {{bindState: function(string,WSSharedDoc):void, writeState:function(string,WSSharedDoc):Promise<any>, provider: any}|null}
|
||||||
|
*/
|
||||||
|
let persistence = null
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {{bindState: function(string,WSSharedDoc):void,
|
||||||
|
* writeState:function(string,WSSharedDoc):Promise<any>,provider:any}|null} persistence_
|
||||||
|
*/
|
||||||
|
export const setPersistence = persistence_ => {
|
||||||
|
persistence = persistence_
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return {null|{bindState: function(string,WSSharedDoc):void,
|
||||||
|
* writeState:function(string,WSSharedDoc):Promise<any>}|null} used persistence layer
|
||||||
|
*/
|
||||||
|
export const getPersistence = () => persistence
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @type {Map<string,WSSharedDoc>}
|
||||||
|
*/
|
||||||
|
export const docs = new Map()
|
||||||
|
|
||||||
|
const messageSync = 0
|
||||||
|
const messageAwareness = 1
|
||||||
|
// const messageAuth = 2
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {Uint8Array} update
|
||||||
|
* @param {any} _origin
|
||||||
|
* @param {WSSharedDoc} doc
|
||||||
|
* @param {any} _tr
|
||||||
|
*/
|
||||||
|
const updateHandler = (update, _origin, doc, _tr) => {
|
||||||
|
const encoder = encoding.createEncoder()
|
||||||
|
encoding.writeVarUint(encoder, messageSync)
|
||||||
|
syncProtocol.writeUpdate(encoder, update)
|
||||||
|
const message = encoding.toUint8Array(encoder)
|
||||||
|
doc.conns.forEach((_, conn) => send(doc, conn, message))
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @type {(ydoc: Y.Doc) => Promise<void>}
|
||||||
|
*/
|
||||||
|
let contentInitializor = _ydoc => Promise.resolve()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This function is called once every time a Yjs document is created. You can
|
||||||
|
* use it to pull data from an external source or initialize content.
|
||||||
|
*
|
||||||
|
* @param {(ydoc: Y.Doc) => Promise<void>} f
|
||||||
|
*/
|
||||||
|
export const setContentInitializor = (f) => {
|
||||||
|
contentInitializor = f
|
||||||
|
}
|
||||||
|
|
||||||
|
export class WSSharedDoc extends Y.Doc {
|
||||||
|
/**
|
||||||
|
* @param {string} name
|
||||||
|
*/
|
||||||
|
constructor (name) {
|
||||||
|
super({ gc: gcEnabled })
|
||||||
|
this.name = name
|
||||||
|
/**
|
||||||
|
* Maps from conn to set of controlled user ids. Delete all user ids from awareness when this conn is closed
|
||||||
|
* @type {Map<Object, Set<number>>}
|
||||||
|
*/
|
||||||
|
this.conns = new Map()
|
||||||
|
/**
|
||||||
|
* @type {awarenessProtocol.Awareness}
|
||||||
|
*/
|
||||||
|
this.awareness = new awarenessProtocol.Awareness(this)
|
||||||
|
this.awareness.setLocalState(null)
|
||||||
|
/**
|
||||||
|
* @param {{ added: Array<number>, updated: Array<number>, removed: Array<number> }} changes
|
||||||
|
* @param {Object | null} conn Origin is the connection that made the change
|
||||||
|
*/
|
||||||
|
const awarenessChangeHandler = ({ added, updated, removed }, conn) => {
|
||||||
|
const changedClients = added.concat(updated, removed)
|
||||||
|
if (conn !== null) {
|
||||||
|
const connControlledIDs = /** @type {Set<number>} */ (this.conns.get(conn))
|
||||||
|
if (connControlledIDs !== undefined) {
|
||||||
|
added.forEach(clientID => { connControlledIDs.add(clientID) })
|
||||||
|
removed.forEach(clientID => { connControlledIDs.delete(clientID) })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// broadcast awareness update
|
||||||
|
const encoder = encoding.createEncoder()
|
||||||
|
encoding.writeVarUint(encoder, messageAwareness)
|
||||||
|
encoding.writeVarUint8Array(encoder, awarenessProtocol.encodeAwarenessUpdate(this.awareness, changedClients))
|
||||||
|
const buff = encoding.toUint8Array(encoder)
|
||||||
|
this.conns.forEach((_, c) => {
|
||||||
|
send(this, c, buff)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
this.awareness.on('update', awarenessChangeHandler)
|
||||||
|
this.on('update', /** @type {any} */ (updateHandler))
|
||||||
|
if (isCallbackSet) {
|
||||||
|
this.on('update', (_update, _origin, doc) => {
|
||||||
|
debouncer(() => callbackHandler(/** @type {WSSharedDoc} */ (doc)))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
this.whenInitialized = contentInitializor(this)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets a Y.Doc by name, whether in memory or on disk
|
||||||
|
*
|
||||||
|
* @param {string} docname - the name of the Y.Doc to find or create
|
||||||
|
* @param {boolean} gc - whether to allow gc on the doc (applies only when created)
|
||||||
|
* @return {WSSharedDoc}
|
||||||
|
*/
|
||||||
|
export const getYDoc = (docname, gc = true) => map.setIfUndefined(docs, docname, () => {
|
||||||
|
const doc = new WSSharedDoc(docname)
|
||||||
|
doc.gc = gc
|
||||||
|
if (persistence !== null) {
|
||||||
|
persistence.bindState(docname, doc)
|
||||||
|
}
|
||||||
|
docs.set(docname, doc)
|
||||||
|
return doc
|
||||||
|
})
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {any} conn
|
||||||
|
* @param {WSSharedDoc} doc
|
||||||
|
* @param {Uint8Array} message
|
||||||
|
*/
|
||||||
|
const messageListener = (conn, doc, message) => {
|
||||||
|
try {
|
||||||
|
const encoder = encoding.createEncoder()
|
||||||
|
const decoder = decoding.createDecoder(message)
|
||||||
|
const messageType = decoding.readVarUint(decoder)
|
||||||
|
switch (messageType) {
|
||||||
|
case messageSync:
|
||||||
|
encoding.writeVarUint(encoder, messageSync)
|
||||||
|
syncProtocol.readSyncMessage(decoder, encoder, doc, conn)
|
||||||
|
|
||||||
|
// If the `encoder` only contains the type of reply message and no
|
||||||
|
// message, there is no need to send the message. When `encoder` only
|
||||||
|
// contains the type of reply, its length is 1.
|
||||||
|
if (encoding.length(encoder) > 1) {
|
||||||
|
send(doc, conn, encoding.toUint8Array(encoder))
|
||||||
|
}
|
||||||
|
break
|
||||||
|
case messageAwareness: {
|
||||||
|
awarenessProtocol.applyAwarenessUpdate(doc.awareness, decoding.readVarUint8Array(decoder), conn)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.error(err)
|
||||||
|
// @ts-ignore
|
||||||
|
doc.emit('error', [err])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {WSSharedDoc} doc
|
||||||
|
* @param {any} conn
|
||||||
|
*/
|
||||||
|
const closeConn = (doc, conn) => {
|
||||||
|
if (doc.conns.has(conn)) {
|
||||||
|
/**
|
||||||
|
* @type {Set<number>}
|
||||||
|
*/
|
||||||
|
// @ts-ignore
|
||||||
|
const controlledIds = doc.conns.get(conn)
|
||||||
|
doc.conns.delete(conn)
|
||||||
|
awarenessProtocol.removeAwarenessStates(doc.awareness, Array.from(controlledIds), null)
|
||||||
|
if (doc.conns.size === 0 && persistence !== null) {
|
||||||
|
// if persisted, we store state and destroy ydocument
|
||||||
|
persistence.writeState(doc.name, doc).then(() => {
|
||||||
|
doc.destroy()
|
||||||
|
})
|
||||||
|
docs.delete(doc.name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
conn.close()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {WSSharedDoc} doc
|
||||||
|
* @param {import('ws').WebSocket} conn
|
||||||
|
* @param {Uint8Array} m
|
||||||
|
*/
|
||||||
|
const send = (doc, conn, m) => {
|
||||||
|
if (conn.readyState !== wsReadyStateConnecting && conn.readyState !== wsReadyStateOpen) {
|
||||||
|
closeConn(doc, conn)
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
conn.send(m, {}, err => { err != null && closeConn(doc, conn) })
|
||||||
|
} catch (e) {
|
||||||
|
closeConn(doc, conn)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const pingTimeout = 30000
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {import('ws').WebSocket} conn
|
||||||
|
* @param {import('http').IncomingMessage} req
|
||||||
|
* @param {any} opts
|
||||||
|
*/
|
||||||
|
export const setupWSConnection = (conn, req, { docName = (req.url || '').slice(1).split('?')[0], gc = true } = {}) => {
|
||||||
|
conn.binaryType = 'arraybuffer'
|
||||||
|
// get doc, initialize if it does not exist yet
|
||||||
|
const doc = getYDoc(docName, gc)
|
||||||
|
doc.conns.set(conn, new Set())
|
||||||
|
// listen and reply to events
|
||||||
|
conn.on('message', /** @param {ArrayBuffer} message */ message => messageListener(conn, doc, new Uint8Array(message)))
|
||||||
|
|
||||||
|
// Check if connection is still alive
|
||||||
|
let pongReceived = true
|
||||||
|
const pingInterval = setInterval(() => {
|
||||||
|
if (!pongReceived) {
|
||||||
|
if (doc.conns.has(conn)) {
|
||||||
|
closeConn(doc, conn)
|
||||||
|
}
|
||||||
|
clearInterval(pingInterval)
|
||||||
|
} else if (doc.conns.has(conn)) {
|
||||||
|
pongReceived = false
|
||||||
|
try {
|
||||||
|
conn.ping()
|
||||||
|
} catch (e) {
|
||||||
|
closeConn(doc, conn)
|
||||||
|
clearInterval(pingInterval)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, pingTimeout)
|
||||||
|
conn.on('close', () => {
|
||||||
|
closeConn(doc, conn)
|
||||||
|
clearInterval(pingInterval)
|
||||||
|
})
|
||||||
|
conn.on('pong', () => {
|
||||||
|
pongReceived = true
|
||||||
|
})
|
||||||
|
// put the following in a variables in a block so the interval handlers don't keep in in
|
||||||
|
// scope
|
||||||
|
{
|
||||||
|
// send sync step 1
|
||||||
|
const encoder = encoding.createEncoder()
|
||||||
|
encoding.writeVarUint(encoder, messageSync)
|
||||||
|
syncProtocol.writeSyncStep1(encoder, doc)
|
||||||
|
send(doc, conn, encoding.toUint8Array(encoder))
|
||||||
|
const awarenessStates = doc.awareness.getStates()
|
||||||
|
if (awarenessStates.size > 0) {
|
||||||
|
const encoder = encoding.createEncoder()
|
||||||
|
encoding.writeVarUint(encoder, messageAwareness)
|
||||||
|
encoding.writeVarUint8Array(encoder, awarenessProtocol.encodeAwarenessUpdate(doc.awareness, Array.from(awarenessStates.keys())))
|
||||||
|
send(doc, conn, encoding.toUint8Array(encoder))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
62
tsconfig.json
Normal file
62
tsconfig.json
Normal 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": "./dist", /* 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. */
|
||||||
|
"emitDeclarationOnly": true,
|
||||||
|
// "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": {
|
||||||
|
},
|
||||||
|
// "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
|
||||||
|
},
|
||||||
|
"include": ["./src/**/*"]
|
||||||
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user